spring之AOP(转)

Spring之AOP篇:

AOP框架是Spring的一个重要组成部分.但是Spring IOC 并不依赖于AOP,这就意味着你有权力选择是否使用AOP,AOP作为Spring IOC容器的一个补充,使它成为一个强大的中间件解决方案。

一、AOP(Aspect-Oriented Programming)

    AOP的目标:横切关注点  这种问题用面向对象很难解决

  AOP是用来解决什么问题的呢?
     横越多个模块的业务关注点,比如记录日志,事务管理
 1、AOP的解决方案——拦截
   1)由谁来拦截?--代理类Proxy 和本类实现同一个接口
          就像是这样:action发出命令--代理类(处理一些操作)--本类
   2)拦截的过程:
       I.将Proxy(如UserServiceProxy)交给调用者(如UserAction),调用者调用需要的方法
       II.Proxy先拦截该方法,执行拦截逻辑
       III.然后执行调用者真正的实现类(如UserServiceImp)
   3)核心概念:
     a、代理对象Proxy:是核心,负责拦截,由工具自动创建
     b、拦截器(Interceptor):实现拦截逻辑的对象
     c、目标对象(Target):是Proxy所代理的对象,是已有的类
  以上三个类实现了一个“金三角”
     Proxy--拦截-->Interceptor--实现拦截逻辑--处理目标对象-->Target

 2、代理如何实现?

     
有两种方式:

       
1)JDK动态代理(使用这个比较多)

           
由JDK自带的动态代码生成技术,可以对实现了接口的类进行处理

       
2)CGLib

    
对没有实现任何接口的类进行处理

 这两种方式的共同点:(都是在后台自动生成的)

  在程序运行期间,动态的生成代码并进行动态编译和加载

     

   
问题:Spring的AOP是如何实现代理的?是自己实现的吗?

       
注:Spring借用了JDK动态代理和CGLib来实现代理对象。

    
Spring进行如下判断来实现代理:

  i、如果目标类实现了接口,Spring则调用JDK动态代理;

  ii、反之,调用CGLib

     

     
Proxy是核心:

     
创建Proxy的方法:ProxyFactoryBean

 <bean id="arithProxy"

        
class="org.springframework.aop.framework.ProxyFactoryBean">

      
<property name="target"
ref="arith"/>

      
<property
name="interceptorNames">

      
<list>

       
<!--<value>logBefore</value>

          
<value>logAfter</value>

          
<value>logThrows</value>
-->

      
<value>logAround</value>

     
</list>

    
</property>

      JDK动态代理的实现方式:(工厂模式)
  UserDao userDao = new UserDaoImp();
  UserDao proxy = (UserDao)
  Proxy.newProxyInstance(userDao.getClass().getClassLoader(),

    userDao.getClass().getInterfaces(),

    new
LogHandler(userDao));

二、Spring中的AOP方式(在Spring中将拦截称为通知)
   拦截逻辑放在Advice之中,Advice按时间将拦截器分为四类:
      1)Before Advice:方法执行之前进行拦截
    public class LogBeforeAdvice implements MethodBeforeAdvice{

 //Method method拦截的方法
 //Object[] args 方法中的参数
 //target 目标对象
 @Override
 public void before
 (Method method, Object[] args, Object target)
   throws Throwable {
  MyLogger logger = new MyLogger();
  System.out.println("Methods:"+method.getName()+

    
begins, args:"+Arrays.toString(args));

 }

}

    2)After
Advice:方法执行之后进行拦截

  public class LogAfterAdvice implements
AfterReturningAdvice{

 @Override

 public void afterReturning(Object resultValue,
Method method, Object[] args,

   Object
target) throws Throwable {

  MyLogger logger = new
MyLogger();

  System.out.println("Methods:"+method.getName()+

    
ends, result:"+resultValue);

 }

}

   3)AfterThrowing
Advice:方法异常结束之后进行拦截

  public class LogThrowingAdvice implements
ThrowsAdvice {

 

 //ThrowsAdvice 没有方法,但方法名必须是afterThrowing

 public void
afterThrowing(IllegalArgumentException e)

 throws Throwable{

  MyLogger logger = new
MyLogger();

  logger.log("IllegalArgumentException!");

 }

}

   4)Around Advice:环绕通知

  public class LogAroundAdvice implements
