【转载】面向切面编程(AOP)学习
看到这篇文章,学习一下:http://www.ciaoshen.com/2016/10/28/aop/
想理清一下从“动态代理”,到 “注释”,到“面向切面编程”这么一个技术演进的脉络。
只想讲清楚两个问题:
- 什么是面向切面编程?为什么要面向切面?
- 动态代理技术是怎么实现面向切面的?注释在其中扮演了什么角色?
提到另一篇文章:https://my.oschina.net/huangyong/blog/161338
目前最知名最强大的 Java 开源项目就是 AspectJ 了。Rod Johnson(老罗)写了一个叫做 Spring 框架,从此一炮走红,成为了 Spring 之父。
他在自己的 IOC 的基础之上,又实现了一套 AOP 的框架,后来仿佛发现自己越来越走进深渊里,在不能自拔的时候,有人建议他还是集成 AspectJ 吧,他在万般无奈之下才接受了该建议。于是,我们现在用得最多的想必就是 Spring + AspectJ 这种 AOP 框架了。
Spring AOP:抛出增强
程序报错,抛出异常了,一般的做法是打印到控制台或日志文件中,这样很多地方都得去处理,有没有一个一劳永逸的方法呢?那就是 Throws Advice(抛出增强),它确实很强:
@Component public class GreetingImpl implements Greeting { @Override public void sayHello(String name) { System.out.println("Hello! " + name); throw new RuntimeException("Error"); // 故意抛出一个异常,看看异常信息能否被拦截到 } }
下面是抛出增强类的代码:
@Component public class GreetingThrowAdvice implements ThrowsAdvice { public void afterThrowing(Method method, Object[] args, Object target, Exception e) { System.out.println("---------- Throw Exception ----------"); System.out.println("Target Class: " + target.getClass().getName()); System.out.println("Method Name: " + method.getName()); System.out.println("Exception Message: " + e.getMessage()); System.out.println("-------------------------------------"); } }
8. Spring AOP:引入增强
这个功能确实太棒了!但还有一个更厉害的增强。如果某个类实现了 A 接口,但没有实现 B 接口,那么该类可以调用 B 接口的方法吗?如果您没有看到下面的内容,一定不敢相信原来这是可行的!
以上提到的都是对方法的增强,那能否对类进行增强呢?用 AOP 的行话来讲,对方法的增强叫做 Weaving(织入),而对类的增强叫做 Introduction(引入)。而 Introduction Advice(引入增强)就是对类的功能增强,它也是 Spring AOP 提供的最后一种增强。
定义了一个新接口 Apology(道歉):
public interface Apology { void saySorry(String name); }
借助 Spring 的引入增强。
@Component public class GreetingIntroAdvice extends DelegatingIntroductionInterceptor implements Apology { @Override public Object invoke(MethodInvocation invocation) throws Throwable { return super.invoke(invocation); } @Override public void saySorry(String name) { System.out.println("Sorry! " + name); } }
如何配置
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="aop.demo"/> <bean id="greetingProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="interfaces" value="aop.demo.Apology"/> <!-- 需要动态实现的接口 --> <property name="target" ref="greetingImpl"/> <!-- 目标类 --> <property name="interceptorNames" value="greetingIntroAdvice"/> <!-- 引入增强 --> <property name="proxyTargetClass" value="true"/> <!-- 代理目标类(默认为 false,代理接口) --> </bean> </beans>
客户端代码
public class Client { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("aop/demo/spring.xml"); GreetingImpl greetingImpl = (GreetingImpl) context.getBean("greetingProxy");
// 注意:转型为目标类,而并非它的 Greeting 接口 greetingImpl.sayHello("Jack"); Apology apology = (Apology) greetingImpl;
// 将目标类强制向上转型为 Apology 接口(这是引入增强给我们带来的特性,也就是“接口动态实现”功能) apology.saySorry("Jack"); } }
以上的示例代码都已经下载:
/Users/baidu/Documents/Data/Work/Code/Demo_Code/aop_demo
下面这篇是续集:
https://my.oschina.net/huangyong/blog/161402
9. Spring AOP:切面
只需要拦截特定的方法就行了,没必要拦截所有的方法。借助了 AOP 的一个很重要的工具,Advisor(切面),来解决这个问题。它也是 AOP 中的核心。
这里提到这个“拦截匹配条件”在 AOP 中就叫做 Pointcut(切点),其实说白了就是一个基于表达式的拦截条件。
归纳一下,Advisor(切面)封装了 Advice(增强)与 Pointcut(切点 )。
下面要做的就是拦截这两个新增的方法,而对 sayHello() 方法不作拦截。
@Component public class GreetingImpl implements Greeting { @Override public void sayHello(String name) { System.out.println("Hello! " + name); } public void goodMorning(String name) { System.out.println("Good Morning! " + name); } public void goodNight(String name) { System.out.println("Good Night! " + name); } }
Spring AOP 中,提供了许多切面类了,这些切面类就有基于正则表达式的切面类。
<?xml version="1.0" encoding="UTF-8"?> <beans ..."> <context:component-scan base-package="aop.demo"/> <!-- 配置一个切面 --> <bean id="greetingAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice" ref="greetingAroundAdvice"/> <!-- 增强 --> <property name="pattern" value="aop.demo.GreetingImpl.good.*"/> <!-- 切点(正则表达式) --> </bean> <!-- 配置一个代理 --> <bean id="greetingProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="greetingImpl"/> <!-- 目标类 --> <property name="interceptorNames" value="greetingAdvisor"/> <!-- 切面 --> <property name="proxyTargetClass" value="true"/> <!-- 代理目标类 --> </bean> </beans>
注意以上代理对象的配置中的 interceptorNames,它不再是一个增强,而是一个切面,因为已经将增强封装到该切面中了。此外,切面还定义了一个切点(正则表达式),其目的是为了只将满足切点匹配条件的方法进行拦截。
除了 RegexpMethodPointcutAdvisor 以外,在 Spring AOP 中还提供了几个切面类,比如:
DefaultPointcutAdvisor:默认切面(可扩展它来自定义切面)
NameMatchMethodPointcutAdvisor:根据方法名称进行匹配的切面
StaticMethodMatcherPointcutAdvisor:用于匹配静态方法的切面
10. Spring AOP:自动代理(扫描 Bean 名称)
能否让 Spring 框架为我们自动生成代理呢?Spring AOP 提供了一个可根据 Bean 名称来自动生成代理的工具,它就是 BeanNameAutoProxyCreator。是这样配置的:
<?xml version="1.0" encoding="UTF-8"?> <beans ...> ... <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames" value="*Impl"/> <!-- 只为后缀是“Impl”的 Bean 生成代理 --> <property name="interceptorNames" value="greetingAroundAdvice"/> <!-- 增强 --> <property name="optimize" value="true"/> <!-- 是否对代理生成策略进行优化 --> </bean> </beans>
关于CGLib和JDK动态代理:
根据多年来实际项目经验得知:CGLib 创建代理的速度比较慢,但创建代理后运行的速度却非常快,而 JDK 动态代理正好相反。
如果在运行的时候不断地用 CGLib 去创建代理,系统的性能会大打折扣,所以建议一般在系统初始化的时候用 CGLib 去创建代理,
并放入 Spring 的 ApplicationContext 中以备后用。
11. Spring AOP:自动代理(扫描切面配置)
Spring AOP 基于切面也提供了一个自动代理生成器:DefaultAdvisorAutoProxyCreator。
<?xml version="1.0" encoding="UTF-8"?> <beans ...> ... <bean id="greetingAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="pattern" value="aop.demo.GreetingImpl.good.*"/> <property name="advice" ref="greetingAroundAdvice"/> </bean> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"> <property name="optimize" value="true"/> </bean> </beans>
这里无需再配置代理了,因为代理将会由 DefaultAdvisorAutoProxyCreator 自动生成。也就是说,这个类可以扫描所有的切面类,并为其自动生成代理。
12. Spring + AspectJ(基于注解:通过 AspectJ execution 表达式拦截方法)
@Aspect @Component public class GreetingAspect { @Around("execution(* aop.demo.GreetingImpl.*(..))") public Object around(ProceedingJoinPoint pjp) throws Throwable { before(); Object result = pjp.proceed(); after(); return result; } private void before() { System.out.println("Before"); } private void after() { System.out.println("After"); } }
方法的参数中包括一个 ProceedingJoinPoint 对象,它在 AOP 中称为 Joinpoint(连接点),可以通过该对象获取方法的任何信息,例如:方法名、参数等。
总结如下图:
再来一张表格,总结一下各类增强类型所对应的解决方案:
增强类型 | 基于 AOP 接口 | 基于 @Aspect | 基于 <aop:config> |
Before Advice(前置增强) | MethodBeforeAdvice | @Before | <aop:before> |
AfterAdvice(后置增强) | AfterReturningAdvice | @After | <aop:after> |
AroundAdvice(环绕增强) | MethodInterceptor | @Around | <aop:around> |
ThrowsAdvice(抛出增强 | ThrowsAdvice | @AfterThrowing | <aop:after-throwing> |
IntroductionAdvice(引入增强) | DelegatingIntroductionInterceptor | @DeclareParents | <aop:declare-parents> |
最后给一张 UML 类图描述一下 Spring AOP 的整体架构:
以上,需要结合例子深入学习。
(完)