1. 从代理机制初探AOP1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.voidking.aop; import java.util.logging.*;public class HelloSpeaker { private Logger logger = Logger.getLogger(this.getClass().getName()); public void hello(String name ) { logger.log(Level .INFO , "hello method starts..."); System .out .println("hello,"+name ); logger.log(Level .INFO , "hello method ends..."); } }
在HelloSpeaker类中,当执行hello()方法时,程序员希望该方法执行开始与执行完毕时都留下日志。最简单的做法是上面的程序设计,在方法执行前后加上日志动作。
然而对于HelloSpeaker类来说,日志的这种动作并不属于HelloSpeaker的逻辑,这使得HelloSpeaker增加了额外的职责。
如果程序中这种日志动作到处都有,以上的写法势必造成程序员必须到处撰写这些日志动作的代码。这将使得维护日志代码的困难加大。如果需要的服务不只是日志动作,有一些非类本身职责的相关动作也混入到类中,如权限检查、事务管理等,会使得类的负担加重,甚至混淆类本身的职责。
另一方面,使用以上的写法,如果有一天不再需要日志(或权限检查、交易管理等)服务,将需要修改所有留下日志动作的程序,无法简单地将这些相关服务从现有的程序中移除。
可以使用代理(Proxy)机制来解决这个问题,有两种代理方式:静态代理(static proxy)和动态代理(dynamic proxy)。
在静态代理的实现中,代理类与被代理的类必须实现同一个接口。在代理类中可以实现记录等相关服务,并在需要的时候再呼叫被代理类。这样被代理类就可以仅仅保留业务相关的职责了。
举个简单的例子,首先定义一个IHello接口:
1 2 3 4 5 6 package com.void king.aop; public interface IHello { public void hello(String name); }
然后让实现业务逻辑的HelloSpeaker2类实现IHello接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.voidking.aop;public class HelloSpeaker2 implements IHello { @Override public void hello(String name) { // TODO Auto-generated method stub System .out .println ("hello,"+name); } }
代理类HelloProxy同样要实现IHello接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package com.voidking.aop;import java.util.logging.Level;import java.util.logging.Logger;public class HelloProxy implements IHello { private Logger logger = Logger.getLogger(this .getClass().getName()); private IHello helloObject; public HelloProxy (IHello helloObject) { this .helloObject = helloObject; } @Override public void hello (String name) { log("hello method starts..." ); helloObject.hello(name); log("hello method ends..." ); } private void log (String msg) { logger.log(Level.INFO, msg); } }
写一个测试程序来看看效果:
1 2 3 4 5 6 7 8 9 package com.voidking.aop;public class ProxyDemo { public static void main (String[] args) { IHello proxy = new HelloProxy (new HelloSpeaker2 ()); proxy.hello("Justin" ); } }
这是静态代理的基本示例,但是可以看到,代理类的一个接口只能服务于一种类型的类,而且如果要代理的方法很多,势必要为每个方法进行代理。静态代理在程序规模稍大时必定无法胜任。
2. 动态代理JDK1.3之后加入了可协助开发动态代理功能的API等相关类别,不需要为特定类和方法编写特定代理类,使用动态代理。使用动态代理可以使一个处理者(Handler)为各个类服务。
IHello.java、HelloSpeaker.java、LogHandler.java、ProxyDemo.java代码分别如下:
1 2 3 4 5 6 package com.void king.aop2; public interface IHello { public void hello(String name); }
1 2 3 4 5 6 7 8 9 10 11 12 package com.voidking.aop2;public class HelloSpeaker implements IHello { @Override public void hello(String name) { // TODO Auto-generated method stub System .out .println ("Hello,"+name); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.voidking.aop2;import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class LogHandler implements InvocationHandler { private Object sub ; public LogHandler() { // TODO Auto-generated constructor stub } public LogHandler(Object obj){ sub =obj ; } @Override public Object invoke(Object proxy, Method method , Object [] args ) throws Throwable { // TODO Auto-generated method stub System .out .println ("before you do thing"); method.invoke(sub , args ) ; System.out.println("after you do thing" ); return null; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.voidking.aop2;import java.lang.reflect.Proxy;public class ProxyDemo { public static void main (String[] args) { HelloSpeaker helloSpeaker = new HelloSpeaker (); LogHandler logHandler = new LogHandler (helloSpeaker); Class cls = helloSpeaker.getClass(); IHello iHello = (IHello)Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), logHandler); iHello.hello("Justin" ); } }
HelloSpeaker本身的职责是显示文字,却必须插入日志动作,这是的HelloSpeaker的职责加重。日志的程序代码横切(cross-cutting)到HelloSpeaker的程序执行流程中,日志这样的动作在AOP术语中被称为横切关注点(cross-cutting concerns)。
使用代理类将记录与业务逻辑无关的动作提取出来,设计为一个服务类,如同前面的范例HelloProxy或者LogHandler,这样的类称为切面(Aspect)。
将日志等动作(cross-cutting concerns)设计为通用,不介入特定业务类的一个职责清楚的Aspect类,这就是所谓的Aspect-Oriented Programming,AOP。
3. 通知AdviceSpring提供了5种通知(Advice)类型:
Interception Around Advice:在目标对象的方法执行前后被调用。 Before Advice:在目标对象的方法执行前被调用。 After Returning Advice:在目标对象的方法执行后被调用。 Throw Advice:在目标对象的方法抛出异常时被调用。 Introduction Advice:一种特殊类型的拦截通知,只有在目标对象的方法调用完毕后执行。 4. 切入点PointcutPointcut定义了Advice应用的时机。 在Spring中,使用PointcutAdvisor把Pointcut与Advice结合为一个对象。Spring中大部分内建的Pointcut都有对应的PointAdvisor。 静态切入点只限于给定的方法和目标类,而不考虑方法的参数。动态切入点与静态切入点的区别是,动态切入点不仅限定于给定的方法和类,还可以指定方法的参数。大多数切入点,可以使用静态切入点,很少有机会创建动态切入点。
5. 源代码分享https://github.com/voidking/aop.git
6. 小结Advice和Pointcut到底干嘛用的?书上根本没有讲清楚哇!不去查资料了,需要的时候再去深入学习。
7. 参考文档《Java EE基础实用教程》,郑阿奇主编