深入浅出 spring AOP 刑红瑞

来源:互联网 发布:mac终端查看当前路径 编辑:程序博客网 时间:2024/06/07 09:55
深入浅出AOP

深入浅出 springAOP (一)    源出处

先不讨论AOP的各种名词,也不作各种AOP的比较,我将在例子中介绍各种名词。
1。先写一个javabean,就是target object。

packageorg.tatan.test;

publicclass MessageBean {

public void write() {

        System.out.print("AOP example");
     }

}

2。写一个AOP的advice类MethodInterceptor是AOP联盟的标准接口,它是最简单最实用的连接点(joinpoint),实现了aroundadvice ,你可以在他返回前调用target的方法。

packageorg.tatan.test;

importorg.aopalliance.intercept.MethodInterceptor;

importorg.aopalliance.intercept.MethodInvocation;

publicclass MessageCode implements MethodInterceptor {

publicObject invoke(MethodInvocation invocation) throws Throwable {

   System.out.print("this is a ");

       Object returnValue = invocation.proceed();

       return returnValue ;

   }

}

3。把MessageCode advice weave 到proxy factory,proxy factory是整个架构的核心
先创建instance of MessageBean,然后创建代理的instance ,MessageCode advice 传递给的
addAdvice()方法,设置Target Object,调用getProxy()产生代理对象。

importorg.springframework.aop.framework.ProxyFactory;

publicclass AOPExample {

 public static void main(String[] args) {

         MessageBean target = new MessageBean();

         ProxyFactory pf = new ProxyFactory();

        pf.addAdvice(new essageCode()); 

     pf.setTarget(target);

        MessageBean proxy = (MessageBean) pf.getProxy();

        //输出原始信息

        target.write();

        //输出代理对象调用的信息

        proxy.write();    

 }
}

4。classpath中加入cglib-nodep-2.1_2.jar ,spring.jar,aopalliance.jar,commons-logging.jar
结果
AOP example
this is a AOP example

深入浅出 spring AOP (二)

有人问我,为什末使用CGLIBproxy而不使用JDKDynamic Proxies,这和spring aop使用的原则相关。

1.      使用AOP的时候,尽可能的使用接口,而不是使用具体的类,这样就可以使用JDK Dynamic Proxies,如果目标类没有实现接口,spring使用CGLIB生成目标类的子类。下面给个例子接口类

packageorg.tatan.test;

publicinterface Worker {

       void doSomeWork(int numOfTimes);

   }

  

目标类
package org.tatan.test;

 public class WorkerBean implements Worker {

   public void doSomeWork(int numOfTimes) {

       for (int i = 0; i < numOfTimes; i++) {

System.out.print("");

       }

   }

}

Advice执行流程
package org.tatan.test;

importjava.lang.reflect.Method;

importorg.aopalliance.intercept.MethodInterceptor;

 importorg.aopalliance.intercept.MethodInvocation;

publicclass AroundAdvice implements MethodInterceptor {

publicObject invoke(MethodInvocation invocation) throws Throwable {

       Object returnValue = invocation.proceed();

       Method m = invocation.getMethod();

       Object target = invocation.getThis();

       Object[] args = invocation.getArguments();

       System.out.println("Executed method: " + m.getName());

       System.out.println("On object of type: " + target.getClass().getName());

       System.out.println("With arguments:");

        for (inti=0;i<args.length;i++) {

           System.out.println("---->" + args[i]);

       }

       System.out.println();

            return returnValue;

   }

}
运行

packageorg.tatan.test;

 importorg.springframework.aop.framework.ProxyFactory;

publicclass AOPExample2 {

   public static void main(String[] args) {

       Worker bean = getWorkerBean();

       bean.doSomeWork(100000000);

   }

   private static Worker getWorkerBean() {
        WorkerBean target = newWorkerBean();
        ProxyFactory pf = newProxyFactory();
        pf.setTarget(target);
        pf.addAdvice(new AroundAdvice());
        pf.setInterfaces(newClass[]{Worker.class});
        return (Worker) pf.getProxy();
    }
}

如果调用了setInterfaces();就不要cglib了,使用JDKDynamic Proxies,只是使用JDKDynamic Proxies程序执行的效率比较低。
使用CGLIB的Frozen效率比标准的CGLIB效率高。

packageorg.tatan.test;

importorg.springframework.aop.framework.ProxyFactory;

