@Import
함께 사용할 설정 클래스를 지정
AppConf1.java
package com.test.test1.config;
import com.test.test1.MemberDAO;
import com.test.test1.MemberPrinter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
// 의존 관계가 없는것들
@Configuration
@Import(AppConf2.class)
public class AppConf1 {
@Bean
public MemberDAO memberDAO() {
return new MemberDAO();
}
@Bean
public MemberPrinter memberPrinter() {
return new MemberPrinter();
}
}
MainForSpring.java
// ctx = new AnnotationConfigApplicationContext(AppCtx.class);
//ctx = new AnnotationConfigApplicationContext(AppConf1.class, AppConf2.class);
ctx = new AnnotationConfigApplicationContext(AppConf1.class);
AppConf1.java
@Configuration
@Import({ AppConf2.class, AppConf3.class })
public class AppConf1 {
... (생략) ...
=> 중괄호로 나열 !
getBean("bean_name", bean_type) 메서드
private static void processVersionCommand(String[] args) {
// VersionPrinter versionPrinter = ctx.getBean("versionPrinter", VersionPrinter.class);
VersionPrinter versionPrinter = ctx.getBean("notexistent", VersionPrinter.class);
versionPrinter.print();
}
"notexistent" 존재하지 않는 빈 이름을 사용했을 때 !
🚨 error. NoSuchBeanDefinitionException: No bean named 'notexistent' available
private static void processVersionCommand(String[] args) {
// VersionPrinter versionPrinter = ctx.getBean("versionPrinter", VersionPrinter.class);
// VersionPrinter versionPrinter = ctx.getBean("notexistent", VersionPrinter.class);
VersionPrinter versionPrinter = ctx.getBean("versionPrinter", MemberInfoPrinter.class);
versionPrinter.print();
}
🚨 Type mismatch: cannot convert from MemberInfoPrinter to VersionPrinter
해당 타입의 빈 객체가 한 개만 존재하는 경우, 빈 타입만으로 빈을 구할 수 있다.
private static void processVersionCommand(String[] args) {
// VersionPrinter versionPrinter = ctx.getBean("versionPrinter", VersionPrinter.class);
// VersionPrinter versionPrinter = ctx.getBean("notexistent", VersionPrinter.class);
// VersionPrinter versionPrinter = ctx.getBean("versionPrinter", MemberInfoPrinter.class);
VersionPrinter versionPrinter = ctx.getBean(VersionPrinter.class);
versionPrinter.print();
}
ctx.getBean(VersionPrinter.class);
이렇게로만 가져올 수 있다 !
private static void processVersionCommand(String[] args) {
// VersionPrinter versionPrinter = ctx.getBean("versionPrinter", VersionPrinter.class);
// VersionPrinter versionPrinter = ctx.getBean("notexistent", VersionPrinter.class);
// VersionPrinter versionPrinter = ctx.getBean("versionPrinter", MemberInfoPrinter.class);
VersionPrinter versionPrinter = ctx.getBean(NotExistent.class);
versionPrinter.print();
}
ctx.getBean(NotExistent.class); 로 없는 경우
🚨 NotExistent cannot be resolved to a type
AppConf2.java
@Bean
public VersionPrinter versionPrinter() {
VersionPrinter versionPrinter = new VersionPrinter();
versionPrinter.setMajorVersion(5);
versionPrinter.setMinorVersion(3);
return versionPrinter;
}
@Bean
public VersionPrinter newVersionPrinter() {
VersionPrinter versionPrinter = new VersionPrinter();
versionPrinter.setMajorVersion(6);
versionPrinter.setMinorVersion(1);
return versionPrinter;
}
같은 타입 2개 생성
MainForSpring.java
private static void processVersionCommand(String[] args) {
VersionPrinter versionPrinter = ctx.getBean(VersionPrinter.class);
versionPrinter.print();
}
VersionPrinter 라는 같은 타입 !!
🚨 NoUniqueBeanDefinitionException: No qualifying bean of type 'ex01.VersionPrinter' available: expected single matching bean but found 2: versionPrinter,newVersionPrinter
VersionPrinter 타입을 한정지을 수 없다. 내가 어떤 걸 리턴해줘야하는지 모르겠다.
@Autowired
사용 객체에서 의존 객체 필드에 @Autowired 어노테이션을 붙이면 스프링이 자동으로 해당 타입의 빈 객체를 찾아서 필드에 할당해준다.
ChangePasswordService.java
public class ChangePasswordService {
@Autowired
private MemberDAO memberDAO; // 자동으로 인스턴스에 넣어주므로
/* 별도로 의존 주입을 위한 코드가 필요없어진다.
public ChangePasswordService(MemberDAO memberDAO) {
this.memberDAO = memberDAO;
}
*/
...(생략)...
@Autowired 어노테이션을 setter 메서드에 붙이면, 스프링이 해당 setter 메서드를 통해 의존성을 주입 (= setter 기반 의존성 주입)
MemberInfoPrinter.java
package com.test.test1;
import org.springframework.beans.factory.annotation.Autowired;
public class MemberInfoPrinter {
// @Autowired
private MemberDAO memberDAO;
// @Autowired
private MemberPrinter printer;
@Autowired
public void setMemberDAO(MemberDAO memberDAO) {
this.memberDAO = memberDAO;
}
@Autowired
public void setMemberPrinter(MemberPrinter printer) {
this.printer = printer;
}
public void printMemberInfo(String email) {
Member member = memberDAO.selectByEmail(email);
if (member == null) {
System.out.println("일치하는 데이터가 없습니다.");
return;
}
printer.print(member);
System.out.println();
}
}
전제조건 : 해당하는 객체들이 Bean으로 등록되어 있어야 한다.
AppCtx 설정 클래스를 사용하도록 변경 ⬇️
AppCtx.java
@Bean
public ChangePasswordService changePwdSvc() {
//return new ChangePasswordService(memberDAO);
return new ChangePasswordService();
}
MainForSpring.java
ctx = new AnnotationConfigApplicationContext(AppCtx.class);
MemberListPrinter.java
package com.test.test1;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collection;
public class MemberListPrinter {
@Autowired
private MemberDAO memberDAO;
@Autowired
private MemberPrinter printer;
// public MemberListPrinter(MemberDAO memberDAO, MemberPrinter printer) {
// this.memberDAO = memberDAO;
// this.printer = printer;
// }
public void printAll() {
Collection<Member> members = memberDAO.selectAll();
members.forEach(member -> printer.print(member));
}
}
AppCtx.java
@Bean
public MemberListPrinter memberListPrinter() {
// return new MemberListPrinter(memberDAO(), memberPrinter());
return new MemberListPrinter(); <= 의존객체를 매개변수로 가지는 생성자가 삭제되었기 때문에 기본 생성자로 변경
}
// @Bean ⇐ MemberListPrinter에서 @Autowired 대상객체를 빈으로 등록하지 않음
public MemberDAO memberDAO() {
return new MemberDAO();
}
🚨 No qualifying bean of type 'ex01.MemberDAO' available: expected at least 1 bean which qualifies as autowire candidate.
MemberDAO가 빈으로 등록되어 있는지 먼저 확인해야 한다.
AppCtx.java
@Bean
public MemberPrinter memberPrinter() {
return new MemberPrinter();
}
@Bean
public MemberPrinter memberPrinter2() {
return new MemberPrinter();
}
2개의 빈이 MemberPrinter로 타입이 같다
MemberListPrinter.java
public class MemberListPrinter {
@Autowired
private MemberPrinter printer;
...(생략)...
어떤 것을 넣어야할지 모호해져서 error 발생
`🚨 .NoUniqueBeanDefinitionException: No qualifying bean of type 'ex01.MemberPrinter' available: expected single matching bean but found 2: memberPrinter2,memberPrinter
@Qualifier
자동 주입 가능한 빈이 두 개 이상이면 자동 주입할 빈을 지정할 방법이 필요하다.
@Qualifier를 이용해서 자동 주입 대상 빈을 한정지을 수 있다.
AppCtx.java
@Bean
@Qualifier("printer")
public MemberPrinter memberPrinter() {
return new MemberPrinter();
}
MemberListPrinter.java
@Autowired
@Qualifier("printer")
private MemberPrinter printer;
MemberInfoPrinter.java
@Autowired
@Qualifier("printer")
public void setMemberPrinter(MemberPrinter printer) {
this.printer = printer;
}
css class 선택자와 비슷한 느낌..!!
- 빈 설정에 @Qualifier 를 사용하지 않으면 빈 이름이 기본 한정자로 지정된다!
@Bean
public MemberPrinter memberPrinter2() { <= memberPrinter2 = 빈 이름 = 기본 한정자
return new MemberPrinter();
}
- @Autowired(빈을 사용하는 쪽)도 @Qualifier가 없으면 필드나 파라미터 이름을 한정자로 사용한다.
@Autowired
private MemberPrinter printer; <= printer = 필드 이름 = 기본 한정자
상속받으면서 발생하는 오류 해결 방법들
MemberSummaryPrinter.java
MemberPrinter를 상속받음 !
package com.test.test1;
public class MemberSummaryPrinter extends MemberPrinter {
@Override
public void print(Member member) {
System.out.printf("회원정보: 이메일=%s, 이름=%s\n", member.getEmail(), member.getName());
}
}
@Bean
@Qualifier("printer")
public MemberPrinter memberPrinter() {
return new MemberPrinter();
}
@Bean
public MemberSummaryPrinter memberPrinter2() {
return new MemberSummaryPrinter();
}
@Qualifier 제거
package com.test.test1;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collection;
public class MemberListPrinter {
@Autowired
private MemberDAO memberDAO;
@Autowired
//@Qualifier("printer")
private MemberPrinter printer;
// public MemberListPrinter(MemberDAO memberDAO, MemberPrinter printer) {
// this.memberDAO = memberDAO;
// this.printer = printer;
// }
public void printAll() {
Collection<Member> members = memberDAO.selectAll();
members.forEach(member -> printer.print(member));
}
}
실행하면 오류가 난다.
🚨 NoUniqueBeanDefinitionException: No qualifying bean of type 'ex01.MemberPrinter' available: expected single matching bean but found 2: memberPrinter2,memberPrinter
왤까? 상속 받았기 때문에 상속관계에 있는 애들이 다 나온다..ㅠㅠ
AppCtx.java
@Bean
@Qualifier("summaryPrinter")
public MemberSummaryPrinter memberPrinter2() {
return new MemberSummaryPrinter();
}
MemberListPrinter.java
@Autowired
@Qualifier("summaryPrinter")
private MemberPrinter printer;
AppCtx.java
@Qualifier 제거
@Bean
@Qualifier("printer")
public MemberPrinter memberPrinter() {
return new MemberPrinter();
}
@Bean
// @Qualifier("summaryPrinter")
public MemberSummaryPrinter memberPrinter2() {
return new MemberSummaryPrinter();
}
MemberListPrinter.java
1. @Qualifier 제거
2. private MemberSummaryPrinter printer;
사용 클래스에서 자식 클래스 타입으로 명확하게 주입 !
public class MemberListPrinter {
@Autowired
private MemberDAO memberDAO;
@Autowired
// @Qualifier("summaryPrinter")
private MemberSummaryPrinter printer;
자동 주입할 대상이 필수가 아닌 경우 → 선택적으로 주입
=> 반드시 인스턴스를 갖지 않아도 된다고 명시할 수 있다.
MemberPrinter.java
public class MemberPrinter {
@Autowired(required = false)
private DateTimeFormatter dateTimeFormatter;
public void print(Member member) {
if (dateTimeFormatter == null) {
System.out.printf("회원정보: ID=%s, 이메일=%s, 이름=%s, 등록일=%tF\n",
member.getId(), member.getEmail(), member.getName(),
member.getRegisterDateTime());
} else {
System.out.printf("회원정보: ID=%s, 이메일=%s, 이름=%s, 등록일=%tF\n",
member.getId(), member.getEmail(), member.getName(),
dateTimeFormatter.format(member.getRegisterDateTime()));
}
}
}
MemberPrinter.java
public class MemberPrinter {
// @Autowired(required = false)
private DateTimeFormatter dateTimeFormatter;
// @Autowired(required = false)
@Autowired
public void setDateTimeFormatter(Optional<DateTimeFormatter> dateTimeFormatterOptional) {
if (dateTimeFormatterOptional.isPresent()) { // 값이 오는 경우
this.dateTimeFormatter = dateTimeFormatter;
} else { // 값이 오지 않는 경우
this.dateTimeFormatter = null;
}
}
public class MemberPrinter {
// @Autowired(required = false)
@Autowired
private Optional<DateTimeFormatter> dateTimeFormatterOptional;
// @Autowired(required = false)
// @Autowired
// public void setDateTimeFormatter(Optional<DateTimeFormatter> dateTimeFormatterOptional) {
// if (dateTimeFormatterOptional.isPresent()) {
// this.dateTimeFormatter = dateTimeFormatterOptional.get();
// } else {
// this.dateTimeFormatter = null;
// }
// }
public void print(Member member) {
DateTimeFormatter dateTimeFormatter = dateTimeFormatterOptional.orElse(null);
if (dateTimeFormatter == null) {
System.out.printf("회원정보: ID=%s, 이메일=%s, 이름=%s, 등록일=%tF\n",
member.getId(), member.getEmail(), member.getName(),
member.getRegisterDateTime());
} else {
System.out.printf("회원정보: ID=%s, 이메일=%s, 이름=%s, 등록일=%tF\n",
member.getId(), member.getEmail(), member.getName(),
dateTimeFormatter.format(member.getRegisterDateTime()));
}
}
}
@Nullable
반환값이 null이 될 수 있다.
public class MemberPrinter {
@Autowired
@Nullable
private DateTimeFormatter dateTimeFormatter;
public void print(Member member) {
if (dateTimeFormatter == null) {
System.out.printf("회원정보: ID=%s, 이메일=%s, 이름=%s, 등록일=%tF\n",
member.getId(), member.getEmail(), member.getName(),
member.getRegisterDateTime());
} else {
System.out.printf("회원정보: ID=%s, 이메일=%s, 이름=%s, 등록일=%tF\n",
member.getId(), member.getEmail(), member.getName(),
dateTimeFormatter.format(member.getRegisterDateTime()));
}
}
}
or
setter 메서드에 붙일 수도 있다.
public class MemberPrinter {
private DateTimeFormatter dateTimeFormatter;
@Autowired
@Nullable
public void setDateTimeFormatter(DateTimeFormatter dateTimeFormatter) {
this.dateTimeFormatter = dateTimeFormatter;
}
public void print(Member member) {
if (dateTimeFormatter == null) {
System.out.printf("회원정보: ID=%s, 이메일=%s, 이름=%s, 등록일=%tF\n",
member.getId(), member.getEmail(), member.getName(),
member.getRegisterDateTime());
} else {
System.out.printf("회원정보: ID=%s, 이메일=%s, 이름=%s, 등록일=%tF\n",
member.getId(), member.getEmail(), member.getName(),
dateTimeFormatter.format(member.getRegisterDateTime()));
}
}
}
자동주입할 빈이 없다면 ❓
- setter 메서드에
required = false
로 되어있는 것은 setter 메서드를 호출하지 않는 반면- setter 메서드에
@Nullable
로 되어 있으면 setter 메서드를 반드시 호출한다.
AppCtx.java
@Bean
public MemberPrinter memberPrinter() {
return new MemberPrinter();
}
@Bean
public MemberSummaryPrinter memberPrinter2() {
return new MemberSummaryPrinter();
}
@Bean
public MemberInfoPrinter memberInfoPrinter() {
MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
infoPrinter.setMemberPrinter(memberPrinter2());
return infoPrinter;
}
MemberInfoPrinte.java
public class MemberInfoPrinter {
private MemberDAO memberDAO;
private MemberPrinter printer;
@Autowired
public void setMemberDAO(MemberDAO memberDAO) {
this.memberDAO = memberDAO;
}
@Autowired ⇐ 사용하지 않으면 MemberSummaryPrinter 타입을 주입
⇐ 사용하면 MemberPrinter 타입을 주입 → 예외 발생
public void setMemberPrinter(MemberPrinter printer) {
this.printer = printer;
}
public void printMemberInfo(String email) {
Member member = memberDAO.selectByEmail(email);
if (member == null) {
System.out.println("일치하는 데이터가 없습니다.");
return;
}
printer.print(member);
System.out.println();
}
}
스프링이 클래스를 검색해서 빈으로 등록해 주는 기능
=> 설정 클래스에 빈을 등록하지 않아도 원하는 클래스를 빈으로 등록, 사용하는 것이 가능
=> 설정과 관련된 코드가 감소
이제 빈으로 직접 등록하지 않아도 된다!!! 😆
@Component
해당 클래스를 스캔 대상으로 표시
=> @Component 어노테이션에 값을 설정하지 않으면 클래스 이름의 첫 글자를 소문자로 바꾼 이름을 빈 이름으로 사용
MemberDAO.java
package com.test.test1;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@Component // => public MemberDAO memberDAO() { return new MemberDAO(); }
public class MemberDAO {
private static long nextId = 0;
private Map<String, Member> map = new HashMap<>();
public Member selectByEmail(String email) {
return map.get(email);
}
public void insert(Member member) {
member.setId(++nextId);
map.put(member.getEmail(), member);
}
public void update(Member member) {
map.put(member.getEmail(), member);
}
public Collection<Member> selectAll() {
return map.values();
}
}
@Component // public MemberDAO memberDAO() { return new MemberDAO();
~~~~~~~~~ ~~~~~~~~~
빈 타입 빈 이름
ChangePasswordService.java
@Component
public class ChangePasswordService {
MemberRegisterService.java
@Component
public class MemberRegisterService {
@Component 에 값을 설정하면 해당 값을 빈 이름으로 사용
MemberInfoPrinter.java
@Component("infoPrinter") // => public MemberInfoPrinter infoPrinter() { return new MemberInfoPrinter(); }
public class MemberInfoPrinter {
MemberListPrinter.java
@Component("listPrinter")
public class MemberListPrinter {
@ComponentScan
@ComponentScan 어노테이션으로 스캔 설정
=> 설정 클래스에 @ComponentScan 어노테이션을 적용하면 @Component 어노테이션을 붙인 클래스를 스캔해서 스프링 빈으로 자동 등록 !!
AppCtx.java
=> @Component 어노테이션을 사용한 클래스를 빈으로 등록하는 코드를 삭제
package com.test.test1.config;
import com.test.test1.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = { "ex01" })
public class AppCtx {
@Bean
@Qualifier("printer")
public MemberPrinter memberPrinter() {
return new MemberPrinter();
}
@Bean
@Qualifier("summaryPrinter")
public MemberSummaryPrinter memberPrinter2() {
return new MemberSummaryPrinter();
}
@Bean
public VersionPrinter versionPrinter() {
VersionPrinter versionPrinter = new VersionPrinter();
versionPrinter.setMajorVersion(5);
versionPrinter.setMinorVersion(3);
return versionPrinter;
}
}
빈 등록해주던 메서드는 다 빠졌다 !
MainForSpring.java
빈의 이름이 바뀌었기 때문에 변경된 빈 이름으로 사용
private static void processInfoCommand(String[] args) {
if (args.length != 2) {
printHelp();
return;
}
MemberInfoPrinter memberInfoPrinter = ctx.getBean("infoPrinter", MemberInfoPrinter.class);
memberInfoPrinter.printMemberInfo(args[1]);
}
private static void processListCommand(String[] args) {
MemberListPrinter memberListPrinter = ctx.getBean("listPrinter", MemberListPrinter.class);
memberListPrinter.printAll();
}
private static void processNewCommand(String[] args) {
if (args.length != 5) {
printHelp();
return;
}
// MemberRegisterService regSvc = assembler.getRegSvc();
MemberRegisterService regSvc = ctx.getBean(MemberRegisterService.class);
RegisterRequest reg = new RegisterRequest();
reg.setEmail(args[1]);
reg.setName(args[2]);
reg.setPassword(args[3]);
reg.setConfirmPassword(args[4]);
if (!reg.isPasswordEqualToConfirmPassword()) {
System.out.println("패스워드와 패스워드 확인이 일치하지 않습니다.");
return;
}
try {
regSvc.regist(reg);
System.out.println("등록되었습니다.");
} catch(DuplicateMemberException e) {
System.out.println("이미 존재하는 이메일입니다.");
}
}
private static void processChangeCommand(String[] args) {
if (args.length != 4) {
printHelp();
return;
}
// ChangePasswordService pwdSvc = assembler.getPwdSvc();
ChangePasswordService pwdSvc = ctx.getBean(ChangePasswordService.class);
try {
pwdSvc.changePassword(args[1], args[2], args[3]);
System.out.println("패스워드를 변경하였습니다.");
} catch(RuntimeException e) {
System.out.println(e.getMessage());
}
}
@Component
일반적인 Bean
@Controller
스프링 MVC에서 사용자의 요청을 받고 응답을 반환하는 역할
@Service
비즈니스 로직 처리
@Repository
데이터 접근 계층. JPA를 이용해서 구현할 때 DB access 로직을 담는 곳
@Aspect
AOP(Aspect Oriented Programming). 공통 관심사를 로직에서 분리 (로그인, 트랜잭션 관리 등) => 중복 발생을 최소화
@Configuration
설정 파일 정의
ex02 패키지를 만들고 MemberRegisterService 만들기 ⬇️
package com.test.test1.ex02;
import org.springframework.stereotype.Component;
@Component
public class MemberRegisterService { // memberRegisterService 이름의 빈이 등록
}
AppCtx.java
@Configuration
@ComponentScan(basePackages = { "ex01", "ex02" })
public class AppCtx {
두 개의 빈이 충돌난다. MemberRegisterService가 ex01, ex02 에 다 있어서 자동 스캔하다보니 다른 패키지의 같은 이름의 클래스를 발견해서 오류를 낸다.
=> 오류 해결 방법 => 명시적으로 빈 이름을 설정
@Component("ex02MemberRegisterService")
public class MemberRegisterService { // memberRegisterService 이름의 빈이 등록
}
컴포넌트 이름을 이렇게 부여하면 해결된다 !!
MemberDAO.java
=> 컴포넌트 스캔을 통해 자동으로 빈 등록
@Component
public class MemberDAO {
AppCtx.java
=> 수동으로 빈을 등록하는 코드를 추가
@Configuration
@ComponentScan(basePackages = { "ex01", "ex02" })
public class AppCtx {
@Bean
public MemberDAO memberDAO() {
return new MemberDAO();
}
아무 문제 없다 -! 자동 & 수동 모두 등록되면 어떤 걸 먼저 볼까? => 수동
=> 객체 생성 -> 의존 설정 -> 초기화 -> 소멸
~~~~~~~~~~~~
org.springframework.beans.factory.DisposableBean 인터페이스의 destroy() 메서드를 이용
package com.test.test1.ex03;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class Client implements InitializingBean, DisposableBean {
private String host;
public void setHost(String host) {
this.host = host;
}
public void send() {
System.out.println("Client.send() is called ... " + this.host);
}
@Override
public void afterPropertiesSet() throws Exception {
// 초기 설정 작업들
System.out.println("Client.afterPropertiesSet() is called ... ");
}
@Override
public void destroy() throws Exception {
System.out.println("Client.destroy() is called ... ");
}
}
각각의 추상메서드를 override해서 구현해주었다.
@Configuration
@ComponentScan(basePackages = { "ex01", "ex02" })
public class AppCtx {
@Bean
public Client client() {
Client client = new Client();
client.setHost("www.test.com");
return client;
}
package com.test.test1.main;
import com.test.test1.config.AppCtx;
import com.test.test1.ex03.Client;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
public class MainForClient {
public static void main(String[] args) {
AbstractApplicationContext ctx = new AnnotationConfigApplicationContext(AppCtx.class);
Client c = ctx.getBean(Client.class);
c.send();
System.out.println("before ctx.close()");
ctx.close();
System.out.println("after ctx.close()");
}
}
다음과 같이 실행된다.
Client.afterPropertiesSet() is called ...
Client.send() is called ... www.test.com
before ctx.close()
Client.destroy() is called ...
after ctx.close()
package com.test.test1.ex03;
public class Client2 {
private String host;
public void setHost(String host) {
this.host = host;
}
public void send() {
System.out.println("Client.send() is called ... " + this.host);
}
public void connect() {
System.out.println("Client.connect() is called ... ");
}
public void close() {
System.out.println("Client.close() is called ... ");
}
}
@Configuration
@ComponentScan(basePackages = { "ex01", "ex02" })
public class AppCtx {
@Bean(initMethod = "connect", destroyMethod = "close")
public Client2 client2() {
Client2 client = new Client2();
client.setHost("www.test.com");
return client;
}
// @Bean
public Client client() {
Client client = new Client();
client.setHost("www.test.com");
return client;
}
public class MainForClient {
public static void main(String[] args) {
AbstractApplicationContext ctx = new AnnotationConfigApplicationContext(AppCtx.class);
Client2 c = ctx.getBean(Client2.class);
c.send();
System.out.println("before ctx.close()");
ctx.close();
System.out.println("after ctx.close()");
}
}
Client.connect() is called ...
Client.send() is called ... www.test.com
before ctx.close()
Client.close() is called ...
after ctx.close()
싱글톤 범위
별도로 스코프를 지정하지 않으면 기본적으로 싱글톤 스코프가 적용
- @Scope("singleton")
- @Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
AppCtx.java
@Configuration
@ComponentScan(basePackages = { "ex01", "ex02" })
public class AppCtx {
@Bean(initMethod = "connect", destroyMethod = "close")
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON) <=singleton
public Client2 client2() {
Client2 client = new Client2();
client.setHost("www.test.com");
return client;
}
MainForClient.java
public class MainForClient {
public static void main(String[] args) {
AbstractApplicationContext ctx = new AnnotationConfigApplicationContext(AppCtx.class);
Client2 c1 = ctx.getBean(Client2.class);
Client2 c2 = ctx.getBean(Client2.class);
System.out.println("c1 == c2 : " + (c1 == c2)); // true
ctx.close();
}
}
Client.connect() is called ...
c1 == c2 : true
Client.close() is called ...
true
로 뜬다. => 싱글톤 스코프
이므로 인스턴스가 하나만 생성된다!
close도 자동으로 된다. 😇
프로토타입 범위
새로운 인스턴스가 요청될 때마다 생성되는 범위
- @Scope("prototype")
- @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
AppCtx.java
@Configuration
@ComponentScan(basePackages = { "ex01", "ex02" })
public class AppCtx {
@Bean(initMethod = "connect", destroyMethod = "close")
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) <= prototype
public Client2 client2() {
Client2 client = new Client2();
client.setHost("www.test.com");
return client;
}
MainForClient.java
public class MainForClient {
public static void main(String[] args) {
AbstractApplicationContext ctx = new AnnotationConfigApplicationContext(AppCtx.class);
Client2 c1 = ctx.getBean(Client2.class);
Client2 c2 = ctx.getBean(Client2.class);
System.out.println("c1 == c2 : " + (c1 == c2)); // false
ctx.close();
}
}
Client.connect() is called ...
Client.connect() is called ...
c1 == c2 : false
false
로 뜬다. => 프로토타입 스코프
이므로 인스턴스가 따로따로 생성되므로 커넥션이 2번 일어난다.
또한 close() 메서드를 호출하지 않는다.
=> 프로토타입 범위를 갖는 빈은 완전한 lifecycle을 따르지 않는다.
=> 빈 객체의 소멸 처리를 코드에서 직접해야 한다 🥵
- 애플리케이션의 핵심 비즈니스 로직과 공통적인 기능(횡단 관심사, cross-cutting concerns)을 분리해서 모듈화하는 방법
- 로깅, 보안(인증, 인가), 트랜잭션 관리 등의 횡단 관심사를 비즈니스 로직과 분리해서 코드의 가독성, 유지보수성과 재활성을 높이는데 유용
똑같은 코드가 여러 메서드에 분산되어 중복으로 사용되고 있다,, 🥵
관심사를 분리하자 !!!
스프링 AOP는 프록시 패턴을 사용해서 런타임에 위빙을 수행
어드바이스 | 어노테이션 | 사용 시 |
---|---|---|
Before advice | @Before | 대상 메서드가 실행되기 전에 적용할 어드바이스를 정의 |
After returning advice | @AfterReturning | 대상 메서드가 성공적으로 실행되고 결과값을 반환한 후 적용할 어드바이스를 정의 |
After throwing advice | @AfterThrowing | 대상 메서드에서 예외가 발생했을 때 적용할 어드바이스를 정의 |
After advice | @After | 대상 메서드의 정상 수행 여부와 상관없이 무조건 실행되는 어드바이스를 정의 |
Around advice | @Around | 대상 메서드의 호출 전후, 예외 발생 등 모든 시점에 적용할 수 있는 어드바이스를 정의 |
스프링 프레임워크에서 AOP 기능을 사용하기 위해서는 spring-aop 모듈이 필요
=> spring-context 모듈을 추가하면 자동으로 추가
package com.test.test1.ex04;
public interface Calculator {
public long factorial(long num);
}
ImpCalculator
package com.test.test1.ex04;
public class ImpCalculator implements Calculator{
@Override
public long factorial(long num) {
long result = 1;
for (long i = 1; i <= num; i++) {
result *= i;
}
return result;
}
}
RecCalculator
package com.test.test1.ex04;
public class RecCalculator implements Calculator{
@Override
public long factorial(long num) {
if (num == 1)
return 1;
// 재귀 호출
return num * factorial(num - 1);
}
}
ImpCalculator
package com.test.test1.ex04;
public class ImpCalculator implements Calculator{
@Override
public long factorial(long num) {
long start = System.nanoTime();
long result = 1;
for (long i = 1; i <= num; i++) {
result *= i;
}
long end = System.nanoTime();
System.out.printf("ImpCalculator.factorial(%d) 실행시간 = %d\n", num, (end-start));
return result;
}
public static void main(String[] args) {
ImpCalculator c = new ImpCalculator();
c.factorial(100000000);
}
}
RecCalculator 클래스에 factorial 메서드 적용이 불가 => 재귀 호출 방식이므로 시작과 끝 시간을 측정하는 것이 불가
실행 클래스에서 메서드 실행 전후에 값을 계산하는 방식으로 변경
MainForCalculator
package com.test.test1.ex04;
public class MainForCalculator {
public static void main(String[] args) {
}
}
ImplCalculator
package com.test.test1.ex04;
public class ImpCalculator implements Calculator {
@Override
public long factorial(long num) {
long result = 1;
for (long i = 1; i <= num; i ++) {
result *= i;
}
return result;
}
}
MainForCalculator.java
package com.test.test1.ex04;
public class MainForCalculator {
public static void main(String[] args) {
final long num = 10L;
ImpCalculator imp = new ImpCalculator();
long start1 = System.nanoTime();
imp.factorial(num);
long end1 = System.nanoTime();
System.out.printf("ImpCalculator.factorial(%d) 실행시 = $d\n", num, (end1-start1));
RecCalculator rec = new RecCalculator();
long start2 = System.nanoTime();
rec.factorial(num);
long end2 = System.nanoTime();
System.out.printf("RecCalculator.factorial(%d) 실행시 = $d\n", num, (end2-start2));
}
}
=> 시간 계산 방식과 출력 방식에 코드 중복이 발생
=> 시간 계산 방식과 출력 방식에 변경이 필요하면 모든 중복 부분을 수정해야 한다. 🥵
=> 일관되게 변경이나 정책을 반영하는 것이 어렵다 ㅠ.ㅠ
프록시 객체 ⇒ 핵심 기능을 구현하지 않는 대신 여러 객체에 공통적으로 적용할 수 있는 기능을 구현
공통의 인터페이스를 상속받아야 하고
필드를 만들고 생성자도 있어야 한다.
ExeTimeCalculator.java <- 프록시 객체 🩷
package com.test.test1.ex04;
public class ExeTimeCalculator implements Calculator {
private Calculator delegate; // 인터페이스를 필드로 갖고 온다. rec, imp 가 들어올 것
public ExeTimeCalculator(Calculator delegate) {
this.delegate = delegate; // 생성자에서 원하는 인스턴스를 넣어 캐스팅이 가능
}
@Override
public long factorial(long num) {
long result = delegate.factorial(num);
return result;
}
}
MainForCalculator.java
package com.test.test1.ex04;
public class MainForCalculator {
public static void main(String[] args) {
final long num = 10L;
Calculator imp = new ExeTimeCalculator(new ImpCalculator());
System.out.println(imp.factorial(num));
Calculator rec = new ExeTimeCalculator(new RecCalculator());
System.out.println(rec.factorial(num));
}
}
ExeTimeCalculator.java
public class ExeTimeCalculator implements Calculator {
private Calculator delegate;
public ExeTimeCalculator(Calculator delegate) {
this.delegate = delegate;
}
@Override
public long factorial(long num) {
long start = System.nanoTime();
long result = delegate.factorial(num);
long end = System.nanoTime();
System.out.printf("%s.factorial(%d) 실행시간 = %d\n",
this.delegate.getClass().getSimpleName(), num, (end - start));
return result;
}
}
디자인 패턴 입문 책 추천 ⬇️
디자인 패턴 책 추천 ⬇️
보안 책 추천 ⬇️
- Aspect를 사용할 클래스에 @Aspect 를 추가
- @Pointcut 으로 공통 기능을 적용할 Pointcut을 정의
- 공통 기능을 구현한 메서드에 @Around 를 적용
ExeTimeAspect.java
package com.test.test1.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import java.util.Arrays;
@Aspect
public class ExeTimeAspect {
// 공통 기능을 적용할 대상을 설정
// ex04 패키지와 그 하위 패키지에 위치한 public 메서드를 Pointcut으로 설정
@Pointcut("execution(public * ex04..*(..))")
private void publicTarget() {
}
// Around Advice
// publicTarget() 메서드에 정의한 Pointcut에 공통 기능을 적용
// measure => 사용자 정의 메서드 => 공통 기능을 구현
// ProceedingJoinPoint => 프록시 대상 객체의 메서드를 호출할 때 사용
@Around("publicTarget(")
public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.nanoTime();
try{
Object result = joinPoint.proceed(); // proceed() 는 원래 실행되어야 할 메서드가 실행되는 것 !
return result;
} finally {
long end = System.nanoTime();
System.out.printf("%s.%s(%s) 실행결과 = %d \n",
joinPoint.getTarget().getClass().getSimpleName(),
joinPoint.getSignature().getName(),
Arrays.toString(joinPoint.getArgs()),
(end - start)
);
}
}
}
config/AppCtxAspect.java
package com.test.test1.config;
import com.test.test1.aop.ExeTimeAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class AppCtxAspect {
@Bean
public ExeTimeAspect exeTimeAspect() {
return new ExeTimeAspect();
}
@Bean
public Calculator recCalculator() {
return new RecCalculator();
}
@Bean
public Calculator impCalculator() {
return new ImpCalculator();
}
}
MainForAspect.java
package com.test.test1.main;
import com.test.test1.config.AppCtxAspect;
import com.test.test1.ex04.Calculator;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
public class MainForAspect {
public static void main(String[] args) {
AbstractApplicationContext ctx = new AnnotationConfigApplicationContext(AppCtxAspect.class);
Calculator imp = ctx.getBean("impCalculator", Calculator.class);
System.out.println(imp.factorial(10));
Calculator rec = ctx.getBean("recCalculator", Calculator.class);
System.out.println(rec.factorial(10));
ctx.close();
}
}