Annotation?
사전적 의미로는 주석이라는 뜻
자바에서는 코드 사이에 주석처럼 쓰이며 소스코드에 추가하면 단순 주석의 기능을 하는 것이 아닌, 특별한 기능을 수행하도록 하는 기술이다.
프로그램 코드의 일부가 아닌 프로그램에 관한 데이터를 제공, 코드에 정보를 추가하는 정형화된 방법으로 프로그램에게 추가적인 정보를 제공해주는 메타데이터라고 볼 수 있다.
해당 클래스 위에 작성하여 표시한다.
💡 메타 데이터?
데이터에 대한 데이터 , ‘어떤 목적을 가지고 만들어진 데이터’ 라고도 정의함
역할
- 클래스와 메서드에 추가하여 다양한 기능을 부여하는 역할
- 해당 클래스의 역할을 정함
- Bean을 주입하기도 함
- 자동으로 getter, setter를 생성하기도 함
- 컴파일러에게 코드 작성 문법 에러를 체크하도록 정보를 제공함
- 소프트웨어 개발 툴이 빌드나 배치시 코드를 자동으로 생성할 수 있도록 정보를 제공함
- 실행시(런타임시) 특정 기능을 실행하도록 정보를 제공함
효과
- 코드량 감소
- 유지보수 용이
- 생산성 증가
@Bean
개발자가 직접 제어가 불가능한 외부 라이브러리등을 Bean으로 만들려고 할 때 사용되는 Annotation이다.
ArrayList와 같은 라이브러리를 Bean으로 등록하기 위해서는 별도로 해당 라이브러리 객체를 반환하는 메소드를 만들고 @Bean 어노테이션을 사용하면 된다.
아래왜 같이 name 옵션을 주면 원하는 id로 Bean이 생성이 가능하고 @Bean 만 사용한다면 메소드 이름을 camelCase로 변경한 것이 Bean id로 등록된다. 아래에서 name옵션이 없다면 ‘myTest’가 아이디가 된다.
@Configuration
public class ApplicationConfig {
@Bean(name="myarray")
public ArrayList<String> myTest(){
return new ArrayList<String>();
}
}
@Configuration
수동으로 스프링 컨테이너에 Bean을 등록할 때 사용하는 어노테이션이다. → Bean 설정을 담당하는 클래스에 사용
public class TestResource{
...
}
@Configuration
public class TestConfig{
@Bean
public TestResource testResource(){
return new TestResource();
}
}
이렇게 작성하면 @Configuration이 붙어있는 클래스를 자동으로 Bean으로 등록하고 해당 클래스를 파싱해서 @Bean이 있는 메소드를 찾아서 Bean을 생성해준다.
@Bean을 사용하는 클래스에는 반드시 @Configuration 어노테이션을 사용하여 해당 클래스에서 Bean을 등록하고자 함을 명시해줘야 한다.
물론 스프링 Bean으로 등록된 다른 클래스 안에서 @Bean으로 직접 Bean을 등록해주어도 동작은 한다.
하지만 @Configuration 안에서 @Bean을 사용해야 싱글톤을 보장받을 수 있으므로 @Bean은 반드시 @Configuration과 같이 사용해주어야 한다.
@Bean 어노테이션의 경우 수동으로 Bean을 직접 등록하는 경우에 사용하는데 다음과 같은 경우에 사용한다
- 개발자가 직접 제어가 불가능한 라이브러리를 활용할 때
- 애플리케이션 전범위적으로 사용되는 클래스를 등록할 때
- 다형성을 활용하여 여러 구현체를 등록해주어야 할 때
1번의 경우 Json 메세지를 만들고 싶어서 Gson과 같은 외부 라이브러리를 사용할 때 해당 클래스를 싱글톤 Bean으로 등록해주면 하나의 객체만 생성하여 여러 클래스가 공유함으로써 메모리에서 이득을 얻을 수 있다. 해당 클래스는 내가 만든게 아니라 가져다 사용하는 것이기 때문에 Bean으로 등록해 주어야 한다.
2,3번의 경우에 Bean으로 등록하면 좋은 이유는 한 눈에 파악하여 유지보수하기 좋기 떄문이다. 만약 여러 구현체들을 Bean으로 등록할 때 어떤 구현체들이 Bean으로 등록되는지 파악하려면 코드를 전부 찾아볼 필요 없이 @Configuration이 붙은 클래스만 찾아보면 되기 때문이다.
@ComponentScan
@ComponentScan은 @Component, @Service, @Repository, @Controller, @Configuation 중 하나라도 등록된 클래스를 찾으면 Context에 Bean으로 등록한다.
@ComponentScan이 있는 클래스의 하위 Bean으로 등록 될 클래스들을 스캔하여 Bean으로 등록해준다.
@Component
개발자가 직접 작성한 Class를 Bean으로 등록하기 위한 Annotation으로 <bean> 태그와 같은 역할을 한다. @Bean과 다른점은 name이 아닌 value를 이용하여 Bean의 이름을 지정한다
만약 Component에 value와 같은 추가 정보가 없다면 클래스 이름을 camelCase로 변경한 것이 Bean id로 사용된다. 아래의 코드에선 ‘myTest’가 된다.
@Component(value="myTest")
public class MyTest {
public MyTest() {
System.out.println("hi I'm Test");
}
}
Spring에서 @Component로 전부 사용하지 않고 @Service, @Repository, @Controller 등을 사용하는 이유는 가독성 향상과 추가적으로 사용할 수 있는 기능들이 있기 때문이다.
추가로 @Component와 @Service,@Repository는 성격이 같지만 @Controller는 다르다
@Component 대신에 @Service나 @Repository를 붙여도 사용은 가능하지만 @Controller의 경우에는 @Component로 대체가 되지 않는다. @Controller는 요청을 받아들이기 위한 용도로 사용된다.
@Service
해당 클래스가 Service 역할을 한다고 알려주기 위해 사용하는 Annotation
비즈니스 로직을 수행하는 것을 나타내는 용도이다.
@Repository
Data Access Layer의 DAO 또는 Repository 클래스에 사용한다.
스프링에서 지원하지 않는 Exception을 Spring Exception으로 전환하기 위해서 사용되며 여기서는 Exception이 발생한 경우 Unchecked Exception을 DataAccessException으로 전환시켜 준다 DataAccessException 자동변환과 같은 AOP의 적용 대상을 선정하기 위해 사용한다.
@Controller
해당 클래스가 Controller의 역할을 한다고 알려주기 위해 사용하는 Annotation
view의 return이 주된 목적으로 API, view를 동시에 사용해야 하는 경우 사용한다.
만약 API 서비스로 사용하는 경우는 @ResponseBody를 사용하여 객체를 반환해야 한다.
@RestController
Spring에서 View로 응답하지 않는 Controller로 메소드의 반환 결과를 JSON 형태로 반환한다. 해당 어노테이션이 있는 Controller의 메소드는 HttpResponse로 바로 응답이 가능하다
@RequestMapping 메소드가 기본적으로 @ResponseBody 의미를 가정하므로 따로 사용하지 않는다.
데이터만 return하는 것이 주된 목적이다.
@Autowired
필드(field), 수정자(setter), 생성자(constructor)에서 사용하며 필요한 의존 객체의 타입에 해당하는 Bean을 찾아서 주입해 준다.
무조건적인 객체에 대한 의존성을 주입시킨다.
이 어노테이션을 사용하면 스프링이 자동으로 값을 할당하며 Controller에서 DAO, Service에 관한 객체들을 주입시킬 때 많이 사용한다.
생성자 주입에 사용하는 경우 스프링 4.3 부터는 이 어노테이션을 생략할 수 있다. → 생성자의 매개변수로 객체를 전달받도록 되어있는 경우에는 해당 객체가 생성될 때에는 반드시 그 객체를 주입받아야 학기 때문이다
Autowired 사용 전
아래는 생성자로 TestRepository를 전달받는 TestService 클래스
@Service
public class TestService {
private TestRepository testRepository;
public TestService(TestRepository testRepository){
this.testRepository = testRepository;
}
}
원래의 경우에는 아래와 같이 xml파일을 작성하거나 클래스를 생성하여 Bean설정을 만들 수도 있다
<bean id="testRepository" class="com.test.spring.TestRepository"/>
<bean id="testService" class="com.test.spring.TestService">
<constructor-arg name="testRepository" ref="TestRepository"/>
</bean>
testRepository를 bean으로 생성하고 그걸 <constructor-arg> 를 사용하여 주입
@Configuration
public class ApplicationConfig {
@Bean
public TestRepository testRepository(){
return new TestRepository();
}
@Bean
public TestService testService(){
return new TestService(testRepository());
}
}
java로 작성하면 이런 방식으로 작성하면 된다.
Autowired 사용
@Service
public class TestService {
private TestRepository testRepository;
@Autowired
public TestService(TestRepository testRepository){
this.testRepository = testRepository;
}
}
@Repository
public class TestRepository{
...
}
위와 같이 간단하게 객체의 의존성을 가지는 부분에 Autowired를 사용하여 의존성 주입을 할 수 있다.
TestRepository는 당연히 Bean으로 등록이 되어야하기 때문에 @Repository 어노테이션이 있어야한다.
Setter에서 Autowired 사용
@Autowired 인터페이스를 보면 생성자, 메소드, 파라미터, 필드, 어노테이션 타입 등에 Autowired를 사용가능한 것을 볼 수 있다.
특히 required() 메소드의 default 값이 true로 설정되어 있는데 이는 ‘꼭 필요한 대상이니 주입을 반드시 받아야 한다’ 라는 의미이다.
public class TestRepository{
...
}
@Service
public class TestService {
private TestRepository testRepository;
@Autowired
public setTestService(TestRepository testRepository){
this.testRepository = testRepository;
}
}
setter로 주입되는 의존성의 경우에는 생성자처럼 필수적으로 주입되는 것이 아닌 필요에 의해 선택적으로 주입이 된다. 따라서 위의 코드처럼 @Repository를 지워도 컴파일시에 오류가 발생하면 안된다.
하지만 오류가 발생한다.
이유는 Autowired의 required() 메소드의 default 값이 true 이므로 반드시 주입을 받아야 한다고 인식하기 때문이다.
따라서 required 메소드의 값을 false로 바꾼다면 오류가 발생하지 않을 것이다.
@Service
public class TestService {
private TestRepository testRepository;
@Autowired(required = false)
public setTestService(TestRepository testRepository){
this.testRepository = testRepository;
}
}
이렇게 하면 Bean에 등록되지 않아도 testRepository를 Autowired 할 수 있다.
@Autowired 대상이 되는 객체가 여러개라면?
@Repository
public class FirstTestRepository implements TestRepository {
}
,
@Repository
public class SecondTestRepository implements TestRepository {
}
TestRepository 인터페이스를 생성하고 그걸 구현한 클래스 2개가 있다고 한다면, 이를 Bean으로 등록했을 시에 같은 타입의 Bean이 생성된다.
@Service
public class TestService {
private TestRepository testRepository;
@Autowired
public TestService(TestRepository testRepository){
this.testRepository = testRepository;
}
}
이 서비스 클래스를 그대로 사용한다면 하나 이상의 TestRepository Type이 존재한다는 오류 메세지가 나오며 컴파일에 실패한다. 해결법으로는 크게 2가지가 있다.
1. @Primary
주입되기 원하는 클래스에 이 어노테이션을 부여하면 Spring이 자동으로 해당 객체를 주입함
@Repository
@Primary
public class FirstTestRepository implements TestRepository {
}
,
@Repository
public class SecondTestRepository implements TestRepository {
}
이렇게하면 FirstTestRepository가 주입되는 것
2. Qualifier
@Service
public class TestService {
private TestRepository testRepository;
@Autowired
@Qualifier("firstTestRepository")
public TestService(TestRepository testRepository){
this.testRepository = testRepository;
}
}
Qualifier를 사용하면 어떤 Bean을 주입될지 명시하여 해결할 수 있다.
firstTestRepository인 이유는 Bean을 등록할 때 따로 id를 정하지 않았으므로 기본 id인 클래스 이름을 camelCase로 바꾼 것이 되었다.
3. List 형태로 모든 타입의 Bean을 주입받는 것도 가능하다
@Autowired
private List<TestRepository> testRepository;
이런 식으로 사용가능하다.
'Java > Spring(Boot)' 카테고리의 다른 글
Transactional 몰랐던 부분(신기해서 적어봄) + Transaction과 아닌 부분 나누기 (0) | 2023.03.21 |
---|---|
JPA와 ORM (0) | 2023.02.18 |
이해 안돼서 다시 정리하는 IoC/DI (0) | 2023.02.07 |
Spring Framework 개념 정리 (0) | 2023.02.02 |