스프링 어노테이션 정리, 최신화를 주기적으로 할 예정입니다.
스프링 어노테이션
@EventListener(ApplicationReadyEvent.class)
- 스프링 컨테이너가 완전히 초기화를 다 끝내고, 실행 준비가 되었을 때 발생하는 이벤트이다. 스프링이 이 시점에 해당 애노테이션이 붙은 initData() 메서드를 호출해준다.
@Profile("local")
특정 프로필의 경우에만 해당 스프링 빈을 등록한다.
- 여기서는 local 이라는 이름의 프로필이 사용되는 경우에만 testDataInit 이라는 스프링 빈을 등록한다. 이 빈은 앞서 본 것인데, 편의상 초기 데이터를 만들어서 저장하는 빈이다.
- 프로파일 별로 jar빌드도 가능하며 설정도 가능하다.
@Configuration
Bean을 수동으로 등록해서 사용하도록 설정 클래스 역할을 하게 된다.
이 클래스 안에서 @Bean 어노테이션이 동봉된 메소드를 선언하면, 그 메소드를 통해 스프링 빈을 정의하고 생명주기를 설정하게 된다.
@Configuration을 클래스에 적용하고 @Bean을 해당 Class의 method에 적용하면 @Autowired로 Bean을 부를 수 있다.
@Bean
@Configuration 선언한 빈 설정 클래스에 빈 선언을 담당하는 어노테이션으로, 메소드에만 넣을 수 있다. 보통은 메소드 이름이 곧 빈 이름으로 탄생한다.
@Configuration
public class ApplicationConfig {
@Bean
public void configMethod(){
}
}
@Bean에 아무런 값을 지정하지 않았으므로 Method 이름을 camelCase로 변경한 것이 Bean id로 등록된다.
method 이름이 configMethod()인 경우 configMethod가 Bean id 이다.
또는 @Bean(name="configMethod") 로 지정 가능
@ComponentScan
이 클래스는 자바 빈 설정 클래스이며, 이 @ComponentScan 어노테이션에서 제공하는 package 속성을 통해 스프링 빈 범위를 정의할 수 있다.
복수개를 지정하고 싶다면 자바 8에서는 단순히 @Component 같은 어노테이션을 쓰면 되지만, 자바 7 이하에서는 @ComponentScans 어노테이션 안에 넣으면 된다.
이 클래스 안에서 @Bean 어노테이션이 동봉된 메소드를 선언하면, 그 메소드를 통해 스프링 빈을 정의하고 생명주기를 설정하게 된다.
@Configuration을 클래스에 적용하고 @Bean을 해당 Class의 method에 적용하면 @Autowired로 Bean을 부를 수 있다.
@Component
스프링 Bean을 선언하는 클래스를 작성하기 위해 넣는 어노테이션이다.
클래스 위에 설정하여 스프링 Bean으로 등록해주며, ComponentScan을 통해 스캔되어야 한다.
Component에 대한 추가 정보가 없다면Class의 이름을 camelCase로 변경한 것이 Bean id로 사용된다.
하지만@Bean과 다르게 @Component는 name이 아닌 value를 이용해 Bean의 이름을 지정한다.
@Component
public class MyComponent {
}
@Component(value="myComponent")
public class MyComponent {
}
@Import
@Configuration 어노테이션이 선언된 스프링 설정 클래스를 가져온다.
설정파일 클래스명을 기입하여 가져올 수 있으며, 분리된 두 설정파일을 같이 참조해서 써야 할 경우 사용한다.
아래 예시처럼 클래스 두개가 @Configuration으로 등록되었을 때 한쪽에서 참조가 필요한 경우 사용 가능하다.
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
return new DriverManagerDataSource(...);
}
}
@Configuration
@AnnotationDrivenConfig
@Import(DataSourceConfig.class) // <-- AppConfig imports DataSourceConfig
public class AppConfig extends ConfigurationSupport {
@Autowired DataSourceConfig dataSourceConfig;
@Bean
public void TransferService transferService() {
return new TransferServiceImpl(dataSourceConfig.dataSource());
}
}
이게 있으면 빈 선언 시 실행 순서도 보장되기 때문에 가끔 튀어나오는 빈 선언 오류도 막을 수 있다.
@DependsOn
@Bean 및 @Component 등에 정의 및 선언한 빈 생성 시 요구되는 다른 빈을 정의할 때 사용한다.
이 어노테이션을 달면, 이 어노테이션에서 설정한 빈이 등록되기 전 까지 어노테이션이 달린 빈을 선언하지 않게 된다. @Import 어노테이션이 클래스 단위 종속성 관리라면, @DependsOn 어노테이션은 빈 단위의 종속성 관리라고 보면 된다.
빈을 생성하는데 우선순위를 정해야 하는 경우가 있다. 의존관계에 있는 빈을 생성하려면 먼저 생성되어야 하는 빈이 있을 수 밖에 없는데, 보통 스프링이 우선순위 관계를 분석하여, 자동으로 binary 기능을 통해 우선순위를 지정하고 먼저 생성해야 할 Bean을 골라 생성해준다.
그러나 컴포넌트 스캔으로 Bean생성시 우선순위 설정을 명시적으로 해줘야 될 수도 있다.
@DependsOn 으로 선택적으로 설정도 가능하며, 오류찾기가 쉬워지니 명시해주는 경우도 있다.
@Component("A")
public class ComponentA {
// ...
}
@Component("B")
public class ComponentB {
// ...
}
@Component("C")
@DependsOn(value={"A", "B"})
public class ComponentC {
// ...
}
@Service
@Component 어노테이션과 큰 차이는 없으며, Bean으로 등록하는게 Service 클래스 라는 것을 인식시켜 준다.
보통 Service 어노테이션을 달은 클래스에는 ‘비즈니스 로직’을 주로 담당한다.
비즈니스 로직이기에 순수 자바 위주의 로직으로 구현되며, 다른 클래스에서 주입이 필요한 기능은 Bean을 통해 주입받고 사용한다.
보통 스프링 MVC에 자주 쓰기 때문에 MVC 소속 어노테이션이라고 생각하지만 MVC 없어도 선언 가능하다.
@Repository
주로 DataBase에 접근하는 method를 가지고 있는 Class에서 쓰인다.
@Component 어노테이션 처럼 Bean으로 등록시켜주며, Bean으로 등록하는게 Repository 클래스 라는 것을 인식시켜준다.
@Autowired
필드, 메소드, 생성자에다가 넣어 스프링 Bean으로 등록된 클래스를 주입시키는데 사용한다. 스프링이 Type에 따라 알아서 Bean을 주입 해준다.
해당 클래스에 맞는 빈을 가져오는데, 만약 하나만 있으면 잘 되지만, 만약 2개 이상이면 시그니처 명칭에 맞는 빈을 가져오지만, 그 외의 경우 NoUniqueBeanDefinitionException 예외가 발행한다.
@Inject
@Autowired 어노테이션과 비슷한 역할을 한다. 의존성 주입을 위해 사용되며, @Autowired와 유사하게 주입하려고 하는 객체의 타입이 일치하는 객체를 자동으로 주입한다.
타입이 같은 Bean을 먼저 찾는다. 하지만 같은 타입의 Bean 객체가 여러 개 있다면 다음은 이름으로 찾는데, 그래도 없다면 예외가 발생한다.
@Resource
@Autowired와 마찬가지로 Bean 객체를 주입해주는데 차이점은 Autowired는 타입으로, Resource는 Bean ID를 통해 연결해준다. 만약 매핑되는 ID가 없으면 타입을 검색해서 매핑시켜주는데, 타입도 없다면 예외를 발생시킨다. (name="ID")로 ID를 강제 지정할 수도 있다. 타입 또는 이름이 항상 맞으면 좋겠지만 오버라이딩을 사용하는 경우 둘 다 달라질 수 있기 때문에 항상 이름을 명시해서 사용해주는 것이 나중에 보기도 좋고 안정적이다.
javax.annotation.Resource표준 자바(JSR-250 표준) Annotation으로, Spring Framework 2.5.* 부터 지원 가능한 Annotation이다. Annotation 사용으로 인해 특정 Framework에 종속적인 어플리케이션을 구성하지 않기 위해서는 @Resource를 사용할 것을 권장한다.
@Resource를 사용하기 위해서는 class path 내에 jsr250-api.jar 파일을 추가해야 한다.필드, 입력 파라미터가 한 개인 bean property setter method에 적용 가능하다.
https://codevang.tistory.com/256
@Qualifier
@Qualifier("id")
@Autowired
public classname() { ... }
@Autowired와 같이 쓰이며, 같은 타입의 Bean 객체가 있을 때 해당 아이디를 적어 원하는 Bean이 주입될 수 있도록 하는 Annotation이다.
같은 Class 구현체 로 두 개 이상 Bean이 등록되어 있을 경우, 구분이 필요하기 때문이다. 구분하지 않으면 Spring이 어떤 Bean을 주입해야 할지 알 수 없어서 Spring Container를 초기화하는 과정에서 예외를 발생시킨다. 이 경우 @Qualifier을 @Autowired와 함께 사용하여 정확히 어떤 bean을 사용할지 지정하여 특정 의존 객체를 주입할 수 있도록 한다.
@PostConstruct, @PreConstruct
의존하는 객체를 생성한 이후 초기화 작업을 위해 객체 생성 전/후에(pre/post) 실행해야 할 method 앞에 붙인다.
객체의 초기화 부분
- 객체가 생성된 후 별도의 초기화 작업을 위해 실행하는 메소드를 선언한다.
- @PostConstruct 어노테이션을 설정해놓은 init 메소드는 WAS가 띄워질 때 객체 생성 전에 실행된다
- @PreConstruct 어노테이션을 설정헤놓은 메소드는 객체 생성 후에 실행된다.
@PreDestroy
객체를 제거하기 전(pre)에 해야할 작업을 수행하기 위해 사용한다.
스프링 컨테이너에서 객체(빈)를 제거하기 전에 해야할 작업이 있다면 메소드위에 사용하는 어노테이션.
- close() 하기 직전에 실행 -> ((AbstractApplicationContext) context).close()
@PropertySource
해당 프로퍼티 파일을 Environment로 로딩하게 해준다. 클래스에 @PropertySource("classpath:/settings.properties")라고 적고 클래스 내부에 @Resource를 Environment타입의 멤버 변수앞에 적으면 매핑된다.
@ConfigurationProperties
yaml파일 읽는다. default로 classpath:application.properties 파일이 조회된다. 속성 클래스를 따로 만들어두고 그 위에 (prefix="mail")을 써서 프로퍼티의 접두사를 사용할 수 있다.
@Component
@ConfigurationProperties(prefix = "mail")
@Data
public class mailSiteProperties {
private String naver;
private String google;
}
# application.properties 의 아래 mail
mail.host=[mailserver@mail.com](<mailto:mailserver@mail.com>)
mail.port=9000
mail.defaultRecipients[0]=[admin@mail.com](<mailto:admin@mail.com>)
mail.defaultRecipients[1]=[customer@mail.com](<mailto:customer@mail.com>)
mail.naver=www.naver.com
mail.google=www.google.com
https://programmer93.tistory.com/47
위처럼 만들어졌다면, mailSiteProperties 가 Bean으로 등록되고 불러올 때, 프로퍼티의 값이 자동으로 매핑되어 주입 되도록 할 수 있다.
아래에 보면 mailSiteProperties 에는 프로퍼티에 설정한 값이 자동으로 주입되어 나타난다.
@Lookup
singleton bean에게 prototype scope인 bean을 주입할 때 사용한다
@Scope("prototype")
@Component
class Notification {
init {
println("Notification created")
}
}
@Service
class CommonService {
@Lookup
fun getNotification(): Notification? {
return null
}
}
@Primary
@Configuration 선언한 빈 설정 클래스에 빈 선언을 담당하면서, 기본 빈 요소를 정의할 때 사용한다.
만약 같은 클래스로 여러 개의 빈을 설정했을 때, 이 어노테이션을 추가로 달면, @Autowired 어노테이션을 통해 빈을 주입 시 이 어노테이션을 가진 빈을 우선적으로 가져온다고 보면 된다.
의존성 주입 시 같은 타입의 Bean이 여럿 등록되었다면, 우선순위를 직접 정해주는 게 더 나을 수도 있다. @Primary가 붙은 Bean이 @Autowired로 먼저 매핑되도록 설정 가능하다.
@Required
일반적인 빈 클래스를 초기화 시, 반드시 설정(setter)해야 하는 setter 메소드를 정의할 때 사용한다. @Bean 선언 및 XML 방식의 빈 선언 시, 만약 이 어노테이션을 통해 빈 속성을 정의하지 않으면 BeanInitializationException 예외가 발생한다.
public class RequiredClass {
private int number;
@Required // 반드시 프로퍼티를 이용하여 값을 주입받도록 정의. 값이 안 들어오면 에러나게 함.
public void setNumber(int number)
{ this.number = number; }
public void display()
{ System.out.println("RequiredClass.number = "+number); }
}
선언적 어노테이션(@Component 등)에도 넣을 수 있다. setter method에 적용해주면 Bean 생성시 필수 프로퍼티 임을 알린다. @Required을 사용하여 반드시 필요한 속성들을 정의한다.
엄격한 체크, 그렇지 않으면 BeanInitializationException 예외를 발생.
@Value
생성자, setter 따위의 메소드, 필드 등에다가 스프링에서 설정한 값을 주입할 수 있다.
주로 application.properties 및 스프링이나 자바 property 값을 가져올 때 쓴다.
properties에서 값을 가져와 적용할 때 사용한다.
@Value("${value.from.file}")
private String valueFromFile;
클래스 안에서 선언 후 사용 가능하며, 지역변수에는 불가능하므로 메서드 안에서는 선언 안된다.
SpEL을 이용해서 조금 더 고급스럽게 쓸 수 있다. @Value(#{systemProperties['priority'] ?: 'some default'})
SpEL 예제
https://devoong2.tistory.com/entry/SpELSpring-Expression-Langauge-사용법-어노테이션에-SpEL로-변수-전달하기
@Lazy
이 어노테이션을 선언한 Bean, getter 및 필드는 다른 빈처럼 스프링 시작 시 빈 초기화를 하는 것이 아닌, 처음 가져올 때 빈 초기화를 하게 된다.
미리 초기화를 시키지 않고 선언 될 때 Bean으로 사용하기 되는 것이다.
@Component나 @Bean Annotation과 같이 쓰는데 Class가 로드될 때 스프링에서 바로 bean등록을 마치는 것이 아니라 실제로 사용될 때 로딩이 이뤄지게 하는 방법이다.
초기화 이후 값이 들어와야 동작이 가능한 Bean일 경우 @Lazy 어노테이션을 써서 초기화를 늦출 수 있지만, 조심해서 써야 한다. 지연 초기화를 한다고 해도, 수행 시간을 예측하기 어려운 경우가 많기 때문이다.
오히려 즉시 초기화를 통해 어플리케이션 구성 단계에서부터 오류가 발생하면 잡아낼 수 있어서 잘 사용되지 않는다.
지연 초기화는 지연되기 때문에 오류를 발생 시켜서 잡으려면 해당 객체를 사용해야만 오류를 발견할 수 있다.
@Scope
빈의 생명주기를 설정한다. 기본값은 뭐 많이 쓰는 singleton 이고, 그다음 매 가져올 때마다 빈을 생성해서 보내주는 prototype 이 있으며, 스프링 MVC가 있을 경우 요청 단위인 request, 세션 단위인 session 및 globalSession 생명주기를 추가로 설정할 수 있다.
주로 싱글턴을 쓰지만, 공유변수 사용으로 문제가 될 경우 프로토타입으로 써야될 수도 있다.
@Profile
이 어노테이션이 달린 빈은 특정 프로필에서만 동작하도록 설정할 수 있다.
예를 들어 spring.profile=dev 설정 시 @Profile("dev") 일 때만 빈으로 초기화된다.
다른 프로필로 하면 없는 빈이 되어, 프로파일 기준으로 선택적 빈 등록이 가능하다.
@SpringBootApplication
스프링 부트는 기본적으로 이 어노테이션 기준으로 동작하도록 되어 있다. 스프링에서 필수적인 초기화를 담당한다. @EnableAutoConfiguration, @ComponentScan, @Configuration 어노테이션이 함축되어 있다. 이 클래스를 기준으로 빈 스캔을 하게 되며, 여러 어노테이션 설정을 붙일 수 있다.
@EnableAutoConfiguration
스프링 부트의 핵심 어노테이션으로, 여태까지 Spring boot 없이 XML이던 자바던 필수적으로 annotation-driven 이나 annotation-config 등... 필수적으로 스프링에 세팅하는 왠만한 것들을 자동 설정하여 일단 돌아가게 하도록 도와주는 어노테이션이다. Spring Application Context를 만들 때 자동으로 설정하는 기능을 켠다. classpath의 내용에 기반해서 자동으로 생성해준다. 만약 tomcat-embed-core.jar가 존재하면 톰캣 서버가 setting된다.
@SpringBootApplication에 포함된 어노테이션이며 알아두자.
Spring MVC 어노테이션
@Controller
웹 요청의 기준을 담당한다. 이 어노테이션을 통해 빈 등록과 동시에 라우팅 테이블에 등록하는 중요한 어노테이션이다. API와 view를 동시에 사용하는 경우에 사용한다.대신 API 서비스로 사용하는 경우는 @ResponseBody를 사용하여 객체를 반환한다.view(화면) return이 주목적
@ResponseBody
@ResponseBody: 컨트롤러 메소드 리턴값에 이 어노테이션을 선언함으로써 스프링은 해당 응답 객체를 클라이언트가 요구하는 요청 내용 유형(Content-Type)에 따라 응답하도록 도와주는 어노테이션이다. 보통은 Jackson 모듈에 의해 json 유형으로 응답해줄 것이며, 클라이언트가 xml 응답을 요청하면 xml 응답을 해주기도 하고, 경우에 따라 응답 유형을 mimeType 에 따라 설정할 수 있다.(예: MessagePack 등)
@RestController
이 어노테이션을 달면, 모든 컨트롤러 메소드는 @ResponseBody 어노테이션이 달린 반환 값을 달고 다니게 된다. Spring에서 Controller 중 View로 응답하지 않는, Controller를 의미한다. method의 반환 결과를 JSON 형태로 반환 view가 필요없는 API만 지원하는 서비스에서 사용한다.Spring 4.0.1부터 제공 @RestController = @Controller + @ResponseBody
@RequestMapping(method = RequestMethod.GET, value = "/path")
메소드에 달면, 해당 메소드는 이 어노테이션이 설정한 경로를 호출했을 때 메소드가 설정한 응답값을 받게 되며, 컨트롤러 클래스에 달면, 클래스에 설정한 경로를 기준삼아 각 메소드는 클래스 경로의 하위 경로로 추가되어 경로가 잡히게 된다. method 설정으로 GET만 받거나, POST로 받는 등 특정 HTTP 메소드에만 응답 가능하도록 설정할 수 있으며, GET 의 경우 @GetMapping 같이 메소드 별칭 어노테이션이 있으니 이걸 활용해도 된다. consumes 속성으로 요청 값에 대한 유형을 한정지을 수 있으며, produces 속성을 통해 응답 유형을 강제할 수 있다. 이들 둘이 설정 시 지정 타입 안맞으면 400 Bad Request 응답을 뱉도록 스프링에서 설정되어 있다.
@RequestParam(value="name", defaultValue="World")
컨트롤러 메소드 인자에서 요청값을 받을 때, 요청 주소(URL)에 ? 뒤로 시작하는 질의 문자열, POST 전송 시 요청받을 키/값, 등을 받을 수 있으며, required 속성으로 필수 여부를 지정할 수 있고, defaultValue 속성을 통해 요청이 없을 경우 대체 기본값을 설정할 수 있다. 만약 required = true 일 경우 해당 파라미터 없이 호출하면 400 Bad Request 응답을 뱉도록 스프링에서 설정되어 있다. Spring Webflux 사용시 주의!: Spring WebFlux 사용시 MVC 패턴을 사용할 수 있으나, @RequestParam 어노테이션은 요청 주소 뒤에 질의 문자열만 받게 된다. 만약 POST 를 통해 얻은 값을 얻고 싶으면, @ModelAttribute 어노테이션을 사용해야 한다.
@PathVariable("placeholderName")
동적 라우팅 구성 시, (예: /path/to/{placeholderName}) 동적 라우팅에 대한 바인딩 값을 가져오는 인자 어노테이션이다. 길게 설명할 거 없이 동적 라우팅에서 동적 값을 가져올 때 없어서 안될 어노테이션이다. @Controller
// 1) Class Level
//모든 메서드에 적용되는 경우 “/home”로 들어오는 모든 요청에 대한 처리를 해당 클래스에서 한다는 것을 의미
@RequestMapping("/home")
public class HomeController {
// an HTTP GET for /home
*@RequestMapping(method = RequestMethod.GET)
public String getData(Model model) {
...
}
//* 2) Handler Level
/* 요청 url에 대해 해당 메서드에서 처리해야 되는 경우
“/home/data” POST 요청에 대한 처리를 addData()에서 한다는 것을 의미한다.
value: 해당 url로 요청이 들어오면 이 메서드가 수행된다.
method: 요청 method를 명시한다. 없으면 모든 http method 형식에 대해 수행된다.
*/
// an HTTP POST for /home/data
*@RequestMapping(value = "/data", method = RequestMethod.POST)
public String addData(Employee employee) {
...
}*
@CookieValue
쿠키 값을 parameter로 전달 받을 수 있는 방법이다. 해당 쿠키가 존재하지 않으면 500 에러를 발생시킨다.
속성으로 required가 있는데 default는 true다.false를 적용하면 해당 쿠키 값이 없을 때 null로 받고 에러를 발생시키지 않는다.
// 쿠키의 key가 auth에 해당하는 값을 가져옴
public String view(@CookieValue(value="auth")String auth){...};
public String view(@CookieValue(value="auth", required="false")String auth){...};
@CrossOrigin
CORS 보안상의 문제로 브라우저에서 리소스를 현재 origin에서 다른 곳으로의 AJAX요청을 방지하는 것이다. 즉 해당 도메인에서의 요청만 받도록 해주는 어노테이션이다.
CORS 는 에러나 오류가 아니다. Cross-Origin Resource Sharing Policy 이다. CORS란 현재 Ip가 아닌 다른 Ip로 리소스를 요청하는 구조라고 일단은 생각하면 된다. 최신 브라우저의 대부분은 헤더와 CORS 정책을 적용하기 때문에, CORS 표준을 맞추기 위해서는 모든 개발자 혹은 관리자가 알아야 한다.
하나의 보안정책으로 브라우저에서 지원하며 개발하다보면 마주치는 문제이다. 클라이언트에서 요청하는데 해당 origin 서버가 아닌 다른 서버로 리소스를 요청하는 것을 막는 것이다. (SOP Same Origin Policy 이며, )
해당 Origin 서버를 확인하기 위해 3가지를 만족해야 한다.
- 스키마
- HOST
- Port
3가지를 만족하면 SOP에 걸리지 않아 요청에 문제가 없다. 그런데 브라우저에서 javascript로 동기적 요청을 하는데 SOP에 걸려 요청실패가 뜰 수도 있다. 이는 XSS나 CSRF 같은 보안상의 이슈를 막을 수 있다.
그렇다면 클라이언트에선 브라우저를 통해 서버의 응답을 받은 상태에서 같은 Origin의 자원만 공유 받을 수 있고, 다른 Origin의 자원은 이용 못하는 것인가? 동적으로 요청 및 정보를 받는 것도 불가능한가? 이것을 해결하는 것이 Access-Control-Allow-Origin 이다. responseHeader에 Access-Control을 위한 헤더를 넣어서 응답시키면 된다고 하며, 이는 서버가 해줘야 한다.
즉, 서버가 CORS를 지원하기 위해서 SOP를 해결해야 한다는 소리다. SOP를 지원하기 위한 방법으로 3가지가 있다.
- CorsFilter 로 직접 response에 header 를 넣어주기
- Controller 에서 @CrossOrigin 어노테이션 추가하기
- WebMvcConfigurer 를 이용해서 처리하기
여기서 2번에 해당하는 @CrossOrigin 어노테이션을 컨트롤러 메소드에 추가하여 해결할 수 있다.
@GetMapping
@CrossOrigin(origins = "<http://front-server.com>") // 메서드에서 설정
public ResponseEntity getAllThreatLogs() {
return ResponseEntity.ok(threatService.getAllThreatLogCount());
}
//기본 도메인이 [<http://teck10.tistory.com>](<http://teck10.tistory.com/>) 인 곳에서 온 ajax요청만 받아주겠다.
@CrossOrigin(origins = "[<http://teck10.tistory.com>](<http://teck10.tistory.com/>)", maxAge = 3600)
참고 : https://wonit.tistory.com/572
@RequestMapping이 있는 곳에 사용하면 해당 요청은 타 도메인에서 온 ajax요청을 처리해준다.
@ModelAttribute
view에 객체를 전달하기 위해 model에 attribute를 선언하는 작업을 간편하게 해준다. parameter를 Class(VO/DTO)의 멤버 변수로 binding 해주는 Annotation이다. binding 기준은 <input name="id" /> 처럼 어떤 태그의 name값이 해당 Class의 멤버 변수명과 일치해야하고 setmethod명도 일치해야한다.
@Controller
@RequestMapping("/person/")
public class PersonController{
//view에서 myMEM으로 던져준 데이터에 담긴 id 변수를 Person타입의 person이라는 객체명으로 바인딩.
@RequestMapping(value = "/info", method=RequestMethod.GET)
public void show(@ModelAttribute("myMEM") Person person, Model model){
model.addAttribute(service.read(person.getId()));
}
}
@SessionAttributes
Session에 data를 넣을 때 쓰는 Annotation이다. @SessionAttributes("name")이라고 하면 Model에 key값이 "name"으로 있는 값은 자동으로 세션에도 저장되게 한다.
@GetMapping("/") // @SessionAttribute 으로 세션 가져오기
public String homeLoginV3Spring(@SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember, Model model){
// @SessionAttribute 세션을 생성하는 기능이 아닙니다. 세션이 있으면 세션을 선언한 객체로 매핑시켜줍니다.
// @SessionAttribute 기존에 session에 setAttribute 한게 있어야 이 기능이 가능합니다.
log.warn("LoginComplete member={}",loginMember);
//세션에 회원 데이터가 없으면 home
if (loginMember == null) return "home";
//세션이 유지되면 로그인으로 이동
model.addAttribute("member", loginMember);
return "loginHome";
}
@Valid
빈 검증기(Bean Validator)를 이용해 객체의 제약 조건을 검증하도록 지시하는 어노테이션, 스프링 부트가 spring-boot-starter-validation 라이브러리를 넣으면 자동으로 Bean Validator(빈 검증기)를 인지하고 스프링에 통합한다. 스프링에서 LocalValidatorFactoryBean 을 인식하고 이걸 기준으로 관련 어노테이션에 붙은 정보가 맞는지 알아서 검증을 해준다.
아래 ItemValidation 클래스에 맞게 유효성 검증 조건을 어노테이션 으로 등록.
@Data
public class ItemValidation {
private Long id;
// @NotNull : 인자로 들어온 필드 값에 null 값을 허용하지 않는다
// @NotBlank : null 이 아닌 값이다. 공백이 아닌 문자를 하나 이상 포함한다
// @Null : 어떤 타입이든 수용한다.
// @Email : 인자로 들어온 값을 Email 형식을 갖추어야 한다.
@NotBlank
private String itemName;
@NotNull
@Range(min = 1000, max=1000000)
private Integer price;
@NotNull
@Max(9999)
private Integer quantity;
}
@RestController
@Slf4j
public class TestController {
@PostMapping("/user")
public ResponseEntity<String> savePost(final @Valid @RequestBody ItemValidation item) {
log.info(item.toString());
return ResponseEntity.ok().body("postDto 객체 검증 성공");
}
}
조건에 맞는지 어노테이션 기준으로 검증 후 이상 없다면 정상적으로 수행 된다. 잘못된 객체 값이 나갔을 때 Springboot에 올라온 Log를 살펴보면 MethodArgumentNotValidException이 발생했음을 알 수 있어, 이 Exception을 사용하여 custom한 ErrorMessage를 response로 내보낼 수도 있다.
@ControllerAdvice에 @ExceptionHandler를 지정하여 예외처리를 할 수 있다.
@RestControllerAdvice
public class ApiControllerAdvice {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex){
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors()
.forEach(c -> errors.put(((FieldError) c).getField(), c.getDefaultMessage()));
return ResponseEntity.badRequest().body(errors);
}
}
@Validated
Spring에서는 @Valid를 대신해 AOP 기반으로 메소드의 요청을 가로채서 유효성 검증을 진행해주는 @Validated를 제공하고 있다. @Validated는 JSR 표준 기술이 아니며 Spring 프레임워크에서 제공하는 어노테이션 및 기능이다. @Valid와 같은 기능을 제공하며, 인터페이스를 이용한 그룹기능으로 묶으려면 @Validated를 써야 한다.
또한 어노테이션을 이용한 검증으로 필드 단위로만 검증이 가능하다보니, 복합적인 필드의 조건을 가지고 검증하려면 아래처럼 별도 로직을 만들어 줄 필요도 있다.
@PostMapping("/add") // @Valid 는 자바 표준, @Validated는 스프링 표준, 결과는 같지만, Groups기능은 스프링표준(@Validated) 에서만 사용가능.
public String addItem(@Validated(SaveCheck.class) @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
// Bean Validation Group @Validated(SaveCheck.class) 컨트롤러마다 특정그룹의 어노테이션 정보로 검증하겠다는 소리
//특정 필드 validation이 불가한 요청, 직접 reject로 구현
if (item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
bindingResult.reject("totalPriceMin", new Object[]{10000,
resultPrice}, null);
}
}
return "redirect:/validation/v3/items/{itemId}";
}
@Valid @Validated 자세한 정리
https://mangkyu.tistory.com/174
@InitBinder
@Valid 애노테이션으로 유효성 검증이 필요하다고 한 객체를 가져오기전에 수행해야할 method를 지정한다.
@InitBinder("Item")
public void initBinderForEvent(WebDataBinder webDataBinder) { // The return type must be void.
webDataBinder.addValidators(new CustomtValidator());
}
@Component
public class ItemValidator implements Validator {
....
}
- @InitBinder(“Item”)
- 해당 Controller에 들어오는 요청 중 Item객체에만 CustomValidator( )가 적용된다. 별도 검증기를 직접 만드는 경우 사용하면 유용하다.
@RequestAttribute
Request에 설정되어 있는 속성 값을 가져올 수 있다.
@RequestBody
요청이 온 데이터(JSON이나 XML형식)를 바로 Class나 model로 매핑하기 위한 Annotation이다. POST나 PUT, PATCH로 요청을 받을때에, 요청에서 넘어온 body 값들을 자바 타입으로 파싱해준다.
이 어노테이션이 붙은 파라미터에는 http요청의 본문(body)이 그대로 전달된다.일반적인 GET/POST의 요청 파라미터라면 @RequestBody를 사용할 일이 없을 것이다.반면에 xml이나 json기반의 메시지를 사용하는 요청의 경우에 이 방법이 매우 유용하다.HTTP 요청의 바디내용을 통째로 자바객체로 변환해서 매핑된 메소드 파라미터로 전달해준다.
HTTP POST 요청에 대해 request body에 있는 request message에서 값을 얻어와 매핑한다.RequestData를 바로 Model이나 클래스로 매핑한다. 이를테면 JSON 이나 XML같은 데이터를 적절한 messageConverter로 읽을 때 사용하거나 POJO 형태의 데이터 전체로 받는 경우에 사용한다.
@RequestMapping(value = "/book", method = RequestMethod.POST)
public ResponseEntity<?> someMethod(@RequestBody Book book) {
// we can use the variable named book which has Book model type.
try {
service.insertBook(book);
} catch(Exception e) {
e.printStackTrace();
}
// return some response here
}
@RequestHeader
Request의 header값을 가져올 수 있다. 메소드의 파라미터에 사용한다.
//ko-KR,ko;q=0.8,en-US;q=0.6
@PostMapping("/info")
public Item iformItem(@RequestHeader(value="Accept-Language") String headerA) {
log.info("Header Accept-Language : {}", headerA);
...
}
@RequestParam
request의 parameter에 있는 값을 매핑해서 파라미터에 넣어준다.
method의 파라미터에 사용된다.?moviename=thepurge 와 같은 쿼리 파라미터를 파싱해준다. HTTP GET 요청에 대해 매칭되는 request parameter 값이 자동으로 들어간다. url 뒤에 붙는 parameter 값을 가져올 때 사용한다.
반드시 들어와야 되는 값인 경우 required=true로 설정 가능하다. 기본값임.
@RequestPart
Request로 온 MultipartFile을 바인딩해준다.
@RequestMapping("/request-param-required")
public String requestParamRequired(
@RequestPart("file") MultipartFile file {
.....
return "ok";
}
@ResponseBody
HttpMessageConverter를 이용하여 JSON 혹은 xml 로 요청에 응답할수 있게 해주는 Annotation이다.
view가 아닌 JSON 형식의 값을 응답할 때 사용하는 Annotation으로 문자열을 리턴하면 그 값을 http response header가 아닌 response body에 들어간다.
이미 RestController Annotation이 붙어 있다면, 쓸 필요가 없지만, @Controller 어노테이션이 붙은 단순 컨트롤러라면, HttpResponse로 응답 할 수 있게 해준다.
만약 객체를 return하는 경우 JACKSON 라이브러리에 의해 문자열로 변환되어 전송된다. context에 설정된 viewResolver를 무시한다고 보면된다.
@PathVariable
method parameter 앞에 사용하면서 해당 URL에서 {특정값}을 변수로 받아 올 수 있다.
@RequestMapping(value = "/some/path/{id}", method = RequestMethod.GET)
public ResponseEntity<?> someMethod(@PathVariable int id) {
...
}
HTTP 요청에 대해 매핑되는 request parameter 값이 자동으로 Binding 된다. uri에서 각 구분자에 들어오는 값을 처리해야 할 때 사용한다. REST API에서 값을 호출할 때 주로 많이 사용한다.
// <http://localhost:8080/index/1> 호출
@PostMapping("/index/{idx}")
@ResponseBody
public boolean deletePost(@PathVariable("idx") int postNum) {
return postService.deletePost(postNum);
}
@RequestParam와 @PathVariable 동시 사용 예제
@GetMapping("/user/{userId}/invoices")
public List<Invoice> listUsersInvoices(
@PathVariable("userId") int user,
@RequestParam(value = "date", required = false) Date dateOrNull)
{
...
}
위의 경우 GET /user/{userId}/invoices?date=190101 와 같이 uri가 전달될 때, 구분자 {userId}는 @PathVariable(“userId”)로, 뒤에 붙은 parameter는 @RequestParam(“date”)로 받아온다.
@ExceptionHandler(ExceptionClassName.class)
해당 클래스의 예외를 캐치하여 처리한다.
@ControllerAdvice
Class 위에 ControllerAdvice를 붙이고 어떤 예외를 잡아낼 것인지는 각 메소드 상단에 @ExceptionHandler(예외클래스명.class)를 붙여서 기술한다.
@RestControllerAdvice
@ControllerAdvice + @ResponseBody다.
@ResponseStatus
사용자에게 원하는 response code와 reason을 return해주는 Annotation이다. @ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "my page URL changed..") => 예외처리 함수 앞에 사용한다.
Spring Cloud Eureka
@EnableEurekaServer
Spring Cloud Eureka에서 Eureka 서버로 만들어준다.
@EnableEurekaServer
@SpringBootApplication
public class EurekaServiceApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServiceApplication.class, args);
}
}
@EnableDiscoveryClient
Eureka 서버에서 관리될 수 있는 클라이언트 임을 알려주기위한 Annotation이다.
@Transactional
데이터베이스 트랜잭션을 설정하고 싶은 method에 Annotation을 적용하면 method 내부에서 일어나는 데이터베이스 로직이 전부 성공하게되거나 데이터베이스 접근중 하나라도 실패하면 다시 롤백할 수 있게 해주는 Annotation이다.
스프링 AOP를 통해 제공하는 기능이어서, 스프링 환경에서만 동작한다.
- @Transaction(readOnly=true, rollbackFor=Exception.class)에서 readOnly는 읽기전용임을 알리고 rollbackFor는 해당 Exception이 생기면 롤백하라는 뜻이다.
- @Transaction(noRollbackFor=Exception.class)는 해당 Exception이 나타나도 롤백하지 말라는 뜻이다.
- @Transaction(timeout = 10)은 10초안에 해당 로직을 수행하지 못하면 롤백하라는 뜻이다.
메소드 내에서 Exception이 발생하면 해당 메소드에서 이루어진 모든 DB 작업을 초기화한다.(롤백을 수행해준다. 트랜잭션을 보장하기 위해 커밋전으로 들어가며, 이를 위해 autoCommit을 끄고 시작하며, 작업이 완료된뒤 원상복구됨.)
모든 처리가 정상적으로 됐을때만 DB에 커밋하며 그렇지 않은 경우엔 커밋하지 않고 롤백한다. 비지니스 로직과 트랜잭션 관리는 대부분 Service에서 관리한다. 따라서 일반적으로 DB 데이터를 등록/수정/삭제 하는 Service 메소드에 주로 사용된다.
@Cacheable
method 앞에 지정하면 해당 method를 최초에 호출하면 캐시에 적재하고 다음부터는 동일한 method 호출이 있을 때 캐시에서 결과를 가져와서 return하므로 method 호출 횟수를 줄여주는 Annotation이다.
주의할 점은 입력이 같으면 항상 출력이 같은 method(=순수 함수)에 적용해야한다. 그런데 또 항상 같은 값만 뱉어주는 메서드에 적용하려면 조금 아쉬울 수 있다. 따라서 메서드 호출에 사용되는 자원이 많고 자주 변경되지 않을 때 사용하고 나중에 수정되면 캐시를 없애는 방법을 선택할 수 있다. @Cacheable(value="cacheKey"), @Cacheable(key="cacheKey")
@CachePut
캐시를 업데이트하기 위해서 method를 항상 실행하게 강제하는 Annotation 이다. 해당 Annotation이 있으면 method 호출을 항상한다. 그러므로 @Cacheable과 상충되어 같이 사용하면 안된다.
@CacheEvict
캐시에서 데이터를 제거하는 트리거로 동작하는 method에 붙이는 Annotation이다. 캐시된 데이터는 언제가는 지워져야한다. 그러지 않으면 결과 값이 변경이 일어났는데도 기존의 데이터(캐시된 데이터)를 불러와서 오류가 발생할 수 있다. 물론 캐시 설정에서 캐시 만료시간을 줄 수도 있다. @CacheEvict(value="cacheKey"), @CacheEvict(value="cacheKey", allEntries=true)allEntries는 전체 캐시를 지울지 여부를 선택하는 것이다.
@CacheConfig
클래스 레벨에서 공통의 캐시설정을 공유하는 기능이다.
@Scheduled
Linux의 crontab처럼 정해진 시간에 실행해야하는 경우에 사용한다. @Scheduled(cron = "0 0 07 * * ?")"초 분 시 일 월 요일 년(선택)에 해당 메서드
Lombok Annotation
@NoArgsConstructor
기본 생성자 자동으로 추가한다.
- @NoArgsConstructor(access = AccessLevel.PROTECTED)
기본생성자의 접근 권한을 protected로 제한한다.생성자로 protected Posts() {}와 같은 효과. Entity Class를 프로젝트 코드상에서 기본생성자로 생성하는 것은 금지하고, JPA에서 Entity 클래스를 생성하는것은 허용하기 위해 추가한다.
@AllArgsConstructor
모든 필드 값을 파라미터로 받는 생성자를 추가한다.
@RequiredArgsConstructor
final이나 @NonNull인 필드 값만 파라미터로 받는 생성자를 추가한다.final: 값이 할당되면 더 이상 변경할 수 없다. 잘 사용된다.
@Getter
Class 내 모든 필드의 Getter method를 자동 생성한다.
@Setter
Class 내 모든 필드의 Setter method를 자동 생성한다. Controller에서 @RequestBody로 외부에서 데이터를 받는 경우엔 기본생성자 + set method를 통해서만 값이 할당된다. 그래서 이때만 @Setter를 쓰는게 좋다. Entity Class에는 Setter를 설정하면 안된다. 차라리 DTO 클래스를 생성해서 DTO 타입으로 받도록 하자
@Data
@Getter @Setter @EqualsAndHashCode @AllArgsConstructor을 포함한 Lombok에서 제공하는 필드와 관련된 모든 코드를 생성한다. 사용할 때 의존성 주입이 꼬일 수 있어 주의해서 쓰는 게 좋다.
@ToString
Class 내 모든 필드의 toString method를 자동 생성한다.
- @ToString(exclude = "password")특정 필드를 toString() 결과에서 제외한다.클래스명(필드1이름=필드1값, 필드2이름=필드2값, …) 식으로 출력된다.
@EqualsAndHashCode
equals와 hashCode method를 오버라이딩 해주는 Annotation이다
- @EqualsAndHashCode(callSuper = true) callSuper 속성을 통해 equals와 hashCode 메소드 자동 생성 시 부모 클래스의 필드까지 감안할지 안 할지에 대해서 설정할 수 있다.
즉, callSuper = true로 설정하면 부모 클래스 필드 값들도 동일한지 체크하며, callSuper = false로 설정(기본값)하면 자신 클래스의 필드 값들만 고려한다.
@Builder
어느 필드에 어떤 값을 채워야 할지 명확하게 정하여 생성 시점에 값을 채워준다. 생성자도 비슷한 역할을 한다. 인스턴스 생성 시점에 값을 채워주는데, Builder를 사용하면 어떤 느낌일까?
생성자에서 인자가 많을때 고려해볼수있는 빌더패턴을 이해해야 한다.
https://esoongan.tistory.com/82
빌더패턴은 다음과 같이 만들어진다.
- A클래스 내부에 빌더클래스를 생성한다.
- 각 멤버변수별 메서드를 작성하는데, 각 메소드는 변수에 값을 set하고 빌더객체를 리턴한다.
- build()메서드는 필수 멤버변수의 null체크를 하고 지금까지 set된 builder를 바탕으로 A클래스의 생성자를 호출하고 인스턴스를 리턴한다.
public class PersonInfo {
private String name; //필수적으로 받야할 정보
private int age; //선택적으로 받아도 되는 정보
private int phonNumber; //선택적으로 받아도 되는 정보
public static class Builder { // 빌더 클래스
public Builder(String name) { // 필수변수는 생성자로 값을 넣는다.
this.name = name;
}
// 멤버변수별 메소드 - 빌더클래스의 필드값을 set하고 빌더객체를 리턴한다.
public Builder setAge(int age) {
this.age = age;
return this;
}
public Builder setPhonNumber(int phonNumber) {
this.phonNumber = phonNumber;
return this;
}
// 빌더메소드
public PersonInfo build() {
PersonInfo personInfo = new PersonInfo();
personInfo.name = name;
personInfo.age = age;
personInfo.phonNumber = phonNumber;
return personInfo;
}
}
}
빌더 패턴을 적용해서 클래스를 메서드 체이닝 식으로 만들 수 있게 된다.
PersonInfo personinfo = new PersonInfo
.Builder("SeungJin") // 필수값 입력 ( 빌더클래스 생성자로 빌더객체 생성)
.setAge(25) // 값 set
.setPhoneNumber(1234) // 값 set
.build() // build() 가 객체를 생성해 돌려준다.
다만 빌더를 구현하는데 소스가 상당히 길다. 이를 @Builder하나만 붙여주면 구현이 가능해진다.
@Builder
public class Person {
private final String name;
private final int age;
private final int phone;
}
@Builder를 사용하면 어느 필드에 어떤 값을 채워야 할지 명확하게 인지할 수 있다. (가독성도 좋아진다.)
JPA Annotation
JPA를 사용하면 DB 데이터에 작업할 경우 실제 쿼리를 사용하지 않고 Entity 클래스의 수정을 통해 작업한다.
@Entity
실제 DB의 테이블과 매칭될 Class임을 명시한다.즉, 테이블과 링크될 클래스임을 나타낸다. ※ Entity Class 엔티티를 해당하는 중요 클래스로, 클래스 이름을 언더 스코어 네이밍(_)으로 테이블 이름을 매칭한다.
- SalesManager.java -> sales_manager table
Controller에서 쓸 DTO 클래스란??
Request와 Response용 DTO는 View를 위한 클래스로, 자주 변경이 필요한 클래스이다. Entity 클래스와 DTO 클래스를 분리하는 이유는 View Layer와 DB Layer를 철저하게 역할 분리하기 위해서다. 테이블과 매핑되는 Entity 클래스가 변경되면 여러 클래스에 영향을 끼치게 되는 반면 View와 통신하는 DTO 클래스(Request/ Response 클래스)는 자주 변경되므로 분리해야 한다.
@Table
Entity Class에 매핑할 테이블 정보를 알려준다.@Table(name = "USER")Annotation을 생략하면 Class 이름을 테이블 이름 정보로 매핑한다.
@Id
해당 테이블의 PK 필드를 나타낸다.
@GeneratedValue
PK의 생성 규칙을 나타낸다.가능한 Entity의 PK는 Long 타입의 Auto_increment를 추천스프링 부트 2.0에선 옵션을 추가하셔야만 auto_increment가 된다. 기본값은 AUTO로, MySQL의 auto_increment와 같이 자동 증가하는 정수형 값이 된다.
@Column
테이블의 컬럼을 나타내며, 굳이 선언하지 않더라도 해당 Class의 필드는 모두 컬럼이 된다. @Column을 생략하면 필드명을 사용해서 컬럼명과 매핑 @Column(name = "username") @Column을 사용하는 이유는, 기본값 외에 추가로 변경이 필요한 옵션이 있을 경우 사용한다.
문자열의 경우 VARCHAR(255)가 기본값인데, 사이즈를 500으로 늘리고 싶거나(ex: title),타입을 TEXT로 변경하고 싶거나(ex: content) 등의 경우에 사용