한 대상을 다운로드 할때 그 대상이 의존하고 있는 대상까지도 함께 다운로드함
package ch02;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppContext {
@Bean
public Greeter greeter() {
Greeter g = new Greeter();
g.setFormat("%s, 안녕하세요!");
return g;
}
}
package ch02;
public class Greeter {
private String format;
public String greet(String guest){
return String.format(format, guest);
}
public void setFormat(String format) {
this.format = format;
}
}
package ch02;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
//이 객체는 자바 설정에서 정보를 읽어옴
AnnotationConfigApplicationContext ctx =
//AppContext.class 생성자를 파라미터로 전달 -> 여기서 @Bean으로 설정한 정보를 읽어와
//Gretter 객체를 생성하고 초기화한다
new AnnotationConfigApplicationContext(AppContext.class);
//getBean 메서드는 빈 객체를 검색할 떄 사용
//첫번째 인자는 빈 객체 이름, 두 번쨰 인자는 검색할 빈 객체 타입
Greeter g = ctx.getBean("greeter", Greeter.class);
String msg = g.greet("스프링");
System.out.println(msg);
ctx.close();
}
}
빈을 등록하는 객체를 생성하고 → 거기에 빈을 담음 → 해당 빈을 네이밍 태깅하여 →해당 빈을 불러옴
즉 위의 코드에서는 greeter의 이름의 빈이 스프링 컨테이너(ApplicationContext)에 들어있고, 이게 Greeter객체와 연결되어 있다.
package ch02;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main1 {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(AppContext.class);
Greeter g1 = ctx.getBean("greeter", Greeter.class);
Greeter g2 = ctx.getBean("greeter", Greeter.class);
System.out.println("(g1 == g2) = " + (g1 == g2));
ctx.close();
}
}
위의 값은 true를 반환, 즉 스프링은 한개의 빈 객체만을 생성 → 싱글톤 객체
이 말은 @Bean 어노테이션에 대해 한개의 빈 객체만을 생성
@Configuration은 스프링 설정 클래스로 지정한다는 뜻
의존 : 한 클래스가 다른 클래스의 메서드를 실행할 때를 말함
생성자 주입 하는 이유 : 변경의 유연함
package ch03;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainForSpring {
private static ApplicationContext ctx = null;
public static void main(String[] args) throws IOException {
ctx = new AnnotationConfigApplicationContext(Appctx.class);
BufferedReader reader =
new BufferedReader(new InputStreamReader(System.in));
while (true) {
System.out.println("명령어를 입력하세요");
String command = reader.readLine();
if (command.equalsIgnoreCase("exit")) {
System.out.println("종료합니다");
break;
}
if (command.startsWith("new")) {
proccessNewCommand(command.split(" "));
continue;
} else if (command.startsWith("change")) {
processChangeCommand(command.split(" "));
continue;
}
}
printHelp();
}
private static Assembler assembler = new Assembler();
private static void proccessNewCommand(String[] arg){
if (arg.length != 5) {
printHelp();
return;
}
MemberRegisterService regSvc = ctx.getBean("memberRegSvc", MemberRegisterService.class);
//MemberRegisterService regSvc = assembler.getRegSvc();
RegisterRequest req = new RegisterRequest();
req.setEmail(arg[1]);
req.setName(arg[2]);
req.setPassword(arg[3]);
req.setConfirmaPassword(arg[4]);
if(!req.isPasswordEqualToConfirmPassword()) {
System.out.println("암호가 일치하지 않습니다 \n");
return;
}
try {
regSvc.regist(req);
System.out.println("register complete \n");
} catch (DuplicateMemberException ex) {
System.out.println("Email already exist");
}
}
private static void processChangeCommand(String[] arg){
if(arg.length != 4){
printHelp();
return;
}
// ChangePasswordService changePasswordService =
// assembler.getPwdSvc();
ChangePasswordService changePasswordService = ctx.getBean("changePwdSvc", ChangePasswordService.class);
try {
changePasswordService.changePassword(arg[1], arg[2], arg[3]);
System.out.println("암호를 변경했습니다");
} catch (MemberNotFoundException ex) {
System.out.println("존재하지 않은 이메일 \n");
} catch (WrongIdPasswordException ex) {
System.out.println("email,and password not match \n");
}
}
private static void printHelp() {
System.out.println();
System.out.println("Wrong command check command below");
System.out.println("how to use command");
System.out.println("new : check email, password check");
System.out.println("change : change email current change pass");
System.out.println();
}
}
package ch03;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Appctx {
//각 @Bean 어노테이션마다 한개의 객체를 생성한다
@Bean
public MemberDao memberDao(){
return new MemberDao();
}
//memberDao가 생성한 객체를 memberregisterservice 생성자를 통해 주입
@Bean
public MemberRegisterService memberRegSvc() {
return new MemberRegisterService(memberDao());
}
//set메소드를 통해 의존 객체 주입
@Bean
public ChangePasswordService changePwdSvc(){
ChangePasswordService pwdSvc = new ChangePasswordService();
pwdSvc.setMemberDao(memberDao());
return pwdSvc;
}
}
위의 코드는 스프링컨테이너 객체를 선언하고 각 클래스를 빈으로 생성한 클래스를 불러와서 DI 한 코드이다
생성자 방식 : 빈 객체를 생성하는 시점에서 모든 의존 객체가 주입된다.
public MEmberListPrinter(MemberDao memberDao){
this.memberDao = memberDao;
}
설정 메서드 방식 :세터 메서드 이름을 통해 어떤 의존 객체가주입되는지 알 수 있다.
public void setMemberDao(MemberDao memberdao) {
this.memeberdao = memberDao;
}
@Autowired
: 자동주입기능ㅇ 위한 어노테이션 → 스프링 빈에 의존하는 다른 빈을 자동으로 주입하고 싶을 떄 사용
스프링은 @Configuration빈이 붙은 클래스를 내부적으로 스프링 빈으로 등록한다 그리고 @Autowired가 붙은 대상에 대해 알맞은 빈을 자동으로 주입한다.
@Autowired사용
@configuration
public class App1{
@Bean
mehtod1
@Bean
method2
}
@Configuation
public class App2{
@Autowired
method1 method1;
@Autowired
metthod2 method2;
@Bean
method3(method1){}
@Bean
method4(method20 {}
}
이렇게 @Autowired를 통해 이미 등록된 빈을 다른 설정파일에 의존성 자동주입을 통해 주입시킴
@Import 어노테이션을 사용
@Configuration
@Import(App2.class)
public class impport() {
@Bean
method1(){}
@Bean
method2(){}
}
이렇게 @Import(설정 클래스)를 하게 되면 스프링 컨테이너 생성시 한번에 초기화 시킨다
@Bean
@Qualifier("printer)
public printer printer() {};
//빈의 이름을 printer로 지정해서 등록
----------------------------------
@Autowired
@Qualifier("printer")
public void setMember(Printer printer) {};
//printer로 등록된 객체를 찾아서 자동 주입함
@Autowired(required = false)
public void setDate(Datetime datetime) {}
-----------------------------------
@Autowired
public void setDate(Optional<Datetime> datetime){}
------------------------------------
@Autowired
public void setDate(@Nullable Datetime datetime){}
1.위와 같이 필수 주입 항목이 아닌 경우 required = false로 주게되면 자동주입을 하지 않는다.
2. 만약 null값이 들어오게되면 nullpointException이 발생함으로 이렇게 처리하면되는데, 자바 8부턴
optional을 이용해서 널값이 들어오면 optional을 인자로 전달하고 값이 있으면 해당값전달
3. 마지막은 @nullable 어노테이션 지정, 있으면 해당값 없으면 Null을 전달
@Autowired requried 값 지정과의 차이점은, @Nullable로 지정하면 빈이 존재하지 않아도 메서드는 호출됨
컴포넌트 스캔은 스프링이 직접 클래스를 검색해서 빈으로 등록해주는 기능
@Component("list")
public class name() {}
//클래스도 빈으로 등록 할 수 있다
@Configuration
@ComponentScan(basePackages = {"spring"})
public class AppCtx{}
//basepackage는 스캔 대상패키지 목록을 spring으로 지정한다
//즉 com.xxxx 으로 지정한 (여기서는 spring으로 예시) 패키지를 기준으로 하위 클래스에서
//@Component가 붙은 클래스를 찾아 빈 객체로 등록
@Configuration
@componentScan(basePackages = {com.xxx},
excludeFilters = @Filter(type = FilterType.REGEX, pattern ="com\\..*Dao"))
public class AppCtx{
@Bean
public method1 method() {}
@Bean
public MemberDao memberDao() {}
}
정규표현식을 사용해서 제외 대상을 설정한다
com으로 시작하고 dao로 끝나는 정규표현식 -> com.MemberDao class를 컴포넌트 스캔 대상에서 제외
---------------------------하위 코드는 FilterType.ASPECTJ를 이용함----------------
@Configuration
@componentScan(basePackages = {com.xxx},
excludeFilters = @Filter(type = FilterType.ASPECTJ, pattern ="com.*Dao"))
public class AppCtx{
@Bean
public method1 method() {}
@Bean
public MemberDao memberDao() {}
}
위의 코드와 같게 동작 그런데 이 기능을 사용할려면 aspectjwever를 디펜던시 추가해야함
----------------------------특정 어노테이션이 붙은 타입을 컴포넌트 대상에서 제외 가능 -------
@Retention(RUNTIME)
@Target(TYPE)
public @interface NoProduct {}
@Retention(RUNTIME)
@Target(TYPE)
public @interface ManualBean {}
어노테이션을 지정해 놓고, 이 어노테이션이 붙은 클래스는 컴포넌트 스캔에서 제외
@Configuration
@componentScan(basePackages = {com.xxx},
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = {NoProduct.class, ManualBean.class}))
public class AppCtx{
@Bean
public method1 method() {}
@Bean
public MemberDao memberDao() {}
}
@ManualBean
@Component
public class class1{}
위의 클래스는 제외된다.
------------------------------------------------------------------------
@Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MemberDao.class))와 같이
ASSIGNABLE_TYPE을 통해 제외할 타입목록 설정을 할 수 있다.
만이 여러 필터를 적용시 배열로 작성
excludeFilters = {
@Filter(type = FilterType),
@Filter(type = FilterType)
})
1. 컨테이너 초기화
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(AppContext.class)
2. 컨테이너에서 빈 객체를 구해서 사용
Gretter g = ctx.getBean("greeter" , Greeter.class);
String msg = g.greet("스프링")
System.out.println(msg);
3.컨테이너 종료
ctx.close()
AnnotationConfigApplicationContext를 이용해서 컨텍스트 객체 생성 및 컨테이너 초기화
greeter 이라는 이름의 빈으로 컨테이너 안에 등록하고, 이 빈을 Greeter.class와 연결 시킴
객체 생성 → 의존 설정 → 초기화 → 소멸
1.
public interface InitializingBean{
void afterPropertiesSEt()throws Exception;
}
2.
public interface DisposalBean{
void destory() throws Exception;
}
InitializingBean 인터페이스를 구현하면 초기화 과정에서 빈 객체의 afterpropertiesSet()실행, → 이 메서드에서 초기화과정 메소드를 구현하면 된다
DisposalBean인터페이스 구현시 destroy()메서드 실행 → 빈 소멸과정시 이 메소드를 구현
package ch06;
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;
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("Client, afterPorpertiseSet() 실행");
}
public void send() {
System.out.println("Client,send() to" + host);
}
@Override
public void destroy() throws Exception {
System.out.println("Client.destroy()실행");
}
}
package ch06;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppCtx {
@Bean
public Client client() {
Client client = new Client();
client.sethost("host");
return client;
}
}
package ch06;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
public class Main {
public static void main(String[] args) {
AbstractApplicationContext ctx =
new AnnotationConfigApplicationContext(AppCtx.class);
Client client = ctx.getBean(Client.class);
client.send();
ctx.close();
}
}
package ch06;
public class Client2 {
private String host;
public void setHost(String host) {
this.host = host;
}
public void connect() {
System.out.println("Client2.connect() 실행");
}
public void send(){
System.out.println("Client2.send()to" + host);
}
public void close() {
System.out.println("Client2.close() 실행");
}
}
@Bean(initMethod = "connect", destroyMethod = "close")
public Client2 client2(){
Client2 client2 = new Client2();
client2.setHost("host");
return client2;
}
Client2를 클래스 빈으로 사용시 초기화과정에서 connect 메소드를 사용하고, 소멸과정에서 close()를 실행해야 한다면
@Bean(initMethod = "connect", destroyMethod = "close")
와 같이 설정한다
주의!
afterPropertieseSet()은 빈 설정 메서드에서 호출된다. 그런데 스프링은 빈 객체 생성 이후 이 메서드를 실행함으로 이 메서드가 두번 반복적으로 호출되지 않도록 주의해야 한다
Client client1 = ctx.getBean("client", Client.class);
Client client2 = ctx.getBean("client", Client.class);
if (client1 == client2)
System.out.println("its same");
AbstractApplicationContext ctx =
new AnnotationConfigApplicationContext(AppCtx.class);
스프링 컨테이는 빈 객체를 한개만 생성한다(AppCtx.class)
AppCtx.class의 객체 안에 여러개 설정해도 스프링은 기본 싱글톤 이기 때문이다.
빈의 범위 → 프로토타입의 경우 빈 객체를 구할때마다 매번 새로운 객체 리턴
특정 빈을 프로토타입으로 범위 지정하는 법
@Configuration
public class AppCtxWithPrototype {
@Bean
@Scope("prototype")
public Client client(){
Client client = new Client();
client.sethost("host");
return client;
}
}
이렇게 설정파일을 프로토타입으로 범위를 지정하게되면 빈 객체마다 새로운 객체가 생성됨으로
같지 않음으로 출력
이렇게 서로 다른 범위를 지정해주면
서로 같지 않은 객체로 출력
둘다 싱글톤 타입이면
서로 같은 객체 반환으로 출력
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
dependenc추가 → AOP 어노테이션 사용을 위한 목적
계산기 인터페이스 하나만들고
첫번째는 단순 계산, 두번째는 재귀 로 구현하는 클래스 2개를 구현
package ch07;
public interface Calculator {
public long factorial(long num);
}
package ch07;
public class ImpCalculator implements Calculator {
@Override
public long factorial(long num) {
long result = 1;
for (long i = 1; i <= num; i++){
result +=1;
}
return result;
}
}
package ch07;
public class RecCalculator implements Calculator {
@Override
public long factorial(long num) {
if (num ==0)
return 1;
else
return num * factorial(num - 1);
}
}
두 구현 클래스의 계산 시간을 측정할클래스를 AOP로 구현 → 프록시 객체 이용
package ch07;
public class ExeTimeCal implements Calculator{
//Calculator 주입받음
private Calculator delegate;
//생성자로 필드에 할당
public ExeTimeCal(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",
delegate.getClass().getSimpleName(),
num, (end -start));
return result;
}
}
이 클래스는 생성자를 통해 Calculator 객체를 전달받아 delegate에 할당함
package ch07;
public class Main {
public static void main(String[] args) {
ExeTimeCal cal1 = new ExeTimeCal(new ImpCalculator());
System.out.println(cal1.factorial(20));
ExeTimeCal cal2 = new ExeTimeCal(new RecCalculator());
System.out.println(cal2.factorial(20));
}
}
객체 생성시 시간 측정 클래스안에 각각 Imp와 Rec로 객체를 생성함
→ 기존 코드 변경 없이 공통 코드를 빼냄
여기서 핵심 기능
long result = delegate.factorial(num)
을 통해 핵심기능을 다른 객체에 위임함
이 코드를 통해 factorial로 계산하는 부분은 각각
ImpCalculator() 와 RecCalculator() 가 실행하게되고 ExeTimeCal안의 factorial 메소드는 시간만 측정하면 된다
여러 객체에 공통으로 적용할 수 있는 기능을 분리해서 재사용성을 높여주는 프로그래밍 기법
기본개념 :
핵심 기능에 공통 기능을 삽입하는 것
이러한 방법엔 3가지가 있다
컴파일 시점에 코드에 공통 기능을 삽입
클래스 로딩 시점에 바이트 코드에 공통 기능 삽입
→ 위 2개는 AspectJ와 같은 AOP전용 도구필요
Client -> AOP프록시 → 공통기능 모듈 → 실제 객체
package ch07;
import java.util.Arrays;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class ExeTimeAspect {
@Pointcut("execution(public * ch07..*(..))")
private void publicTarget(){
}
@Around("publicTarget()")
public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.nanoTime();
try {
Object result = joinPoint.proceed();
return result;
} finally {
long finish = System.nanoTime();
Signature sig = joinPoint.getSignature();
System.out.printf("%s.%s(%s)실행시간 : %d ns\n",
joinPoint.getTarget().getClass().getSimpleName(),
sig.getName(), Arrays.toString(joinPoint.getArgs()),
(finish - start));
}
}
}
@Pointcut : 공통 기능 적용할 대상 설정 → 여기서는 Ch07 패키지에 위치한 public 타입 메서드만 설정
@Around : Around Advice를 설정한다. 여기서는 publicTarget()로 해당 값을 지정하였는데,
publicTarget 메소드 안에 정의한 pointcut에 공통 기능을 저으이한다는 뜻이다
그런데 publicTarget()은 ch07과 그 하위 패키지에 public 메서드를 pointcut으로 정의함
따라서 여기서는
package ch07;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class AppCtx {
@Bean
public ExeTimeAspect exeTimeAspect(){
return new ExeTimeAspect();
}
@Bean
public Calculator calculator() {
return new RecCalculator();
}
}
@ EnableAspectJAutoProxy : @Aspect 어노테이션 기능 적용을 위해 선언
위에서
@Around 어노테이션은 publicTarget에 설정함
이 메서드의 pointcut은 ch07하위 패키지에 있는 빈 객체 public 메서드를 설정함
아래 설정한 calculator 타입이 위의 2조건에 맞음으로 ExeTimespect 클래스에서 정의한
meaure()가 아래 정의된 빈에 적용됨
@Bean
public Calculator calculator() {
return new RecCalculator();
}
책에서는 @Autowired 대신 @Bean으로 주입받음
@Autowired로 바꿔서 자동주입시킴(일반적으론 @Lazy로딩처리한다고한다)
package ch07;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainAspect {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(AppCtx.class);
Calculator cal = ctx.getBean("calculator", Calculator.class);
long fiveFact = cal.factorial(5);
System.out.println("cal.factorial(5) =" + fiveFact);
System.out.println(cal.getClass().getName());
ctx.close();
}
}
Appctx에 정의된
@Bean
public ExeTimeAspect exeTimeAspect(){
return new ExeTimeAspect();
}
에서 스프링 빈 순환참조 에러 일어남 어디서 터지는지 모르겠음
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppCtx {
@Bean
public ExeTimeAspect exeTimeAspect(){
return new ExeTimeAspect();
}
@Bean
public Calculator calculator() {
return new RecCalculator();
}
}
proxyTargetClass = True
인터페이스가 아닌 자바 클래스를 상속받아 프록시를 생성함
@Pointcut("execution(public * ch07..*(..))")
Execution 명시자는 Advice를 적용할 메서드를 지정할 때 사용
기본형
excution(수식어 패턴? 리턴타입 패턴 클래스 이름패턴? 메서드 이름패턴(파라미터 패턴))
@Aspect
public class {
@Around("execution(public * ))")
public Object execute(ProceedingJoinPoint joinpoint)
}
@Pointcut 이 아닌 @Around 어노테이션에 execution 명시자 직접 지정 할 수 있다
같은 pointcut을 여러 advie가 사용시 재사용 할 수 있음
각 Asepct 클래스가 공통으로 사용할 pointcut 선언
public class CommonPointcut {
@Pointcut("execution(public * ch07))")
public void commonTarget(){
}
}
@Aspect
public Class class1 {
@Around("CommonPointcut.commonTarget()")
public method
}
@Aspect
public Class class2 {
@Around("CommonPointcut.commonTarget()")
public method
}
위와 같이 선언