Spring AOP和动态代理

来源:互联网 发布:同步备份软件 编辑:程序博客网 时间:2024/06/05 19:52

Spring AOP是Spring的一项很强大的功能。它的“面向切面编程”的思想是面向对象编程OOP的补充和完善。AOP允许我们从横向的角度关注我们的程序,使得程序在安全处理,日志,性能采集等代码能在一个地方统一进行处理,而不用分布在各个方法和调用点。

这些代码集合在一起就称为“切面”。
连接点:被拦截到的点,Spring只支持方法类型的连接点,所以在Spring中,连接点就是指被拦截到的方法。
通知:拦截到连接点之后的要通知的代码。通知分为前置、后置、异常、最终、环绕五种。

此处省略了一些定义,个人感觉这些定义很绕,其实是一件很简单的事:
哪些方法需要被处理?
在方法的什么地方处理?
做哪些处理?

动态代理和Spring AOP有着很大的联系。可以说Spring的AOP机制就是建立在动态代理的基础上的。
之前已经描述过动态代理,这里把重点放在Spring AOP的实现上。


来看一个例子:
创建一个接口,并实现:

public interface IConnect {    void connect(String name);}
public class ConnectImpl implements IConnect{    @Override    public void connect(String name) {        System.out.println("创建连接---"+name);    }}

如果需要在方法执行之前调用某个逻辑,那么可以实现Spring提供的接口:MethodBeforeAdvice

public class BeforeConnect implements MethodBeforeAdvice{    @Override    public void before(Method method, Object[] args, Object target) throws Throwable {        String name = (String)args[0];        System.out.println("连接前处理...");    }}

下面是测试类:

public class BeforeConnectTest {    public static void main(String[] args) {        IConnect target = new ConnectImpl();        BeforeConnect advice = new BeforeConnect();        ProxyFactory proxyFactory = new ProxyFactory();        proxyFactory.setTarget(target);        proxyFactory.addAdvice(advice);        IConnect iConnectProxy = (IConnect) proxyFactory.getProxy();        iConnectProxy.connect("Irving");    }}

ProxyFactory:用来创建编程式的Spring AOP应用。
创建代理工厂,设置代理对象;并且为代理目标添加上前置通知。
生成代理实例,然后调用代理的方法。

将关注点放在ProxyFactory这个代理工厂上,它可以生成一个代理对象。

创建Proxy的方法,最后调用到了JdkDynamicAopProxy的getProxy(ClassLoader classLoader)方法:

public Object getProxy(ClassLoader classLoader) {        if (logger.isDebugEnabled()) {            logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());        }        Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);        findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);    }

可以看到最后,调用到的,是JDK的Proxy类的创建代理的方式。

同样,我们也可以通过Spring的配置方式来实现这样的功能。

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       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.xsd">    <bean id="target" class="ConnectImpl" />    <bean id="beforeConnectAdvice" class="BeforeConnect"/>    <!--定义Spring代理工厂 -->    <bean id="connectProxy" class="org.springframework.aop.framework.ProxyFactoryBean">        <property name="targetName" value="target" />        <property name="proxyInterfaces" value="IConnect"/>        <property name="interceptorNames" value="beforeConnectAdvice" />    </bean></beans>
public class TestAOP {    public static void main(String[] args) {        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:aopConfig.xml");        IConnect connect = (IConnect) context.getBean("connectProxy");        connect.connect("IS");    }}

运行结果如下:

连接前处理…
创建连接—IS

ProxyFactoryBean:用来创建声明式的Spring AOP应用。
由上面的配置可以发现,这种方式其实是将Proxy代理生成的方式挪到了XML的配置中。

和上面的配置方式相似,还有另外一种配置方式,XML的配置如下:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       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.xsd">    <bean id="target" class="ConnectImpl" />    <bean id="beforeConnectAdvice" class="BeforeConnect"/>    <!--定义Spring代理处理器 -->    <bean id="connectProxy" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">        <property name="beanNames">            <value>target</value>        </property>        <property name="interceptorNames">            <value>beforeConnectAdvice</value>        </property>    </bean></beans>

使用方式如下:

public class TestAOP2 {    public static void main(String[] args) {        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:aopConfig2.xml");        IConnect connect = (IConnect)context.getBean("target") ;        connect.connect("IS2");    }}

输出如下:
连接前处理…
创建连接—IS2

这种方式,直接将target实现成了代理对象,而不是返回动态生成的代理。
回头想想前面的两种方式,很直观明了地将切面所需要的几个要素都包含在了配置里面:
哪些方法?在什么地方调用?做哪些操作?

这种配置很清楚很明了,但是也有一些不好的地方:
第一点是关于切面:需要实现特定的接口,MethodBeforeAdvice,一个普通的类是无法被当做切面的。
第二就是我们把连接点的声明放在了切面本身,由切面来决定它是before还是after或者是其他地方来调用切面方法。如果需要在一个方法之前之后都调用某个切面方法,那么需要将这个方法分别写在MethodBeforeAdvice和MethodAfterAdvice接口实现中。

总的来说,这两种配置虽然很直接,但是还不够灵活。

除此之外,还有一种比较常见的实现方式:通过aop:config配置。
可以通过配置pointcut和aspect来实现可以通过配置pointcut和advisor来实现

这两种方式都允许以表达式的形式来定义切点。

先看第一种方式:
接口和实现同上例。
看下切面方法:

public class ConnectAspect {    public  void  beforeConn(){        System.out.println("This is before connect-------");    }    public  void  afterConn(){        System.out.println("This is after connect-------");    }}

这是一个很平常的Java类。

配置文件中引入aop命名空间:

<?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:aop="http://www.springframework.org/schema/aop"       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">    <bean id="target" class="ConnectImpl" />    <bean id="connAspect" class="ConnectAspect"/>    <aop:config>        <aop:pointcut id="connectPointcut" expression="execution(* ConnectImpl.*(..))"/>        <aop:aspect ref="connAspect">            <aop:before pointcut-ref="connectPointcut" method="beforeConn"/>            <aop:after pointcut-ref="connectPointcut"  method="afterConn"/>        </aop:aspect>    </aop:config></beans>

首先定义被代理的目标类,然后定义切面,然后将切面、切点结合起来。很容易理解的是。分别在方法的before和after位置进行切面方法调用。
至于在哪些方法调用,是由expression表达式决定的,
在执行任意包的ConnectImpl方法的任意方法时调用。
这种方法,可以将任意的方法变成切面方法,而且可以动态指定方法的过滤表达式。不过这种方法将类的方法写死在了配置文件中,这样不利于以后的修改和维护,而且这种方式也无法获取到运行时的方法信息。

还有一种方法是:
通过配置pointcut和advisor来实现
这种方式下,切面需要实现接口MethodInterceptor。

public class ConnectAdvice implements MethodInterceptor{    public Object invoke(MethodInvocation invocation) throws Throwable {        System.out.println("ConnectAdvice----before");        Object obj = invocation.proceed();        System.out.println("ConnectAdvice----before");        return obj;    }}

实现invoke方法,这个方法稍后分析,先来看配置文件:

<?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:aop="http://www.springframework.org/schema/aop"       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">    <bean id="target" class="ConnectImpl" />    <bean id="connectAdvice" class="ConnectAdvice"/>    <aop:config>        <aop:pointcut id="connectPointcut" expression="execution(* ConnectImpl.*(..))"/>        <aop:advisor advice-ref="connectAdvice" pointcut-ref="connectPointcut"></aop:advisor>    </aop:config></beans>

测试类:

public class TestAOP4 {    public static void main(String[] args) {        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:aopConfig4.xml");        IConnect connect = (IConnect)context.getBean("target") ;        connect.connect("IS4");    }}

输出结果:
ConnectAdvice—-before
创建连接—IS4
ConnectAdvice—-before

对于实现接口的目标类,Spring AOP使用JdkProxy来生成代理,对于其他的,Spring使用Cglib生成代理。
本例子使用的是接口类型的目标类。

而我们此处并没有显式地指定切面实现InvocationHandler,事实上,ConnectAdvice这个类和InvocationHandler没有任何的父子关系。那这个切面是如何应用起来的呢?

代理对象关系紧密的是InvocationHandler对象,调用代理对象的方法,其实就是调用target对象的方法,调用InvocationHandler对象的invoke方法。

JdkDynamicAopProxy自己实现了InvocationHandler,并且构造方法:

public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {        Assert.notNull(config, "AdvisedSupport must not be null");        if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {            throw new AopConfigException("No advisors and no TargetSource specified");        }        this.advised = config;

传入了一个名为AdvisedSupport的对象,该对象包括了AOP的配置。其中就包括了连接点的配置(包装成ExposeInvocationInterceptor),advisor的配置(包装成DefaultBeanFactoryPointcutAdvisor)。
查看其invoke方法的实现,

// Get the interception chain for this method.//获取当前方法的 interception列表            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);            // Check whether we have any advice. If we don't, we can fallback on direct            // reflective invocation of the target, and avoid creating a MethodInvocation.            //如果没有interception列表,就直接调用目标方法            if (chain.isEmpty()) {                // We can skip creating a MethodInvocation: just invoke the target directly                // Note that the final invoker must be an InvokerInterceptor so we know it does                // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.                retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);            }            else {                // We need to create a method invocation...                invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);                // Proceed to the joinpoint through the interceptor chain.                //调用interceptor链                retVal = invocation.proceed();            }            // Massage return value if necessary.            Class<?> returnType = method.getReturnType();            if (retVal != null && retVal == target && returnType.isInstance(proxy) &&                    !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {                // Special case: it returned "this" and the return type of the method                // is type-compatible. Note that we can't help if the target sets                // a reference to itself in another returned object.                retVal = proxy;            } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {                throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);            }            return retVal;        }

invocation的proceed方法,将每个advisor中的方法分别依次调用。
可见,虽然没有直接定义InvocationHandler,但是内部使用的还是动态代理的方式来实现面向切面编程。

0 0
原创粉丝点击