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,但是内部使用的还是动态代理的方式来实现面向切面编程。
- Spring AOP和动态代理
- spring aop和动态代理
- spring AOP 静态代理和动态代理
- Spring AOP 和 动态代理技术
- Spring Aop 动态代理
- Spring AOP动态代理
- Spring AOP 动态代理
- spring aop动态代理
- Spring(AOP动态代理)
- 动态代理和静态代理以及spring的aop
- spring aop proxy 静态代理和动态代理
- 动态代理和静态代理以及spring的aop
- 基于Spring AOP的JDK动态代理和CGLIB代理
- Spring -AOP -java静态代理和动态代理实例
- AOP和动态代理
- spring AOP 动态代理 jkd动态代理和cglib动态代理 hibernate使用cglib延迟加载
- Spring AOP配置 动态代理
- Spring AOP JDK动态代理
- php curl采集案例
- JFreeChart-图表绘制
- leetcode 530. Minimum Absolute Difference in BST(easy)
- C++ Primer Chapter 7-3
- 算法时间复杂度的表达-渐进符号与主定理
- Spring AOP和动态代理
- 关于python打包成exe
- php服务安装和配置以及和apache整合
- C++ Primer Chapter 7-4
- android 介绍Retrofit的简单使用
- 洛谷 P2604 [ZJOI2010]网络扩容
- Redis源码剖析和注释(七)--- 快速列表(quicklist)
- 前端相关知识网址分类
- 页面添加了禁止右键,如何查看源代码呢?