Spring学习笔记(16)--------------Spring AOP总结(1)

来源:互联网 发布:安卓编程类游戏 编辑:程序博客网 时间:2024/05/21 22:39
1.AOP
 
AOP是Aspect-Oriented  Programming (面向方面编程或面向切面)的简称。

spring  AOP 使用了两种代理机制:
                                                1.基于JDK的动态代理。
                                                2.基于CGLib的动态代理。
这两种动态代理技术都是在运行期织入增强,所以我们不需要装备特殊的编译器或类装载器就可以使用AOP功能。
JDK的动态代理,目标类必须实现接口(InvocationHandler)。
CGLib动态代理,则不对目标类做任何限制,它通过动态生成目标类子类的方式提供代理。

JDK 和 CGLib两个动态代理实现区别:
JDK在创建代理对象时性能高于CGLib,而生成代理对象的运行性能却比CGLib低,如果是singeton的代理,推荐使用CGLib动态代理。

spring 只能在方法级别上织入增强,spring提供了4种类型的方法增强,分别是前置增强、后置增强、环绕增强和异常抛出增强,此外还有一种特殊的引介增强,引介增强是类级别的,它为目标类织入新的接口实现


Spring AOP通过Pointcut(切点)指定在那些类的那些方法上织入横切逻辑,通过Advice(增强)描述横切逻辑和方法的具体织入点(方法前、方法后、方法两端等)。Spring 通过Advisor(切面)将Poincut 和advice 两者组装起来。有了Advisor的信息,spring就可以利用JDK或CGLib的动态代理技术采用统一的方式为目标Bean创建织入切面的代理对象了。
 
2.增强
    Advice (通知)定义在连接点做什么,为切面增强提供织入接口。在spring AOP中,它主要描述Spring AOP围绕方法调用而注入的切面行为。

        具体接口定义在:
        

      接口的实现:
 
3.切点
Pointcut (切点) 决定advice 通知应该作用于那个连接点,也就是说通过Pointcut来定义需要增强方法的集合,这些集合的选取可以按照一定的规则来完成。

        在Pointcut的基本接口定义中可以看到,需要返回一个MethodMatcher,对于Pointde 匹配判断功能,具体是由这个返回的MethodMatcher来完成的,也就是说,由这个MethodMatcher来判断是否需要对当前方法调用进行增强,或者是否需要对当前调用方法应用配置好的Advice通知。


4.Advisor 通知器
 
  完成对目标方法的切面设计(Advice)和关注点的设计(Pointcut)以后,需要一个对象把他们结核起来,完成这个作用的就是Advisor(通知器)。

        通过Advisor,可以定义应该使用那个通知并在哪个关注点使用它,也就是说Advisor,把Advice和Pointcut结合起来,这个结合为使用IoC容器配置AOP应用,或者说即开即用地使用AOP基础设施,提供了便利。
 
5.AOP的增强类型
 
spring aop 增强类型支持5种:
  • 前置增强
           org.springframework.aop.BeforeAdvice  代表前置增强,因为spring只支持方法级的增强,所以MethodBeforeAdvice 是目前可用前置增强,表示在目标方法执行前实施增强。
  • 后置增强
           org.springframework.aop.AfterAdvice 代表后增强,表示目标方法在执行后实施增强 
  • 环绕增强
            org.springframework.aop.MethodInterceptor 代表环绕增强,表示目标方法执行前后实施增强
  • 异常抛出增强
            org.springframework.aop.ThrowsAdvice 代表抛出异常增强,表示目标方法抛出异常后实施增强
  • 引介增强
            org.springframework.aop.IntroductionInterceptor 代表引介增强,表示在目标类中添加一些新的方法和属性
 
6.前置增强的实现
1.创建一个接口类
public interface Waiter { void greetTo(String name); void serveTo(String name);}

2.创建接口实现类
public class NaiveWaiter implements Waiter { @Override public void greetTo(String name) {  System.out.println("greet to "+name+"..."); } @Override public void serveTo(String name) {  System.out.println("serving "+name+"..."); }}

