Jam's story

[Spring] days02 - AOP 본문

Spring

[Spring] days02 - AOP

애플쩀 2022. 7. 12. 17:03

스프링이란: 자바 표준 프레임워크 

스프링 사용하는방법 :

jar 파일 추가

-스프링 기능별로 주요 모듈 + 의존관계 ->jar 추가 

-메이븐 빌드도구 : 프로젝트 생성 +배포 /pom.xml <dependency>

-서버 중앙저장소 (centeral repository)->jar 다운받아서 ->로컬저장소 

 

 

에러메세지 
7월 12, 2022 10:17:28 오전 org.springframework.context.support.AbstractApplicationContext prepareRefresh
정보: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@887af79: startup date [Tue Jul 12 10:17:28 KST 2022]; root of context hierarchy
Exception in thread "main" java.lang.IllegalStateException: CGLIB is required to process @Configuration classes. Either add CGLIB to the classpath or remove the following @Configuration bean definitions: [config]
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.enhanceConfigurationClasses(ConfigurationClassPostProcessor.java:214)
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanFactory(ConfigurationClassPostProcessor.java:145)
	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:640)
	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:630)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:405)
	at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:65)
	at springDI.Ex02.main(Ex02.java:34)

 

 

이 파일을 추가해주면 된다 .

 

에러메세지

 

 

 

 

Config.java -> applicationContext.xml 파일 대신에 자바파일을 사용 - di.Config.java

@Configuration, @Bean 어노테이션을 사용해야 된다.

RecordViewImpl 객체는 RecordImpl 객체에 의존한다  

 RecordViewImpl.java에서 이 두 부분을 이용하여(나머지코드생략) , Config.java 파일 만듬 

package di;

import java.util.Scanner;

public class RecordViewImpl implements RecordView {
	private RecordImpl record =null;

	//프로퍼티 의존성 주입방식
	public void setRecord(RecordImpl record) {
		this.record=record;
	}


}
package di;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//XML 파일을 대신할 JAVA 설정 파일(DI)
//@Configuration, @Bean 어노테이션을 사용해야 된다.
@Configuration
public class Config {
   //RecordImpl record = new RecordImpl();
   //<bean id="record" class="di.RecordImpl"></bean>
   @Bean
   public RecordImpl record() {
      return new RecordImpl();
   }
   
   
   /*
    * <bean id="rvi" class="di.RecordViewImpl"> <property name="record"
    * ref="record"></property> </bean>
    * 
    */
   
   @Bean(name="rvi")
   public RecordViewImpl getRecordViewImpl() {
      RecordViewImpl rvi = new RecordViewImpl();
      rvi.setRecord(record());//setter 프로퍼티를 통해서 DI
      return rvi;
   }
   
}
name="식별값" 
// @Bean(name="rvi") 
//식별값이 rvi인,  타입이 RecordViewImpl 인 객체를 가져와라 
RecordViewImpl rvi=ctx.getBean("rvi",RecordViewImpl.class); 

 

Ex02.java

package springDI;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.GenericXmlApplicationContext;

import di.Config;
import di.RecordViewImpl;

public class Ex02 {
public static void main(String[] args) {
	//스프링 주요 모듈을 사용하여 recordViewimpl 객체 생성하여 
	//성적 입력, 출력하는 코딩을 하겠다. 
	/*
	 * 스프링 주요 모듈(jar) 추가
	 * 1) 메이븐 빌드도구를 사용하면 pom.xml <dependency></dependency>
	 * 2)라이브러리에 jar파일추가 
	 * 
	 * 
	 * file:///C:/spring-framework-3.0.2.RELEASE/docs/spring-framework-reference/htmlsingle/spring-framework-reference.html
	 */
	
	//이제는 객체생성을 new 연산자로 하지 않는다. 
	/*
	 * String resourceLocations="applicationContext.xml";
	 * GenericXmlApplicationContext ctx=new
	 * GenericXmlApplicationContext(resourceLocations);
	 * 
	 * //Object 리턴->RecordViewImpl클래스로 다운캐스팅 RecordViewImpl
	 * rvi=(RecordViewImpl)ctx.getBean("rvi");
	 * 
	 * rvi.input(); rvi.output(); System.out.println("end");
	 */
	
	AnnotationConfigApplicationContext ctx= new AnnotationConfigApplicationContext(Config.class);
	//스프링 컨테이너(공장)_= 스프링 빈 객체 생성+조립
	//공장안에서 getBean해서 가져오자 
	//RecordViewImpl rvi=(RecordViewImpl)ctx.getBean("rvi");
	RecordViewImpl rvi=ctx.getBean("rvi",RecordViewImpl.class); //	@Bean(name="rvi")
}
}

 