MethodInterceptor {

 //MethodInvocation相当于Method的包装
 @Override
 public Object invoke(MethodInvocation methodInvocation)
 throws Throwable {
  MyLogger logger = new MyLogger();
  try {
   //methodInvocation.getMethod()获得方法

   //methodInvocation.getArguments()获得方法的参数

   logger.log("before:"+

     methodInvocation.getMethod().getName()+

     ",
args:"+methodInvocation.getArguments());

   

   //在环绕通知里面,必须执行目标方法!!!!!!!!!!!!!!

   Object result
= methodInvocation.proceed();

   logger.log("ends:"+

     methodInvocation.getMethod().getName());

   return
result;

  } catch (Exception e) {

   logger.log("Exception:"+e.getMessage());

   throw
e;

  }

 }

  注意:虽然环绕通知包含了另外三种,但还是要依据业务逻辑来选择,这样有利于代码的编程量
       并且环绕通知最好不要和另外三种混用,并且并许执行目标方法proceed().

 也可以大致分成两组——
 i、BeforeAdvice, AfterReturning, Throwing
 ii、AroundAdvice:需要在内部负责调用目标类的方法。
      注意:AroundAdvice应单独使用


<!-- Target Object -->
<bean id="arith" class="myspring.calculator.ArithmeticCalculatorImp"></bean>

<!-- Advice -->
<bean id="logBefore" class="myspring.aop.LogBeforeAdvice"/>
<bean id="logAfter" class="myspring.aop.LogAfterAdvice"/>
<bean id="logThrows" class="myspring.aop.LogThrowingAdvice"/>
<bean id="logAround" class="myspring.aop.LogAroundAdvice"/>

<!-- Proxy -->
<bean id="arithProxy"
class="org.springframework.aop.framework.ProxyFactoryBean">

<property name="target"
ref="arith"/>

<property
name="interceptorNames">

  <list>

   
<!--
<value>logBefore</value>

   
<value>logAfter</value>

   
<value>logThrows</value>
-->

   
<value>logAround</value>

  </list>

</property>

</bean>

在test类中这样用:ApplicationContext ac =
   new ClassPathXmlApplicationContext("aop-base.xml");
  IArithmeticCalculator proxy =
   (IArithmeticCalculator)ac.getBean("arithProxy");

     

 面试中可能会问到:
          proxyTargetClass属性
  将其加入到代理bean中去,则Spring将调用CGLib来实现创建Proxy对象。

                  
(无论Target有无实现接口)

     
形如

    
<bean id=""
class="....ProxyFactoryBean">

    .....

      
<property name="proxyTargetClass" value="true"
/>

    
</bean>

 三、Spring AOP基本方式  AspectJ
Spring2.5之后用的

Spring对AspectJ的支持
      应用于有多个target,不想一个一个的配置时,用AspectJ     

1)POJO类(Aspect类),使用@Aspect 

  基本信息:Advice和Pointcut  

2)定义Bean:在Bean定义文件中定义这个Aspect类 

3)启用Spring的自动代理

  <aop:aspectj-autoproxy
/>  

   1、JoinPoint(连接点):被拦截的方法
                程序执行过程中的点,就好像方法中的一个调用,
  或者一个特别的被抛出的异常
               在Spring AOP中,一个连接点通常是方法调用.
   Spring并不明显地使用"连接点"作为术语,

  连接点信息可以通过MathodInvocation的参数穿过拦截器访问,

  相当于实现org.springframework.aop.Pointcut接口.

 2、JoinPoint对象

  获取连接点信息(获取目标方法以及目标对象的信息) 

 1)目标方法:

  i、getSignature():方法签名

    joinPoint.getSignature().getName()

  

  ii、getArgs():方法的实参(方法的实际参数)

                 
joinPoint.getArgs()

  打印输出时:Arrays.toString(joinPoint.getArgs())

 2)目标对象:  getTarget()

    目标对象实际类型:getTarget().getClass()

 3、PointCut(切入点):一组连接点。
                作用:定义了一组被拦截的方法 
  PointCut(切入点):当一个通知被激活的时候,

  会指定一些连接点.一个AOP框架必须允许开发者指定切入点。

  一个AOP框架必须允许开发者指定切入点:例如使用正则表达式.

  只有引入了切入点,才能很好的进行拦截

  PointCut的意义:可以一次性定义多个类的
    多个方法作为拦截目标(即JoinPoint)

  PointCut代表了Target
一组Target组成了PointCut

  一组Target可以理解为一个类的多个方法,或者多个类的多个方法

  代理、拦截器、目标

  PointCut
   在AspectJ中,切入点是用PointCut Expression来
   定义的。(切入点表达式可以理解为过滤条件)

   

   PointCut表达式

    格式 
execution(表达式)

    表达式 
一个表达式就是一个匹配规则

     *
myspring.calculator.IArithmeticCalculator.add(..)

  理解为public void
myspring.calculator.IArithmeticCalculator.add(int a,int b)

  
取代public void
通配一切字符   

   Spring自动代理(即Spring自动生成Proxy对象)

    用<aop:aspectj-autoproxy