4.实现前置增强接口
public class GreetingBeforAdvice implements MethodBeforeAdvice { /**  * method 为目标类的方法  *  * args 为目标类的入参  *  * obj 为目标类的实例  */ @Override public void before(Method method, Object[] args, Object target)   throws Throwable {  String clientName = (String) args[0];  System.out.println("How are you! Mr " + clientName + ".");  // System.out.println(target.getClass().getName()); }}


5.advisor 上场
public class TestBeforAdvice {  public static void main(String[] args) {  Waiter target=new NaiveWaiter();   BeforeAdvice beforeAdvice=new GreetingBeforAdvice();   //spring 提供的代理工厂  ProxyFactory pf=new ProxyFactory();  //设置代理目标  pf.setTarget(target);  //为代理目标添加增强  pf.addAdvice(beforeAdvice);  //生成代理实例  Waiter waiter=(Waiter) pf.getProxy();  waiter.greetTo("John");  waiter.serveTo("Tom"); }}

main方法运行结果:
 
7.JDK的动态代理特性
 
在JDK1.3以上版本,实现了动态代理模式。通过JDK的动态代理特性,可以为任意java对象创建代理对象,对于具体使用来说,这个特性是通过java Reflection API 来完成的。

Proxy原理:
        Proxy的调用过程中,如果客户(Client)调用Proxy的request方法,会在调用目标对象的request方法的前后调用一系列的处理,而这些一系列的处理相对于目标对象来说是透明的,目标对象对这些处理可以毫不知情,这就是Proxy模式。

        JDK中的proxy代理需要实现InvocationHandler接口。
        
            三个参数解析:
                        1.代理对象实例
                        2.method方法对象,代表的是当前Proxy被调用的方法
                        3.被调用的方法中的参数

jdk动态代理实现,实现InvocationHandler  接口,代码如下:

jdk的动态代理是基于接口模式下的代理。
        步骤:
  1.定义接口,如下实例:
                     
  public interface UserManagement {     void addUser(String name,int age);     String deleteUser(String...str);  }

  2.接口类的实现。如下代码:
                       