Project springDI03 만들기 

어노테이션 이용해서 객체 간 의존  자동 연결 

@Autowired (생성자 ,필드 ,메소드 ) 세 곳에 적용 가능 

@Resource

@Inject

 

 

@Autowired 또는 @Resource 어노테이션을 사용하려면  xml 파일에 추가 

    <context:annotation-config></context:annotation-config>

    혹은 단일태그 <context:annotation-config/>

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">
      <!--      @Autowired 또는 @Resource 어노테이션을 사용하려면 추가 
      의존 자동연결할때 필요한 객체들을 등록하는 코딩  -->
    <context:annotation-config></context:annotation-config>

 

@Autowired 사용하기  


 RecordImpl 스프링 빈객체가 생성되면  ==  <bean id="record" class="di.RecordImpl"></bean> 
자동으로 의존 주입(DI)

만약 , 일치하는 스프링 빈이 없다면 예외를 발생하는데 

예외를 발생시키지 않으려면 @Autowired(required=false) 로 설정

package di;

import java.util.Scanner;

import org.springframework.beans.factory.annotation.Autowired;

public class RecordViewImpl implements RecordView {

@Autowired
	private RecordImpl record =null;


}

(코드 밑부분은 생략)

 

이름을 이용하여 주입할때는  @Resource

	/*p111 xml에서 <bean id="record2" class="di.RecordImpl"></bean> */
	@Resource(name="record2")
	private RecordImpl record =null;

 


Spring04 프로젝트 만들기 + 생성

Ex02.java 

p115 컴포넌트 스캔을 이용한 빈들을 등록 

 

만약. xml에 생성해야하는 객체가 수백개가 되어야 한다면 ?

컴포넌트 스캔 

패키지를 설정하면, 특정 패키지 안에서 스캔이되어져서 자동으로 빈으로 등록이 되고, 필요한 것들은 자동으로 주입이 되는것 

= 빈 객체 자동으로 스캔하고 + 자동의존 주입 

 

xml에 추가. 

di 만 등록하면 그 밑에 있는 모든 패키지를 (하위패키지) 모두 등록됨 

<context:component-scan base-package="di"></context:component-scan>

 

RecordViewImpl.java 

(이름 =id 가 없으니 @Resource말고 @Autowired)

@Component //자동스캔
public class RecordViewImpl implements RecordView {
    @Autowired //자동으로 주입까지 시켜줌 
    private RecordImpl record = null;
    }

 

 

에러가 났는데, 

]

검색된 클래스를 빈으로 등록할때 (첫글자를 소문자)로 빈의 이름을 사용한다. 

	  RecordViewImpl rvi=(RecordViewImpl)ctx.getBean("recordViewImpl");

 

@component
public class ProductService{}
ProductService svc=context.getBean("productService",ProductService.class)

스프링 프레임워크 사용하면 객체생성+조립 x (Sample s=new Sample())

 

지금까지 프로젝트 정리 

[springDI] xml 파일에 생성자 혹은 setter 로 주입

[springDI02] Config 자바파일로 주입 JavaSE-1.8 을 Jre 로 바꿔야한다.

[springDI03] xml추가하고  @Autowired  @Resource한다.

