深入浅出 spring AOP 刑红瑞

深入浅出AOP

深入浅出 spring AOP (一)    源出处

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

package org.tatan.test;

public class MessageBean {

public void write() {

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

}

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

package org.tatan.test;

import org.aopalliance.intercept.MethodInterceptor;

import org.aopalliance.intercept.MethodInvocation;

public class MessageCode implements MethodInterceptor {

public Object 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()产生代理对象。

import org.springframework.aop.framework.ProxyFactory;

public class 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 (二)

有人问我,为什末使用CGLIB proxy而不使用JDK Dynamic Proxies,这和spring aop使用的原则相关。

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

package org.tatan.test;

public interface 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;

import java.lang.reflect.Method;

import org.aopalliance.intercept.MethodInterceptor;

 import org.aopalliance.intercept.MethodInvocation;

public class AroundAdvice 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());

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

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

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

        }

        System.out.println();

             return returnValue;

    }

}
运行

package org.tatan.test;

 import org.springframework.aop.framework.ProxyFactory;

public class 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});
        return (Worker) pf.getProxy();
    }
}

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

package org.tatan.test;

import org.springframework.aop.framework.ProxyFactory;

public class 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 -->

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

<property name="transactionManager"><ref bean="transactionManager"/></property>

<property name="transactionAttributeSource"><ref bean="txAttributes"/></property>

</bean>

<!-- Define transactional methods (NameMatchTransactionAttributeSource

applies specific attributes to methods that match to a pattern) -->

<bean id="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 -->

 

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

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

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

</bean>

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

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

<constructor-arg index="0"><ref bean="userController"/></constructor-arg>

</bean>

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

 

<bean id="MimeTarget" class="com。tatan.task.MimeTarget">

   <property name="sessionFactory"><ref bean="sessionFactory"/></property>       

 <property name="MimeDao"><ref bean="MimeDao"/></property>

</bean>
 

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

        <property name="transactionManager">

<ref bean="transactionManager"/></property>

        <property name="target"><ref local="MimeTarget"/></property>

        <property name="transactionAttributes">

            <props>

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

            </props>

        </property>

    </bean>
 

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

<property name="timerTask"><ref bean="MimeTargetProxyFactoryBean"/></property>

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

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

</bean>
 

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

 
深入浅出 spring AOP (四)

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

下面给个例子接口

package org.tatan.test;

 public interface 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(i);

        }

    }

}
advice

package org.tatan.test;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

import org.springframework.aop.framework.ProxyFactory;

import org.springframework.beans.factory.support.AbstractBeanFactory;

import org.springframework.beans.factory.xml.XmlBeanFactory;

import org.springframework.core.io.ClassPathResource;

import org.springframework.core.io.Resource;

public class SimpleBeforeAdvice implements MethodBeforeAdvice {

      public static void main(String[] args) {

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

        AbstractBeanFactory ft = new XmlBeanFactory(res);

        //Instantiate an object

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

        testObject.doSomeWork(100);

}

 public void before(Method method, Object[] args, Object target)

throws Throwable {    

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

    }

}
配置文件

<?xml version="1.0" encoding="UTF-8"?>

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

<beans>

 <bean id="businesslogicbean"

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

  <property name="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>

  <property name="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可以轻易做到。
配置文件

 <property name="password">

        <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">

        <property name="targetClass" >

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

         </property>

        <property name="targetMethod">

<value>decryptPassword</value>

            </property>

        <property name="arguments">

         <value>saflj</value>

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

        return new String("Iamstupid");

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

深入浅出 spring AOP (六)

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

Pointcut的定义
public interface Pointcut {

        ClassFilter getClassFilter ();  

MethodMatcher getMethodMatcher();
}

Pointcut interface只有两个方法,返回ClassFilter and 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">

        <property name="advice">

            <ref local="myAdvice"/>

        </property>

        <property name="mappedName">

            <value>getConnection</value>

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

        <property name="interceptorNames">

            <list>

                <value>myAdvisor</value>

            </list>

        </property>

        <property name="beanNames">

            <list>

                <value>dataSource</value>

            </list>

        </property>
    </bean>
 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值