 import com.yc.jdk.proxy.UserManagement;    public class UserManagementImpl implements UserManagement {        public void addUser(String name, int age) {          System.out.println("add");        }        public String deleteUser(String... str) {          System.out.println("delete");          return "delete";        }}3.继承jdk中的代理类InvocationHandlerimport java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class CheckSecurity implements InvocationHandler {//声明目标类private Object targetObject;/*** 传入要代理的对象* * @param targetObject* @return object*/public Object CheckSecurity(Object targetObject) {this.targetObject = targetObject;return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(),                   this.targetObject.getClass().getInterfaces(), this);}//模拟 环绕代理方法public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {        checkseiurity();        Object chckObject = null;        // 获取方法名称        p("method name value ", method.getName());        // 取得此方法是不是可变参数        p("method is var args ", method.isVarArgs());        System.out.println(args.length);        try {           chckObject = method.invoke(this.targetObject, args);        } catch (RuntimeException e) {            e.printStackTrace();        }        checkseiurity();        return chckObject;}
 
private void checkseiurity() {     System.out.println("--------------check----------------"); }
 
public void p(Object... object) {  for (int i = 0; i < object.length; i++) {  System.out.print(object[i].toString());  }// 产生换行   System.out.println(); }}4.测试jdk动态代理import java.lang.reflect.Proxy;import com.yc.jdk.proxy.impl.UserManagementImpl;public class JdkPorxy {  public static void main(String[] args) throws Exception {   CheckSecurity checkSecurity = new CheckSecurity();   UserManagement management = (UserManagement) checkSecurity.CheckSecurity(new UserManagementImpl());   p(Proxy.isProxyClass(UserManagement.class)); // management.addUser("张三", 12);   management.deleteUser("张三", "李四", "王五", "赵六");  }   public static void p(Object object) {    System.out.println(object.toString());  }}



运行结果:
false
--------------check----------------
method name value deleteUser
method is var args true
1
delete
--------------check----------------
 
8.剖析ProxyFactory
 

Spring 定义了org.springframework.aop.framework.AopProxy接口,并提供了两个final类型实现类


 ProxyFactory的setInterfaces(Class[] Interfaces)指定只对接口进行代理,ProxyFactory就是用JdkDynamicAopProxy;如果是针对类型的代理,则使用Cglib2AopProxy 
      另外ProxyFactory的setOptimize(true)方法,让ProxyFactory启动优化代理方式,这样,针对接口的代理也会使用Cglib2AopProxy 。


import org.springframework.aop.BeforeAdvice;import org.springframework.aop.framework.ProxyFactory;import com.web.spring.aop.example.beforadvice.GreetingBeforAdvice;import com.web.spring.aop.example.beforadvice.NaiveWaiter;import com.web.spring.aop.example.beforadvice.Waiter;/** * @author Chris * */public class TestProxyFactory { public static void main(String[] args) {  //cglibProxyMethod();  jdkProxyMethod(); } public static void jdkProxyMethod() {  Waiter target = new NaiveWaiter();  BeforeAdvice beforeAdvice = new GreetingBeforAdvice();  ProxyFactory proxyFactory = new ProxyFactory();  // 指向jdk代理  proxyFactory.setInterfaces(target.getClass().getInterfaces());  proxyFactory.setTarget(target);  proxyFactory.addAdvice(beforeAdvice);  create(proxyFactory); } public static void cglibProxyMethod() {  Waiter target = new NaiveWaiter();  BeforeAdvice beforeAdvice = new GreetingBeforAdvice();  ProxyFactory proxyFactory = new ProxyFactory();  //强制使用cglib代理  proxyFactory.setOptimize(true);   proxyFactory.setTarget(target);  proxyFactory.addAdvice(beforeAdvice);  create(proxyFactory); } private static void create(ProxyFactory proxyFactory) {  Waiter waiter = (Waiter) proxyFactory.getProxy();  waiter.greetTo("John");  waiter.serveTo("Tom"); }}

 
9.ProxyFactory的XML的配置
 
在spring中配置,将所有的xml贴出来了,以及proxyFactory 所使用的一些属性解释:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="  http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans-4.0.xsd  http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context-4.0.xsd  http://www.springframework.org/schema/util  http://www.springframework.org/schema/util/spring-util-4.0.xsd  http://www.springframework.org/schema/tx  http://www.springframework.org/schema/tx/spring-tx-4.0.xsd  http://www.springframework.org/schema/aop  http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">  <bean id="greetingAdvice" class="com.web.spring.aop.example.beforadvice.GreetingBeforAdvice"/> <bean id="target" class="com.web.spring.aop.example.beforadvice.NaiveWaiter"/> <!--ProxyFactoryBean 是FactoryBean接口的实现类 --> <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"  p:proxyInterfaces="com.web.spring.aop.example.beforadvice.Waiter"  p:interceptorNames="greetingAdvice"  p:target-ref="target"  p:proxyTargetClass="true" /><!-- 1.proxyInterfaces代理所要实现的接口,可以是多个接口。该属性还有一个别名interfaces 2.interceptorNames需要织入目标对象的bean列表,采用Bean的名称指定。这些Bean必须实现了  org.aopalliance.intercept.MethodInterceptor或  org.springframework.aop.Advisor 的Bean,配置中的顺序对应调用的顺序。 3.singleton 返回的代理是否是简单实例,默认为单实例; 4.optimize 当设置成true 时,强制使用Cglib代理。 5.proxyTargetClass 是否对类进行代理(而不是对接口进行代理),设置为true时,使用CGLib代理。 --></beans>


此外,在xml配置文件中proxyTargetClass 设置为true后,无需再设置proxyInterfaces属性,即使设置也会被ProxyFactoryBean忽略。

main方法测试:

 public static void main(String[] args) {
  ApplicationContext applicationContext=new ClassPathXmlApplicationContext("proxyFcatory.xml");
 
  Waiter waiter = (Waiter) applicationContext.getBean("waiter");
 
  waiter.greetTo("John");
 }
 
11.CGlib动态代理
 
springaopjdkstringjavaobject
代理的核心就是拦截方法调用,并在需要的时候执行匹配某方法的通知链。 

和CGLIB不同的是,JDK代理只能代理接口,不能代理类。 

使用JDK代理时,如何处理一个特定的方法调用的决定是在程序运行时做出的,也就是在每次方法被调用时。使用CGLIB代理可以边开这种处理方法,CGLIB会在运行中随时为代理创建新类的字节码,并尽可能的重用已经生成的类的字节码。 


Spring1.2:
将事务代理工厂[TransactionProxyFactoryBean] 或 自动代理拦截器[BeanNameAutoProxyCreator]
的 proxyTargetClass 属性,设置为true,则使用CGLIB代理,此属性默认为false,使用JDK动态代理.
以下引用 Spring Framework reference 2.0.5:
Spring2.0:

Spring AOP部分使用JDK动态代理或者CGLIB来为目标对象创建代理。(建议尽量使用JDK的动态代理)

如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理。所有该目标类型实现的接口都将被代理。若该目标对象没有实现任何接口,则创建一个CGLIB代理。

如果你希望强制使用CGLIB代理,(例如:希望代理目标对象的所有方法,而不只是实现自接口的方法)那也可以。但是需要考虑以下问题:

无法通知(advise)Final 方法,因为他们不能被覆写。 
你需要将CGLIB 2二进制发行包放在classpath下面,与之相较JDK本身就提供了动态代理 
强制使用CGLIB代理需要将 |aop:config| 的 proxy-target-class 属性设为true:

|aop:config proxy-target-class="true"|
...
|/aop:config|

当需要使用CGLIB代理和@AspectJ自动代理支持,请按照如下的方式设置 |aop:aspectj-autoproxy| 的 proxy-target-class 属性:

|aop:aspectj-autoproxy proxy-target-class="true"/|

而实际使用的过程中才会发现细节问题的差别,The devil is in the detail.JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。
CGLIB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的,性能比JDK强。
Spring是依靠什么来判断采用哪种代理策略来生成AOP代理呢?以下代码就是Spring的判断逻辑  

  

  //org.springframework.aop.framework.DefaultAopProxyFactory       //参数AdvisedSupport 是Spring AOP配置相关类       public AopProxy createAopProxy(AdvisedSupport advisedSupport)               throws AopConfigException {           //在此判断使用JDK动态代理还是CGLIB代理           if (advisedSupport.isOptimize() || advisedSupport.isProxyTargetClass()                   || hasNoUserSuppliedProxyInterfaces(advisedSupport)) {               if (!cglibAvailable) {                   thrownew AopConfigException(                           "Cannot proxy target class because CGLIB2 is not available. "                                  +"Add CGLIB to the class path or specify proxy interfaces.");                 }               return CglibProxyFactory.createCglibProxy(advisedSupport);             } else {               returnnew JdkDynamicAopProxy(advisedSupport);             }         }  



advisedSupport.isOptimize()与advisedSupport.isProxyTargetClass()默认返回都是false,所以在默认情况下目标对象有没有实现接口决定着Spring采取的策略,当然可以设置advisedSupport.isOptimize()或者advisedSupport.isProxyTargetClass()返回为true,这样无论目标对象有没有实现接口Spring都会选择使用CGLIB代理。

所以在默认情况下,如果一个目标对象如果实现了接口Spring则会选择JDK动态代理策略动态的创建一个接口实现类(动态代理类)来代理目标对象,可以通俗的理解这个动态代理类是目标对象的另外一个版本,所以这两者之间在强制转换的时候会抛出java.lang.ClassCastException。而所以在默认情况下,如果目标对象没有实现任何接口,Spring会选择CGLIB代理, 其生成的动态代理对象是目标类的子类。

上说的是默认情况下,也可以手动配置一些选项使Spring采用CGLIB代理。 
org.springframework.transaction.interceptor.TransactionProxyFactoryBean是org.springframework.aop.framework. ProxyConfig的子类,所以可以参照ProxyConfig里的一些设置如下所示,将optimize和proxyTargetClass任意一个设置为true都可以强制Spring采用CGLIB代理。

如果当需要使用CGLIB代理和@AspectJ自动代理支持,请按照如下的方式设置 |aop:aspectj-autoproxy| 的 proxy-target-class 属性: 
|aop:aspectj-autoproxy proxy-target-class="true"/|

这样使用CGLIB代理也就不会出现前面提到的ClassCastException问题了,也可以在性能上有所提高,关键是对于代理对象是否继承接口可以统一使用。


两种类型AOP:静态AOP和动态AOP。 

静态代理: 
代理对象与被代理对象必须实现同一个接口。
 
demo:

view plaincopy to clipboardprint?
  1. package cn.partner4java.proxy.staticproxy;  
  2.   
  3.   
  4. /** 
  5. *  静态代理,统一接口 
  6. * @author partner4java 
  7. * 
  8. */  
  9. public interface IHello {  
  10.     /** 
  11.      * 可以带来的统一方法 
  12.      * @param name 
  13.      */  
  14.     public void hello(String name);  
  15. }  
view plaincopy to clipboardprint?
  1. package cn.partner4java.proxy.staticproxy;  
  2.   
  3. /** 
  4. *  被代理的对象,需要借助代理对象加入日志 
  5. * @author partner4java 
  6. * 
  7. */  
  8. public class HelloSpeaker implements IHello {  
  9.   
  10.     public void hello(String name) {  
  11.         System.out.println("Hello " + name);  
  12.     }  
  13.   
  14. }  
view plaincopy to clipboardprint?
  1. package cn.partner4java.proxy.staticproxy;  
  2.   
  3. /** 
  4. *  代理对象,给被代理对象添加日志 
  5. */  
  6. public class HelloProxy implements IHello {  
  7.       
  8.     private IHello iHello;  
  9.   
  10.     public HelloProxy(IHello iHello) {  
  11.         super();  
  12.         this.iHello = iHello;  
  13.     }  
  14.   
  15.   
  16.     public void hello(String name) {  
  17.         System.out.println("记录日志");  
  18.         iHello.hello(name);  
  19.     }  
  20.   
  21. }  
view plaincopy to clipboardprint?
  1. package cn.partner4java.proxy.staticproxy;  
  2.   
  3. /** 
  4. *  调用 
  5. * @author partner4java 
  6. * 
  7. */  
  8. public class ProxyDemo {  
  9.   
  10.     public static void main(String[] args) {  
  11.         IHello iHello = new HelloProxy(new HelloSpeaker());  
  12.         iHello.hello("long");  
  13.     }  
  14.   
  15. }  





动态代理: 
动态代理区别于静态带来实现的地方在于织入过程是在运行时动态进行的。自己实现一般实现java.lang.reflect.InvocationHandler接口。 
例子:
view plaincopy to clipboardprint?
  1. package cn.partner4java.proxy.dynamicproxy;  
  2.   
  3.   
  4. public interface IHello {  
  5.     public void hello(String name);  
  6. }  
view plaincopy to clipboardprint?
  1. package cn.partner4java.proxy.dynamicproxy;  
  2.   
  3. /** 
  4. *  被代理的对象,需要借助代理对象加入日志 
  5. * @author partner4java 
  6. * 
  7. */  
  8. public class HelloSpeaker implements IHello {  
  9.   
  10.     public void hello(String name) {  
  11.         System.out.println("Hello " + name);  
  12.     }  
  13.   
  14. }  
view plaincopy to clipboardprint?
  1. package cn.partner4java.proxy.dynamicproxy;  
  2.   
  3. import java.lang.reflect.InvocationHandler;  
  4. import java.lang.reflect.Method;  
  5. import java.lang.reflect.Proxy;  
  6.   
  7. /** 
  8. *  动态代理对象 
  9. * @author partner4java 
  10. * 
  11. */  
  12. public class LogHandler implements InvocationHandler {  
  13.   
  14.     private Object delegate;  
  15.       
  16.     public Object bind(Object delegate){  
  17.         this.delegate = delegate;  
  18.         return Proxy.newProxyInstance(delegate.getClass().getClassLoader(),   
  19.                 delegate.getClass().getInterfaces(), this);  
  20.     }  
  21.     /** 
  22.      * 代理对象,这里面还可以改变原有的方法 
  23.      */  
  24.     public Object invoke(Object proxy, Method method, Object[] args)  
  25.             throws Throwable {  
  26.         Object result = null;  
  27.         try {  
  28.             System.out.println("添加日志");  
  29.             result = method.invoke(delegate, args);  
  30.         } catch (Exception e) {  
  31.             e.printStackTrace();  
  32.         }  
  33.           
  34.         return null;  
  35.     }  
  36.   
  37. }  
view plaincopy to clipboardprint?
  1. package cn.partner4java.proxy.dynamicproxy;  
  2.   
  3. /** 
  4. *  测试 
  5. * @author partner4java 
  6. * 
  7. */  
  8. public class ProxyDemo {  
  9.     public static void main(String[] args) {  
  10.         LogHandler logHandler = new LogHandler();  
  11.         IHello iHello = (IHello) logHandler.bind(new HelloSpeaker());  
  12.         iHello.hello("long");  
  13.     }  
  14. }  


------------------------------------------------------------------ 


利用ProxyFactory连接CGLIB简单实现AOP: 
加入包aopalliance.jar\cglib-nodep-2.1_3.jar 
demo:
view plaincopy to clipboardprint?
  1. package cn.partner4java.proxy.proxyfactory;  
  2.   
  3. /** 
  4. *  被代理的对象 
  5. * @author partner4java 
  6. * 
  7. */  
  8. public class MessageWriter {  
  9.     public void writeMessage(){  
  10.         System.out.println("world!");  
  11.     }  
  12. }  
view plaincopy to clipboardprint?
  1. package cn.partner4java.proxy.proxyfactory;  
  2.   
  3. import org.aopalliance.intercept.MethodInterceptor;  
  4. import org.aopalliance.intercept.MethodInvocation;  
  5.   
  6. /** 
  7. *  装饰者<br/> 
  8. * MethodInterceptor接口是对方法调用连接点实现包围通知的AOP联盟标准接口 
  9. * @author partner4java 
  10. * 
  11. */  
  12. public class MessageDecorator implements MethodInterceptor{  
  13.   
  14.     public Object invoke(MethodInvocation invocation) throws Throwable {  
  15.         System.out.print("Hello ");  
  16.         Object retVal = invocation.proceed();  
  17.         return retVal;  
  18.     }  
  19.   
  20. }  
view plaincopy to clipboardprint?
  1. package cn.partner4java.proxy.proxyfactory;  
  2.   
  3. import org.springframework.aop.framework.ProxyFactory;  
  4.   
  5. /** 
  6. *  调用组装 
  7. *  这里最重要的部分是我们使用ProxyFactory来创建一个目标对象代理,同时织入通知  
  8. * @author partner4java 
  9. * 
  10. */  
  11. public class HelloWorldWeaver {  
  12.   
  13.     public static void main(String[] args) {  
  14.         //目标  
  15.         MessageWriter target = new MessageWriter();  
  16.           
  17.         //create the proxy  
  18.         ProxyFactory proxyFactory = new ProxyFactory();  
  19.           
  20.         proxyFactory.addAdvice(new MessageDecorator());  
  21.         proxyFactory.setTarget(target);  
  22.           
  23.         //获取返回被代理的目标  
  24.         MessageWriter proxy = (MessageWriter) proxyFactory.getProxy();  
  25.           
  26.         target.writeMessage();  
  27.         System.out.println("---");  
  28.         proxy.writeMessage();  
  29. //      后台打印:  
  30. //      world!  
  31. //      ---  
  32. //      World world!  
  33.     }  
  34.   
  35. }  
 
总结完毕!
 
 
 

 
        
 
 
1 0
原创粉丝点击