[springDI04] 컴포넌트 스캔(스프링 가능) 스캔 대상이 되는 클래스 - 스프링 빈 객체 생성 

 

@Component //자동스캔
public class RecordViewImpl implements RecordView {
    @Autowired //자동으로 주입까지 시켜줌 
    private RecordImpl record = null;
    }

 

@Component("rvi") 괄호안에 이름을 주면 id는 설정한 id로 (rvi)로 설정되고,

설정하지 않으면 기존 클래스의  소문자로 지정됨 

@Component("rvi") //자동스캔
public class RecordViewImpl implements RecordView {
    @Autowired //자동으로 주입까지 시켜줌 
    private RecordImpl record = null;
    }

.xml

<context:component-scan base-package="부모패키지"></context:component-scan>

하위패키지도 자동 스캔된다.

 

p118 Top 

@Component 어노테이션

 ㄴ @Service 서비스클래스 

   ㄴ@Repository : DAO 클래스 

     ㄴ@ Controller: MV[C] 컨트롤러 클래스 

 

스캔 대상 클래스 범위 지정하기 



SpringAOP 프로젝트 생성

 

 

 

AOP  = 관점지향적인 프로그래밍 기법 

2가지관점

1) 공통부분: 인증(로그)처리, 트랜잭션 ,보안 부분 [공통적인 부분을 cross-cutting concern]

2)본연의 업무 (로직부분) => 글쓰기, 글수정 ,글삭제 부분 [핵심 관심 사항 core concern]

글쓰기 /write.do/ writeHandler /세션인증 null-> 로그인 요청 이동 

글수정  /edit.do/ EditHandler /세션인증 null-> 로그인 요청 이동 

글삭제  /delete.do/DeleteHandler /세션인증 null-> 로그인 요청 이동 

출처: 뉴렉처

  1. +++ 반드시 알아야할 AOP 용어 +++ (암기)
    • Aspect: 여러 객체에 공통으로 적용되는 기능 (공통기능)
    • Advice: 공통기능을 핵심기능에 [언제] 적용할지를 정의 - 전+후, 전, 후
    • Weaving: advice 를 핵심로직코드에 적용 (삽입) 하는 것.
    • Joinpoint: Advice 를 적용 (삽입) 가능하는 지점- 예) add() 메서드 호출
    • Pointcut: Advice 를 실제 적용한 지점.
      • 스프링에서는 정규표현식, [AspectJ문법] +++ 을 이용해서 Pointcut 을 설정할 수 있다.
  2. 세가지의 Weaving 방식
    • 컴파일 시
    • 클래스 로딩 시
    • 런타임 시
      • 프록시 (proxy) 기반으로 AOP 지원하기 때문에
      • Joinpoint 를 메서드만 사용할 수 있다.
  3. 스프링 AOP 구현하는 3가지 방법
    • 스프링 API 이용한 AOP 구현 X
    • XML 스키마 기반의 POJO 클래스를 이용한 AOP 구현 O - xml 파일
    • AspectJ 에서 정의한 @Aspect 어노테이션 기반의 AOP 구현 O - @Aspect 어노테이션
  4. Advice 종류
    • Before Advice 
    • Around Advice 
    • After Advice,
    • After Throwing Advice,
    • After Returning Advice

SpringAOP  프로젝트  AOP

app.Calculator.java 인터페이스  덧.뺄.곱.나눗셈메소드

aop.Calculator.impl.java 구현클래스

Ex01.java main(){

//CalculatorImpl 객체 생성해서 계산

}

 


자바로 

Calculator 인터페이스 

package aop;

public interface Calculator {

	int add(int x,int y);
	int sub(int x,int y);
	int mult(int x,int y);
	int div(int x,int y);
}

 

 

CalculatorImpl.java 

package aop;

public class CalculatorImpl implements Calculator {

	@Override
	public int add(int x, int y) {	
		long start=System.nanoTime();
		int result=x+y;
		long end=System.nanoTime();
		System.out.printf("처리시간 :%dns\n",(end-start));
		return result;
	}