publicclass AOPExample2 {

   public static void main(String[] args) {

       Worker bean = getWorkerBean();

       bean.doSomeWork(100000000);

   }

   private static Worker getWorkerBean() {

       WorkerBean target = new WorkerBean();

       ProxyFactory pf = new ProxyFactory();

       pf.setTarget(target);

       pf.addAdvice(new AroundAdvice());

    //  pf.setInterfaces(new Class[]{Worker.class});

       pf.setFrozen(true);

       return (Worker) pf.getProxy();

   }

}
 

原则上使用CGLIB,因为既可以使用类,还可以使用接口,JDK proxy 只能代理口。
2.目标类的方法不能是final的,因为spring要生成目标类的子类,任何要advised的方法都要overide,所以不允许final method。

 
深入浅出 spring AOP (三)

spring AOP使用,使用CGLIB应该使用接口不是类,这点务必注意。
使用BeanNameAutoProxyCreator声明事务,

<!--define transaction interceptor -->

<beanid="txInterceptor"class="org.springframework.transaction.interceptor.TransactionInterceptor">

<propertyname="transactionManager"><refbean="transactionManager"/></property>

<propertyname="transactionAttributeSource"><refbean="txAttributes"/></property>

</bean>

<!--Define transactional methods (NameMatchTransactionAttributeSource

appliesspecific attributes to methods that match to a pattern) -->

<beanid="txAttributes"class="org.springframework.transaction.interceptor.

NameMatchTransactionAttributeSource">
<property name="properties">

<value>add*=PROPAGATION_REQUIRED  update*=PROPAGATION_REQUIRED delete*=PROPAGATION_REQUIRED
</value>
</property>
</bean>

<!--使用Autoproxy定义事务的beans,应用于Controllers-->

 

<beanid="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">

<propertyname="interceptorNames"><value>txInterceptor</value></property>

<propertyname="beanNames"><value>*Controller</value></property>

</bean>

<!--事务管理的beans-->

<beanid="userManager" class="com.tatan.domain.user.UserManager"singleton="true" dependency-check="objects">

<constructor-argindex="0"><refbean="userController"/></constructor-arg>

</bean>

spring文档说明“when BeanNameAutoProxyCreator postprocesses the targetobject and create the proxy, it causes the proxy to be inserted into theApplication context under the name of the original bean”,
UserController 应该是个接口,而不是类,默认情况下,spring使用dynamicproxies,它只使用接口。如果使用CGLIB,最好proxyTargetClass设为true。
使用TransactionProxyFactoryBean也是如此

 

<beanid="MimeTarget" class="com。tatan.task.MimeTarget">

   <propertyname="sessionFactory"><refbean="sessionFactory"/></property>       

 <propertyname="MimeDao"><refbean="MimeDao"/></property>

</bean>
 

<beanid="MimeTargetProxyFactoryBean" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

       <property name="transactionManager">

<refbean="transactionManager"/></property>

       <property name="target"><reflocal="MimeTarget"/></property>

       <property name="transactionAttributes">

           <props>

               <prop key="insert*">PROPAGATION_REQUIRED</prop>

           </props>

       </property>

    </bean>
 

<beanid="MimeTrigger"class="org.springframework.scheduling.timer.ScheduledTimerTask">

<propertyname="timerTask"><refbean="MimeTargetProxyFactoryBean"/></property>

<propertyname="delay"><value>1000</value></property>

<propertyname="period"><value>500000</value></property>

</bean>
 

这样会抛出异常,exceptionorg.springframework.beans.TypeMismatchException: Failed to convert propertyvalue of type [$Proxy0] to required type [java.util.TimerTask] for property'timerTask'; nested exception is java.lang.IllegalArgumentException: argumenttype mismatch
TimerTask是个类,只能用CGLIB产生代理,TransactionProxyFactoryBean默认使用接口来产生target object,将proxyTargetClass = true即可使用CGLIBproxy代理。

 
深入浅出 spring AOP (四)

spring的Advice分为5种,Before,After returning,Around,Throws,Introduction。使用这些Advice可以完成AOP相关部分90%的编码,剩余的10%只好依靠AspectJ了。在大多数情况下,around advice可以完成Before,After returning,Throws的所有功能。Beforeadvice是比较有用的advice,它可以修改传递给method的参数,可以通过异常中断method的执行,通常用于检测用户的权限。Servlet过滤器是Beforeadvice的一种方式,提供了在servlet调用前执行其他处理的能力。