/>来启用

 Pointcut表达式语法
 1)方法级别  —— execution
  execution(* *.*(..)) :所有类的方法
  execution(public * somepackage.SomeClass.someMethod(..))
  总结:execution是对方法签名进行匹配
 2)对象级别(类级别)—— within
 within(somepackage.*):somepackage包下所有的类
 within(com.dang.service.*Service)
 即 com.dang.service包下所有以Service结尾的类
 within(somepackage..*):somepackage以及所有子包中的类
 within(somepackage.SomeInterface+):SomeInterface接口的所有实现类

 总结:within是对类名(带包名)进行匹配。 拦截这个类的所有方法 


4、Aspect类的组成  一般都用注解 重点
  1)Advice(AspectJ中的通知类型)
   i、Before

  
//@Before("LogPointcuts.logAdd()") 方法1

           
@Before("execution(* .(..))") 方法2

        
public void logBefore(JoinPoint joinPoint){

    
logger.log("{before} the method: "

    
  +joinPoint.getSignature().getName()+

    
  
args:"+Arrays.toString(joinPoint.getArgs())); 

 }

            
注:方法1和方法2都是可以的,方法1的话需要在定义一个方法.

  在同一个类或不同的类都是可以的 直接调用这个方法

                 
@Pointcut("execution(* .(..))")

         
public void logOperation(){}

   ii、AfterReturning

  @AfterReturning(pointcut="execution(*
.(..))",

   returning="result")

    public void
afterReturning(JoinPoint joinPoint,Object result){

   logger.log("{AfterReturning}
the method: "

     
  +joinPoint.getSignature().getName()

     
  +"
result:"+result); 

 }

 
  注意:方法有两个参数,在注解中的returning的值必须和Object的值完全一样

   iii、AfterThrowing

  @AfterThrowing(pointcut="execution(*
.(..))",

   throwing="e")

    public void
afterThrowing(JoinPoint joinPoint,IllegalArgumentException
e){

  logger.log("{AfterThrowing} the
method: "

    
  +joinPoint.getSignature().getName()

    
  +" e:"+e.getMessage());

 }

   iv、After:相当于finally,它在AfterReturning

     和AfterThrowing之后执行。

     它的作用——一般为释放资源(如关闭Connection)

  @After("execution(*
.(..))")

 public void after(JoinPoint joinPoint){

  logger.log("{After}
method:"+joinPoint.getSignature().getName());

 }

  注意:After是肯定是要在最后执行的,
   v、Around

  @Around("execution(*
.(..))")

 public Object logAround(ProceedingJoinPoint
joinPoint) throws Throwable{

  logger.log("{Arount}Before
method:"

    +joinPoint.getSignature().getName());

  try{

                      
//一定不要忘记调用目标方法

   Object result
= joinPoint.proceed();

   

   logger.log("{Arount}After
method:"

     +joinPoint.getSignature().getName()

     +"
result:"+result);

        
//返回目标方法的返回值

   return
result;

  }

  catch(IllegalArgumentException
e){

   logger.log("{Around}Exception:Illegal
Argument"

     +Arrays.toString(joinPoint.getArgs()));

   throw
e;

  }

  注意:Around最好不要和其他四种同时使用

  把处理日志的代码放到一起就是切面,模块化(Aspect)

  5.Introduction(引入)
   1)Introduction的定义
  给正在运行的程序中的对象添加方法。
   其实是一种动态技术

  
2)问题:如何给ArithmeticCalculatorImp类加入

   求最大值和最小值的方法

  
假设:最大值和最小值的方法已经存在,且分别属于不同的类。

 extends MaxCalculatorImp, MinCalculatorImp
不行,java不允许多继承

   3)要点:

 a、实现一个POJO类

  定义@DeclareParents来"继承"的父类

        
@DeclareParents(

  value="myspring.calculator.ArithmeticCalculatorImp",

  defaultImpl=MaxCalculatorImp.class

 )

 private MaxCalculator maxCalculator;

 

 @DeclareParents(

   value="myspring.calculator.ArithmeticCalculatorImp",

   defaultImpl=MinCalculatorImp.class

  )

  private MinCalculator
minCalculator;   

 b、定义这个Bean:在Bean定义文件中声明一下。

        
<bean
class="myspring.calculator.CalculatorIntroduction"></bean>

   
在test类中测试时:ApplicationContext ac =

  new
ClassPathXmlApplicationContext("aop.xml");

  

  IArithmeticCalculator cal =
(IArithmeticCalculator)

  ac.getBean("arith");

  cal.add(2, 3);

  cal.div(4, 3);

  

  MaxCalculator max =
(MaxCalculator)cal;

  max.max(3, 6);

  MinCalculator min =
(MinCalculator)cal;

  min.min(3, 6);

 

spring之AOP
posted @ 2017-12-27 16:13  星朝  阅读(244)  评论(0编辑  收藏  举报