	@Override
	public int sub(int x, int y) {
		long start=System.nanoTime();
		int result=x-y;
		long end=System.nanoTime();
		System.out.printf("처리시간 :%dns\n",(end-start));
		return result;
	}

	@Override
	public int mult(int x, int y) {
		long start=System.nanoTime();
		int result=x*y;
		long end=System.nanoTime();
		System.out.printf("처리시간 :%dns\n",(end-start));
		return result;
	}

	@Override
	public int div(int x, int y) {
		long start=System.nanoTime();
		int result=x/y;
		long end=System.nanoTime();
		System.out.printf("처리시간 :%dns\n",(end-start));
		return result;
	}

}

springAOP2프로젝트 생성 

 

 

 CalculatorImpl.java를 springAOP 에서 복사해와서 공통된 기능들은 모두 제거 

	@Override
	public int add(int x, int y) {	
		int result=x+y;
		return result;
	}

앞뒤로 지웠으니 around advice생성

 

라이브러리 추가 

 

LogPrintAroundAdvice 

package aop.advice;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StopWatch;

// com.springsource.org.aopalliance-1.0.0.jar  추가
// MethodInterceptor 인터페이스를 구현해야 된다. - AroundAdvice
public class LogPrintAroundAdvice implements MethodInterceptor{

   // method  == add()/ sub()/ mult()/ div()
   @Override
   //호출한 메소드가 인자값으로 들어온다. 
   public Object invoke(MethodInvocation method) throws Throwable {
       String methodName =    method.getMethod().getName();
       
       // 스프링 에서 제공하는 StopWatch 클래스 -> 로그 처리.
       Log log = LogFactory.getLog(this.getClass());
       StopWatch sw  = new StopWatch();
log.info("> " + methodName + "() start.");       
       sw.start();
       Object result  = method.proceed(); //핵심기능 (add(), sub(),,,) 
       //메소드가 실행되고 반환되는 값을 Object로 받음 
       sw.stop();
log.info("> " + methodName + "() stop.");
log.info("> " + methodName + "() 처리 시간 :  " + sw.getTotalTimeMillis() +"ms");       
       // 로그 처리
      return result;
   }

} // class

 

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="calc" class="aop.CalculatorImpl"></bean>           
    <bean id="logPrintAroundAdvice" class="aop.advice.LogPrintAroundAdvice"></bean>           

<!-- calc의 add() sub() 등등 메서드등을  pointcut으로 설정+aroundadvice 등록  -->
<!--스프링 AOP: 프록시(proxy)기반 => : 핵심기능+ 공통기능 같이 처리하는 가짜 프록시를 만들자  -->
    <bean id="calcProxy" class="org.springframework.aop.framework.ProxyFactoryBean">

        <!-- [1] -->
        <property name="target" ref="calc"></property>

      
<!--[2] pointcut 설정= 적용되는 지점  -->
<!-- name값은 우리가 주는것이 아니다.  -->
<!-- setProxyInterface ->proxyInterface -->
        <property name="proxyInterfaces">
            <list>
                <value>aop.Calculator</value>
            </list>
        </property>

        <!-- [3] Advice 등록 -->
        <property name="interceptorNames">
            <list>
                <value>logPrintAroundAdvice</value>
            </list>
        </property>
    </bean>
</beans>

Ex01.java

package springDI;

import org.springframework.context.support.GenericXmlApplicationContext;

import aop.Calculator;
import aop.CalculatorImpl;