下面给个例子接口

packageorg.tatan.test;

 publicinterface Worker {

voiddoSomeWork(int numOfTimes);

 }
类文件

packageorg.tatan.test;

publicclass WorkerBean implements Worker {

   public void doSomeWork(int numOfTimes) {

       for (int i = 0; i < numOfTimes; i++) {

           System.out.print(i);

       }

   }

}
advice

packageorg.tatan.test;

importjava.lang.reflect.Method;

importorg.springframework.aop.MethodBeforeAdvice;

importorg.springframework.aop.framework.ProxyFactory;

importorg.springframework.beans.factory.support.AbstractBeanFactory;

importorg.springframework.beans.factory.xml.XmlBeanFactory;

importorg.springframework.core.io.ClassPathResource;

importorg.springframework.core.io.Resource;

publicclass SimpleBeforeAdvice implements MethodBeforeAdvice {

     public static void main(String[] args) {

    Resource res = newClassPathResource("org/tatan/test/bean2.xml");

       AbstractBeanFactory ft = new XmlBeanFactory(res);

       //Instantiate an object

       Worker testObject = (Worker) ft.getBean("businesslogicbean");

       testObject.doSomeWork(100);

}

 publicvoid before(Method method, Object[] args, Object target)

throws Throwable {    

    System.out.println("Beforemethod: " + method.getName());

   }

}
配置文件

<?xmlversion="1.0" encoding="UTF-8"?>

<!DOCTYPEbeans PUBLIC  "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

 <beanid="businesslogicbean"

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

  <propertyname="proxyTargetClass">

   <value>true</value>
  </property>
  <property name="singleton">
   <value>false</value>
  </property>
  <property name="exposeProxy">
   <value>true</value>
  </property>
  <property name="proxyInterfaces">
   <value>org.tatan.test.Worker</value>
  </property>
  <property name="target">
   <ref local="beanTarget" />
  </property>

  <propertyname="interceptorNames">

   <list>
    <value>BeforeAdvice</value>
<value>loggingInterceptor</value>
</list>
</property>
 </bean>

 <bean id="BeforeAdvice"class="org.tatan.test.SimpleBeforeAdvice" />

 <bean id="loggingInterceptor"

  class="org.springframework.aop.interceptor.TraceInterceptor"/>

 <bean id="beanTarget"class="org.tatan.test.WorkerBean" />

</beans>

Spring使用TraceInterceptor,你可以把她添加到你的代理bean中当作一个拦截器。TransactionProxyFactoryBean有"preInterceptors"和"postInterceptors"属性,ProxyFactoryBean只有"interceptorNames"属性,这和springlive第九章有点出入,log4j配置文件,详见我的blog(http://blogger.org.cn/blog/more.asp?name=hongrui&id=7968#11881)
log4j.properties

log4j.rootLogger=INFO,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] -<%m>%n

log4j.logger.org.springframework.aop.interceptor.TraceInterceptor=DEBUG
commons-logging.properties
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4Jlogger
写这篇blog的时候,得到竹十一的大力协助,在此表示感谢。
深入浅出 spring AOP (五)
在spring的配置文件中,数据库密码是明文的,如何保护你的数据库密码,使用spring的MethodInvokingFactoryBean可以轻易做到。
配置文件

 <propertyname="password">

        <beanclass="org.springframework.beans.factory.config.MethodInvokingFactoryBean">

        <propertyname="targetClass" >

          <value>com.tatan.util.XxxUtil</value>

         </property>

        <propertyname="targetMethod">

<value>decryptPassword</value>

           </property>

        <propertyname="arguments">

        <value>saflj</value>

        </property>
      </bean>
    </property>
java类
package com.tatan.util;
public class XxxUtil {
public static String decryptPassword(String password)
    {

        return newString("Iamstupid");

    }
}
注意 decryptPassword必须是static,参数可以是List,或Map

深入浅出 springAOP (六)

前面的几个例子都是拦截所有类的所有方法,但是我们主要是拦截某些类的某些方法,使用Pointcut可以做到。pointcut是一系Joinpoint的集合,它定义了需要注入advice的位置。AOP框架必须允许开发者指定切入点,advisor是pointcut和advice的装配器,是将advice织入预定义位置的代码中。

