AOP 那点事儿(转载黄勇老师)
?
before() 与 after() 方法写死在 sayHello() 方法体中了,这样的代码的味道非常不好。如果哪位仁兄大量写了这样的代码,肯定要被你的架构师骂个够呛。
比如:我们要统计每个方法的执行时间,以对性能作出评估,那是不是要在每个方法的一头一尾都做点手脚呢?
再比如:我们要写一个 JDBC 程序,那是不是也要在方法的开头去连接数据库,方法的末尾去关闭数据库连接呢?
这样的代码只会把程序员累死,把架构师气死!
一定要想办法对上面的代码进行重构,首先给出三个解决方案:
2. 静态代理
最简单的解决方案就是使用静态代理模式了,我们单独为 GreetingImpl 这个类写一个代理类:
看看这是有多么的简单:
view source?print?1@Component2public class GreetingImpl implements Greeting { 3??4????... 5}view source?print?1@Component2public class GreetingAroundAdvice implements MethodInterceptor { 3??4????... 5}最后看看客户端吧:
view source?print?1public class Client { 2??3????public static void main(String[] args) { 4????????ApplicationContext context = new ClassPathXmlApplicationContext("aop/demo/spring.xml"); // 获取 Spring Context 5????????Greeting greeting = (Greeting) context.getBean("greetingProxy");??????????????????????? // 从 Context 中根据 id 获取 Bean 对象(其实就是一个代理) 6????????greeting.sayHello("Jack");????????????????????????????????????????????????????????????? // 调用代理的方法 7????} 8}代码量确实少了,我们将配置性的代码放入配置文件,这样也有助于后期维护。更重要的是,代码只关注于业务逻辑,而将配置放入文件中。这是一条最佳实践!
除了上面提到的那三类增强以外,其实还有两类增强也需要了解一下,关键的时候您要能想得到它们才行。?
7.?Spring AOP:抛出增强
程序报错,抛出异常了,一般的做法是打印到控制台或日志文件中,这样很多地方都得去处理,有没有一个一劳永逸的方法呢?那就是?Throws Advice(抛出增强),它确实很强,不信你就继续往下看:
?
view source?print?01@Component02public class GreetingImpl implements Greeting { 03??04????@Override05????public void sayHello(String name) { 06????????System.out.println("Hello! " + name); 07??08????????throw new RuntimeException("Error"); // 故意抛出一个异常,看看异常信息能否被拦截到 09????} 10}下面是抛出增强类的代码:
?
view source?print?01@Component02public class GreetingThrowAdvice implements ThrowsAdvice { 03??04????public void afterThrowing(Method method, Object[] args, Object target, Exception e) { 05????????System.out.println("---------- Throw Exception ----------"); 06????????System.out.println("Target Class: " + target.getClass().getName()); 07????????System.out.println("Method Name: " + method.getName()); 08????????System.out.println("Exception Message: " + e.getMessage()); 09????????System.out.println("-------------------------------------"); 10????} 11}抛出增强类需要实现?org.springframework.aop.ThrowsAdvice 接口,在接口方法中可获取方法、参数、目标对象、异常对象等信息。我们可以把这些信息统一写入到日志中,当然也可以持久化到数据库中。
这个功能确实太棒了!但还有一个更厉害的增强。如果某个类实现了 A 接口,但没有实现 B 接口,那么该类可以调用 B 接口的方法吗?如果您没有看到下面的内容,一定不敢相信原来这是可行的!
8. Spring AOP:引入增强
以上提到的都是对方法的增强,那能否对类进行增强呢?用 AOP 的行话来讲,对方法的增强叫做 Weaving(织入),而对类的增强叫做 Introduction(引入)。而?Introduction Advice(引入增强)就是对类的功能增强,它也是 Spring AOP 提供的最后一种增强。建议您一开始千万不要去看《Spring Reference》,否则您一定会后悔的。因为当您看了以下的代码示例后,一定会彻底明白什么才是引入增强。
定义了一个新接口 Apology(道歉):
view source?print?1public interface Apology { 2??3????void saySorry(String name); 4}但我不想在代码中让?GreetingImpl 直接去实现这个接口,我想在程序运行的时候动态地实现它。因为假如我实现了这个接口,那么我就一定要改写 GreetingImpl 这个类,关键是我不想改它,或许在真实场景中,这个类有1万行代码,我实在是不敢动了。于是,我需要借助 Spring 的引入增强。这个有点意思了!
view source?print?01@Component02public class GreetingIntroAdvice extends DelegatingIntroductionInterceptor implements Apology { 03??04??? @Override05??? public Object invoke(MethodInvocation invocation) throws Throwable { 06??? ? ? return super.invoke(invocation); 07??? } 08??09????@Override10????public void saySorry(String name) { 11????????System.out.println("Sorry! " + name); 12????} 13}以上定义了一个引入增强类,扩展了?org.springframework.aop.support.DelegatingIntroductionInterceptor 类,同时也实现了新定义的?Apology 接口。在类中首先覆盖了父类的 invoke() 方法,然后实现了 Apology 接口的方法。我就是想用这个增强类去丰富?GreetingImpl 类的功能,那么这个 GreetingImpl?类无需直接实现 Apology 接口,就可以在程序运行的时候调用?Apology 接口的方法了。这简直是太神奇的!
看看是如何配置的吧:
view source?print?01<?xml version="1.0" encoding="UTF-8"?> 02<beans xmlns="http://www.springframework.org/schema/beans"03???????xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"04???????xmlns:context="http://www.springframework.org/schema/context"05???????xsi:schemaLocation="http://www.springframework.org/schema/beans 06???????http://www.springframework.org/schema/beans/spring-beans.xsd 07???????http://www.springframework.org/schema/context 08???????http://www.springframework.org/schema/context/spring-context.xsd"> 09??10????<context:component-scan base-package="aop.demo"/> 11??12????<bean id="greetingProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> 13????????<property name="interfaces" value="aop.demo.Apology"/>????????? <!-- 需要动态实现的接口 -->14????????<property name="target" ref="greetingImpl"/>??????????????????? <!-- 目标类 -->15????????<property name="interceptorNames" value="greetingIntroAdvice"/> <!-- 引入增强 -->16????????<property name="proxyTargetClass" value="true"/>??????????????? <!-- 代理目标类(默认为 false,代理接口) -->17????</bean> 18??19</beans>需要注意?proxyTargetClass 属性,它表明是否代理目标类,默认为 false,也就是代理接口了,此时?Spring 就用 JDK 动态代理。如果为 true,那么 Spring 就用 CGLib 动态代理。这简直就是太方便了!Spring 封装了这一切,让程序员不在关心那么多的细节。我们要向老罗同志致敬,您是我们心中永远的 idol!
当您看完下面的客户端代码,一定会完全明白以上的这一切:
view source?print?01public class Client { 02??03????public static void main(String[] args) { 04????????ApplicationContext context = new ClassPathXmlApplicationContext("aop/demo/spring.xml"); 05????????GreetingImpl greetingImpl = (GreetingImpl) context.getBean("greetingProxy"); // 注意:转型为目标类,而并非它的 Greeting 接口 06????????greetingImpl.sayHello("Jack"); 07??08????????Apology apology = (Apology) greetingImpl; // 将目标类强制向上转型为 Apology 接口(这是引入增强给我们带来的特性,也就是“接口动态实现”功能) 09????????apology.saySorry("Jack"); 10????} 11}没想到 saySorry() 方法原来是可以被 greetingImpl 对象来直接调用的,只需将其强制转换为该接口即可。
推荐看http://my.oschina.net/huangyong/blog/158380