一、AOP介绍
面向方面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP)。OOP中模块化的关键单元是类,而在AOP中,模块化单元是方面。方面实现了诸如跨越多种类型和对象的事务管理之类的关注点的模块化。(这种担忧通常被称为AOP文献中的横切关注点。)
Spring的一个关键组件是AOP框架。虽然Spring IoC容器不依赖于AOP,但是如果您不想使用AOP,则意味着您不需要使用AOP,AOP补充了Spring IoC以提供非常强大的中间件解决方案。
带有AspectJ切入点的Spring AOP,通过使用 基于模式的方法 或 @AspectJ注释样式,提供了编写自定义方面的简单而强大的方法 。这两种样式都提供完全类型的advice和使用AspectJ切入点语言,同时仍然使用Spring AOP进行编织。
AOP的相关术语:
1. Aspect 切面
跨越多个类别的关注点的模块化。事务管理是企业Java应用程序中横切关注点的一个很好的例子。在Spring AOP中,方面是通过使用常规类(基于模式的方法)或使用@Aspect 注释(@AspectJ 样式)注释的常规类来实现的。
2. JoinPoint 连接点
程序执行期间的一个点,例如执行方法或处理异常。在Spring AOP中,连接点始终表示方法执行。
3. PointCut 切入点
匹配连接点的谓词。建议与切入点表达式相关联,并在切入点匹配的任何连接点处运行(例如,执行具有特定名称的方法)。由切入点表达式匹配的连接点的概念是AOP的核心,Spring默认使用AspectJ切入点表达式语言。
4. Advice 增强:特定连接点的某个方面采取的操作。不同类型的建议包括“周围”,“之前”和“之后”建议。许多AOP框架(包括Spring)将建议建模为拦截器并在连接点周围维护一系列拦截器。
① Before(前置增强);
② After(后置增强 ):AfterReturning、AfterThrowing、After(Finally,Spring AOP中没有);
③ Around(环绕增强):
环绕连接点的advice,例如方法调用。这是最有力的advice。around通知可以在方法调用之前和之后执行自定义行为。它还负责选择是继续加入点还是通过返回自己的返回值或抛出异常来快速建议的方法执行。
AroundAdvice是最普遍的Advice。由于Spring AOP(如AspectJ)提供了全方位的建议类型,因此建议使用可以实现所需行为的最不强大的advice类型。例如,如果您只需要使用方法的返回值更新缓存,那么最好实现AfterReturningAdvice而不是AroundAdvice,尽管AroundAdvice可以完成同样的事情。
④ Introduction(引入增强):
代表类型声明其他方法或字段。Spring AOP允许您向任何建议的对象引入新接口(以及相应的实现)。例如,您可以使用简介使bean实现IsModified接口,以简化缓存。(介绍被称为AspectJ社区中的类型间声明。);
⑤ ControlFlow(流程增强):
Spring控制流切入点在概念上类似于AspectJ cflow切入点,虽然功能较弱。(目前无法指定切入点在另一个切入点匹配的连接点下执行)
控制流切入点与当前调用堆栈匹配。 例如,如果com.mycompany.web包中的方法或SomeCaller类调用了连接点,则可能会触发它。 使用org.springframework.aop.support.ControlFlowPointcut类指定控制流切入点。
5. Target 目标类
由一个或多个方面建议的对象。 也称为“建议对象”。 由于Spring AOP是使用运行时代理实现的,因此该对象始终是代理对象。
6. Proxy 代理类
由AOP框架创建的对象,用于实现切面接口(advice方法executions等)。 在Spring Framework中,AOP代理是JDK动态代理或CGLIB代理。
7. Weaving 织入
将切面与其他应用程序类型或对象链接以创建advice对象。 这可以在编译时(例如,使用AspectJ编译器),加载时或在运行时完成。 与其他纯Java AOP框架一样,Spring AOP在运行时执行编织。
实现方案:
1. RTW(Run-Time Weaving)运行期织入
它是Spring AOP中使用的编织方法。生成代理以执行编织而不修改实际目标对象。在运行时,编织在Method调用完成。虽然有一个优点是不对源文件和类文件进行修改,但是存在一个缺点,即随着应用于切入点的advise数量的增加,性能会提高
2. LTW(Load-Time Weaving)类加载器织入
当使用特殊的ClassLoader将类加载到JVM中时,类如何编写字节序操作与CTW相比,编译时间相对较短,因为它不会像RTW那样操作源文件和类文件。但是,在运行时,时间比CTW慢,因为在对象在内存中时会发生编织。当对象加载到应用程序上下文中时,性能会降低,因为aspectj weaver和spring-instrument会进行对象处理。
3. CTW(Compile-Time Weaving)编译期间织入
AspectJ有一个名为AJC(AspectJ Compiler)的编译器,它是Java Compiler的扩展。通过AJC编译java文件,并在编译期间通过字节代码操作直接插入Advisor代码以执行编织。其中的优点是三种织机中性能最快(当在JVM运行时时,建议代码已经插入到方法中)。
Spring AOP功能和目标
Spring AOP是用纯Java实现的。不需要特殊的编译过程。Spring AOP不需要控制类加载器层次结构,因此适合在servlet容器或应用程序服务器中使用。
Spring AOP目前仅支持方法执行连接点(建议在Spring bean上执行方法)。虽然可以在不破坏核心Spring AOP API的情况下添加对字段拦截的支持,但未实现字段拦截。如果您需要建议字段访问和更新连接点,请考虑使用AspectJ等语言。
Spring AOP的AOP方法与大多数其他AOP框架的方法不同。目的不是提供最完整的AOP实现(尽管Spring AOP非常强大)。相反,目标是在AOP实现和Spring IoC之间提供紧密集成,以帮助解决企业应用程序中的常见问题。
例如,Spring Framework的AOP功能通常与Spring IoC容器一起使用。通过使用普通bean定义语法来配置方面(尽管这允许强大的“自动代理”功能)。这是与其他AOP实现的重要区别。使用Spring AOP无法轻松或高效地完成某些操作,例如建议非常细粒度的对象(通常是域对象)。在这种情况下,AspectJ是最佳选择。但是,我们的经验是Spring AOP为适合AOP的企业Java应用程序中的大多数问题提供了出色的解决方案。
AOP代理
AOP在Spring Framework中用于
-
提供声明性企业服务,尤其是作为EJB声明性服务的替代品。最重要的此类服务是声明式事务管理。
-
允许用户实现自定义方面,补充他们使用AOP的OOP。
Spring AOP也可以使用CGLIB代理。这是代理类而不是接口所必需的。默认情况下,如果业务对象未实现接口,则使用CGLIB。由于优化的做法是编程接口而不是类,业务类通常实现一个或多个业务接口。可以强制使用CGLIB,在那些需要建议未在接口上声明的方法或需要将代理对象作为具体类型传递给方法的情况下。
例子
Spring的配置文件 spring-config-filter-method.xml
1 <beans xmlns="http://www.springframework.org/schema/beans" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns:context="http://www.springframework.org/schema/context" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans 5 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 6 http://www.springframework.org/schema/context 7 http://www.springframework.org/schema/context/spring-context-3.0.xsd"> 8 9 <context:component-scan base-package="com.springStyle"/> 10 11 <bean id="userServiceAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> 12 <property name="advice" ref="beforeAdvice"/> 13 <property name="patterns"> 14 <list> 15 <value>com.springStyle.target.*.save.*</value> 16 <value>com.springStyle.target.*.update.*</value> 17 </list> 18 </property> 19 </bean> 20 21 <bean id="userServiceProxyCglib" class="org.springframework.aop.framework.ProxyFactoryBean"> 22 <property name="proxyTargetClass" value="true"/> 23 <property name="target" ref="userService"/> 24 <property name="interceptorNames"> 25 <list> 26 <value>userServiceAdvisor</value> 27 </list> 28 </property> 29 </bean> 30 31 <bean id="userServiceProxyJDK" class="org.springframework.aop.framework.ProxyFactoryBean"> 32 <property name="interfaces" value="com.springStyle.target.UserService"/> 33 <property name="target" ref="userService"/> 34 <property name="interceptorNames"> 35 <list> 36 <value>userServiceAdvisor</value> 37 </list> 38 </property> 39 </bean> 40 41 </beans>
测试代码:
1 @Test 2 public void testFilterMethodJDK() { 3 ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext 4 ("springStyle/declaration/manual/spring-config-filter-method.xml"); 5 UserService proxy = (UserService) ctx.getBean("userServiceProxyJDK"); 6 String ret = proxy.queryAllUser(); 7 8 System.out.println("proxyClass:" +proxy.getClass()); 9 System.out.println("执行结果:" + ret); 10 proxy.saveUser("zhangsan"); 11 }
Spring AOP使用的JDK动态代理,当Sring的上下文环境配置好后,获取AOP的代理对象,JdkDynamicAopProxy其原理还是基于Jdk反射包中的Proxy
1 @Override 2 public Object getProxy(@Nullable ClassLoader classLoader) { 3 if (logger.isTraceEnabled()) { 4 logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource()); 5 } 6 Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true); 7 findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); 8 return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); 9 }
当使用Aop代理调用saveUser方法时,跳转到代理类JdkDynamicAopProxy的invoke方法
JdkDynamicAopProxy#invoke --> ReflectiveMethodInvocation#proceed --> MethodBeforeAdviceInterceptor#invoke(这里调用了自己的advice方法) --> ReflectiveMethodInvocation#invokeJoinpoint --> ReflectiveMethodInvocation#invokeJoinpoint --> AopUtils#invokeJoinpointUsingReflection --> Method#invoke --> DelegatingMethodAccessorImpl#invoke --> NativeMethodAccessorImpl#invoke --> NativeMethodAccessorImpl#invoke0 --> 自己的saveUserUser方法 --> 结束