Spring AOP原理模拟


Spring AOP 之—— 原理模拟 郭蕾 2011.09.10 Spring已经为我们实现了AOP技术,我们在使用的过程中只要进行简单配置就可以使用了, 但在工作中我发现只会使用这些东西是远远不够的,特别是我们使用注解配置 AOP 的时候, 虽然方便简单,但是原理我们根本不懂,这有时候会影响我们的开发效率,不懂原理,我们 永远是初级。现在我就当自己根本不知道 Spring 的 AOP,一点点往下分析: 现在我们在项目中有一个需求,就是如果我们要访问某一个类中的所有方法,那么必须在访 问之前打印一句话,比方说: public class CardServiceImpl implements CardService{ public void delete() { System.out.println("delete"); update(); } public void save() { System.out.println("save"); } public void update() { System.out.println("update"); } } 现在我们让访问 CardServiceImpl 类中的任何方法的时候都向控制台输出一句话,这个时候 也许我们立即会想到在每个方法里加上打印语句,这种方法当然可行。那如果我又说不想打 印了,那你怎么办?又一句句的删除?是不是有些麻烦?当然麻烦了。现在大家都流行 AOP 编程,AOP 是如何实现上面的功能的了?AOP 是通过访问目标对象的代理对象来实现的, 代理对象在方法中做了手脚,怎么去生成代理对象了?我们可以使用java 中的 Proxy 类, 如下说明: 动态代理类(以下简称为代理类)是一个实现在创建类时在运行时指定的接口列表的类,该类具有下 面描述的行为。 代理接口 是代理类实现的一个接口。 代理实例 是代理类的一个实例。 每个代理 实例都有一个关联的调用处理程序 对象,它可以实现接口 InvocationHandler。通过其中一个代理接 口的代理实例上的方法调用将被指派到实例的调用处理程序的 Invoke 方法,并传递代理实例、识别 调用方法的 java.lang.reflect.Method 对象以及包含参数的 Object 类型的数组。调用处理程序以适当 的方式处理编码的方法调用,并且它返回的结果将作为代理实例上方法调用的结果返回。 上面是在 JDK 中的说明,我们得知,在使用代理类是必须是面向接口编程的,这一点是前 提条件,下面是我们使用 Proxy 模拟的代理类工厂: public class ProxyClassFactory implements InvocationHandler{ private Object targetObject; public Object createProxyInstance(Object targetObject){ this.targetObject=targetObject; return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(), this.targetObject.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("进入方法"); Object result=method.invoke(targetObject,args); System.out.println("方法结束"); return result; } } 先简单介绍一下上面的类: Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(), this.targetObject.getClass().getInterfaces(), this); 该方法返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。 指定接口我们可以得到,类加载器也可以得到,只要写处理程序就可以了,上面我们把指定 程序调到本类上,而我们的 ProxyClassFactory 又实现了 InvocationHandler 接口,实现了接 口中的 invoke 方法,我们在调用方法之前和之后分别打印提示语句。接下来使用下我们的 代理对象工厂类。 public interface CardService { public void save(); public void delete(); public void update(); } public class CardServiceImpl implements CardService{ public void delete() { System.out.println("delete"); update(); } public void save() { System.out.println("save"); } public void update() { System.out.println("update"); } } public class Test { public static void main(String[] args) { ProxyClassFactory proxy=new ProxyClassFactory(); CardService card=(CardService)proxy.createProxyInstance(new CardServiceImpl()); card.save(); card.delete(); System.out.println(card.getClass().getName()); } } Main 方法输出如下: 进入方法 save 方法结束 进入方法 delete update 方法结束 $Proxy0 从上面可以看出来,在进入方法的时候会打印相应语句,在运行结束方法的时候会打印相应 语句。同样我们打印card对象可以得到他的真实本质(代理类)。还有就是我在原来的文章 中说过的: 我们在delete方法中调用了update方法,发现update方法并没有打印相应的进入方法提 示和退出方法提示,这也就证明了我们在spring管理事务的时候为什么同一个service调 用同一个service中的方法事务注解不管用。 我们知道使用Proxy实现代理必须有接口,这也是spring默认的实现AOP的方法,如果不 用默认的JDK提供的动态代理来实现AOP,我们还可以配置spring,使用cglib(第三方 jar包)来实现代理,具体如下: public class CGLIBProxy implements MethodInterceptor { private Object targetObject; public Object createProxyInstance(Object targetObject){ this.targetObject = targetObject; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.targetObject.getClass()); enhancer.setCallback(this);//设置回调用对象为本身,所以我们类中实现类 MethodInterceptor接口 return enhancer.create(); } public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { return methodProxy.invoke(this.targetObject, args); } } 使用cglib来实现代理,不需要接口,生成的代理类就是我们目标类的子类,代理类将重写 我们目标类的非final的方法。 注意spring是默认的是使用JDK来实现动态代理的,如果不进行特殊配置,都是使用这种 代理方式来实现的,也就是spring为什么要提倡面向接口编程的原因之一。比方说我们的 service层使用spring来进行事务管理,事务管理也是通过AOP来实现的。也就有了下面 的这个例子: @Service("transactionService") @Transactional public class TransactionServiceImpl implements TransactionService{ //业务实现 } 我们进行一下测试: ApplicationContext ac=new ClassPathXmlApplicationContext("spring/applicationContext-common.xml"); transactionService=(TransactionServiceImpl)ac.getBean("transactionService"); 这样的结果是得不到transactionService的,会报$Proxy19 cannot be cast to XXX 之类的错误,为什么了?因为我们使用了事务注解,而事务是通过代理来实现的,也就是我 们生成了TransactionServiceImpl的一个代理对象。这个代理对象按照我们的模拟来看是该 类的父类。所以肯定会有强制转型错误了。所以正确的写法是: transactionService=(TransactionService)ac.getBean("transactionService"); 如果不加事务注解的话,怎么写都没错误了,因为他根本没有代理之类的事情。。。
还剩4页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 15 金币 [ 分享pdf获得金币 ] 2 人已下载

下载pdf

pdf贡献者

guolei

贡献于2011-10-19

下载需要 15 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf