Spring AOP和动态代理

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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值