Pointcut的定义
public interface Pointcut {

        ClassFilter getClassFilter();  

MethodMatcher getMethodMatcher();
}

Pointcut interface只有两个方法,返回ClassFilterand MethodMatcher的实例。ClassFilter接口被用来将切入点限制到一个给定的目标类的集合。 如果matches()永远返回true,所有的目标类都将被匹配。

 public interface ClassFilter {
boolean matches(Class clazz);
}
MethodMatcher接口如下:
public interface MethodMatcher {
    boolean matches(Method m, Class targetClass);
    boolean isRuntime();
boolean matches(Method m, Class targetClass, Object[] args);
 }

matches(Method, Class) 方法被用来测试这个切入点是否匹配目标类的给定方法。这个测试可以在AOP代理创建的时候执行,避免在所有方法调用时都需要进行 测试。如果2个参数的匹配方法对某个方法返回true,并且MethodMatcher的isRuntime()也返回true,那么3个参数的匹配方法将在每次方法调用的时候被调用。这使切入点能够在目标通知被执行之前立即查看传递给方法调用的参数。

大部分MethodMatcher都是静态的,意味着isRuntime()方法 返回false。这种情况下3个参数的匹配方法永远不会被调用。
如果可能,尽量使用静态切入点。spring提供Pointcut interface的7种实现,详见它的文档。

在使用Pointcut之前,必须先创建Advisor或指定PointcutAdvisor。一个advisor就是一个aspect的完整的模块化表示,一个advisor应该包括通知和切入点。Spring中很多内建的切入点都有对应的PointcutAdvisor,DefaultPointcutAdvisor是最通用的advisor类,它可以和ethodInterceptor、 BeforeAdvice或者ThrowsAdvice一起使用。在应用较小时,只有很少类需要被切入时,ProxyFactoryBean可以使用。当有许多类需要被切入时,为每个代理创建ProxyFactoryBean就显得很繁琐。可以通过容器来创建代理。Spring提供了两个类实现自动代理:BeanNameAutoProxyCreator和DefaultAdvisorAutoProxyCreator。BeanNameAutoProxyCreator为匹配名字的Bean产生代理,它可以使用在将一个或者多个aspect应用在命名相似的Bean中。

自动代理框架假设代理将要暴露出什么接口。如果目标Bean没有实现任何接口,这时就会动态产生一个子类。
最有效自动代理是DefaultAdvisorAutoProxyCreator,你只需要在BeanFactory中包含它的配置。他使用实现了BeanPostProcessor接口。在Bean定义被加载倒Spring容器中后,DefaultAdvisorAutoProxyCreator将搜索配置文件中的Advisor,最后它将Advisor应用到匹配Advisor切入点的Bean中。
注意:这个代理只对Advisor起作用,它需要通过Advisor来得到需要通知的Bean。
还有元数据自动代理(MetaData AutoProxy),元数据自动代理配置依赖于源代码属性而不是外部XML配置文件。这可以非常方便的使源代码和AOP元数据组织在同一个地方。元数据自动代理最常用的地方是用来声明事务。
下面举个非常有用的例子,拦截DataSource的getConnection,用于查看数据连接的处理advisor类
package com.tatan;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.lang.reflect.Method;
public class MyAdvice  implements MethodInterceptor {

   public Object invoke(MethodInvocation invocation)throws Throwable {

        Object returnValue =invocation.proceed(); 

        Method m =invocation.getMethod();

        Object target =invocation.getThis();

        Object[] args =invocation.getArguments();

       System.out.println("Executed method: " + m.getName());

       System.out.println("On object of type: " +target.getClass().getName());

        if(args!=null)
        {
for (int i=0;i<args.length;i++) {

           System.out.println("---->" + args[i]);

        }
        }
        return returnValue;
    }
}

 <bean id="myAdvice"class="com.tatan.MyAdvice"/>

<bean id="myAdvisor"

         class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">

        <propertyname="advice">

           <ref local="myAdvice"/>

        </property>

        <propertyname="mappedName">

           <value>getConnection</value>

        </property>
    </bean>
<bean id="dataSourceProxy" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">

        <propertyname="interceptorNames">

           <list>

               <value>myAdvisor</value>

           </list>

        </property>

        <propertyname="beanNames">

           <list>

               <value>dataSource</value>

           </list>

        </property>
    </bean>