public class Ex01 {
public static void main(String[] args) {
	String  resourceLocations="applicationContext.xml";
	GenericXmlApplicationContext ctx= new GenericXmlApplicationContext(resourceLocations);

	//Calculator calc=ctx.getBean("calc", CalculatorImpl.class);
	Calculator calc=ctx.getBean("calcProxy", CalculatorImpl.class);
	System.out.println(calc.add(4, 2));
}
}
  • Calculator 인터페이스를 하나 선언함 
  • CalculatorImpl 클래스 구현함 = 여기서 공통기능은 다 빼고 핵심기능만 냅둠 
  • 보조기능을 할수있는 aroundAdvice인 LogPrintAroundAdvice클래스만듬 (로그기록- 전후 ) 
  • Calculator calc=ctx.getBean("calcProxy", Calculator.class); 보조기능이 장착된 Proxy를 쓰자 

LogPrintBeforeAdvice .java

package aop.advice;

import java.lang.reflect.Method;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.MethodBeforeAdvice;

public class LogPrintBeforeAdvice implements MethodBeforeAdvice{

	@Override
	public void before(Method method, //add()
									Object[] args, //그 메소드 호출할때 매개변수들 
									Object target //핵심 기능이 구현된 실제객체 (calc)
									) throws Throwable {
		
		String methodName=method.getName();
		Log log=LogFactory.getLog(this.getClass());
		//호출되는 지 확인만 해보는 예제 
		log.info(">>>"+methodName+"() logPrintBeforeAdvice 호출됨 ");
	}

}

xml에 추가 

  <bean id="logPrintBeforeAdvice" class="aop.advice.LogPrintBeforeAdvice"></bean>
 
  <property name="interceptorNames">
        <list>
           <value>logPrintAroundAdvice</value>
           <value>logPrintBeforeAdvice</value>
        </list>
      </property>

 

LogPrintAfterReturningAdvice.java

package aop.advice;

import java.lang.reflect.Method;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.AfterReturningAdvice;

//핵심기능이 수행후에  예외가 발생하지 않았을경우 
public class LogPrintAfterReturningAdvice implements AfterReturningAdvice {

	@Override
	public void afterReturning
	(Object returnValue,
			Method method,  //add()
			Object[] args, //매개변수
			Object target) throws Throwable {

		String methodName=method.getName();
		Log log=LogFactory.getLog(this.getClass());
		//호출되는 지 확인만 해보는 예제 
		log.info(">>>"+methodName+"() logPrintAfterReturningAdvice호출됨 :"+ returnValue);
	}

}

xml에 추가 

    <!--  id는 소문자 -->

     <bean id="logPrintBeforeAdvice" class="aop.advice.LogPrintBeforeAdvice"></bean>

      <property name="interceptorNames">
        <list>
           <value>logPrintAroundAdvice</value>
           <value>logPrintBeforeAdvice</value>
             <value>logPrintAfterReturningAdvice</value>
        </list>
      </property>

 

실행결과 


springAOP02 복사해서 springAOP02 _02

 

  • advice 파일마다 @Component를 붙여주었고 xml 파일을 수정하였다. 

xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">

<context:component-scan base-package="aop"/>           

    <bean id="calcProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
     
    </bean>
</beans>
에러메세지 
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'calcProxy' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'calc' while setting bean property 'target'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'calc' is defined

 

 

 

@Component("calc")로 바꾸어주어야한다. 

 

오늘까지 배운것은 AOP방법인데 잘 쓰이지 않는다. 

 


XML 스키마 기반의 POJO 클래스를 이용한 AOP구현

/*
       * 스프링 AOP 3가지 방법 중에
       * *** [ XML 스키마 기반 AOP 구현 ] *** p209
       * (처리과정)
       *  1) 스프링 AOP를 사용하기 위해 jar 의존파일 추가
       *     com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
       *  2) aop.advice 패키지              --      삭제
       *           ㄴ B, A,A advice 3가지
       *       --> 공통 기능을 제공할 클래스 추가   
       *       aop.LogPrintProfiler.java 
       *                 trace() 구현 - Around Advice
       *  3) xml 설정 파일 
       *    ㄴ   aop 설정하는 태그
       *     Aspect를 설정
       *     Advice를 어떤 Pointcut에 적용할지를 설정(지정)
       * */

 

 

 

Comments