对于初学者也许Spring IoC相对好理解,你也许会觉得AOP就不好理解了,不要紧,这里博主带领一下大家理解AOP框架。在传统的Spring书籍中会对你介绍十分生涩难懂的,切面,切点,通知,织入等内容,好吧,我必须承认,这些对初学的码农确实很难理解,我们先把这些放下,这篇主要和大家玩约定编程。
首先我们先来一个简单到不能简单的服务接口:
package com.learn.spring.aop.service; public interface HelloService { public void sayHello(); }
这个接口够简单了吧,不用我解释的,然后给出实现类:
package com.learn.spring.aop.service.impl; import com.learn.spring.aop.service.HelloService; public class HelloServiceImpl implements HelloService { @Override public void sayHello() { System.out.println("hello world!!"); } }
典型的hello world,初学者的问题也没有问题,好吧,假设上面你给你写的,下面是博主设计的,首先是一个拦截器接口:
package com.learn.spring.aop.proxy; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public interface Interceptor { // 前置通知 void before(); // 是否采用环绕通知 boolean useAround(); // 环绕通知 Object around(Object target, Method method, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException; // 事后方法 void after(); // 异常返回方法 void afterThrowing(); // 正常返回方法 void afterReturning(); }
依据这个接口,你是否可以给出一个很简单的实现:
package com.learn.spring.aop.proxy; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class MyInterceptor implements Interceptor { @Override public void before() { System.out.println("事前通知"); } @Override public boolean useAround() { return false; } @Override public Object around(Object target, Method method, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { System.out.println("环绕通知"); //环绕通知 return method.invoke(target, args); } @Override public void after() { System.out.println("事后通知"); } @Override public void afterThrowing() { System.out.println("异常返回通知"); } @Override public void afterReturning() { System.out.println("正常返回通知"); } }好吧,对于你而言,也不难吧。
好了,来点好玩的,然后我给你一个ProxyBean(com.learn.spring.aop.proxy.ProxyBean)的静态方法:
/** * 绑定代理对象 * @param target 服务对象 * @param interceptor 拦截器 * @return 代理对象,该对象你可以强制转换为服务对象的接口类型 */ public static Object getProxyBean(Object target, Interceptor interceptor)
然后我们暂且把这个方法返回的对象,记为proxy。
通过注释我们可以知道,我们如果使用ProxyBean.proxy(new HelloServiceImpl (), interceptor),就会返回一个对象proxy,按照约定:它可以通过强制转换为HelloService。例如:
HelloService helloService = new HelloServiceImpl(); HelloService proxy = (HelloService) ProxyBean.getProxyBean(helloService, new MyInterceptor());然后你就可以通过proxy去调用方法了,好了,来点好玩的。
如果你用proxy去调用方法,博主和你约定如下(这是一次很重要的约定哦):
1、首先调用拦截器的before方法;
2、如果拦截器的useAround方法,返回为true,则执行拦截器的around方法,不执行target对象原有的sayHello方法;
3、无论结果如何都会执行拦截器的after方法;
4、如果around方法有异常或者原有的target对象的sayHello方法有异常,则执行拦截器的afterThrowing方法,否则执行afterReturning方法。
好了,为了更好的说明,我们来个流程图。
好了,依据我们的约定,你的代码将会被织入这个流程中,假设你再开发中使用了这样的代码进行测试。
package com.learn.spring.aop.main; import com.learn.spring.aop.proxy.MyInterceptor; import com.learn.spring.aop.proxy.ProxyBean; import com.learn.spring.aop.service.HelloService; import com.learn.spring.aop.service.impl.HelloServiceImpl; public class ProxyMain { public static void main(String[] args) { HelloService helloService = new HelloServiceImpl(); HelloService proxy = (HelloService) ProxyBean.getProxyBean(helloService, new MyInterceptor()); proxy.sayHello(); } }
那么其结果按照我们约定的流程图,就是:
事前通知 hello world!! 事后通知 正常返回通知
然后我们把MyInterceptor中的useAround方法,修改为返回true,于是就可以看到:
事前通知 环绕通知 hello world!! 事后通知 正常返回通知
跟着我们修改拦截器的around方法,让它抛出异常,如下所示
@Override public Object around(Object target, Method method, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { System.out.println("环绕通知"); if (args == null || args.length == 0) { throw new RuntimeException("测试异常"); } //环绕通知 return method.invoke(target, args); }
于是得到这样的打印:
事前通知 环绕通知 事后通知 异常返回通知
这一切都是按照我们流程约定来的,你应该照着我们约定的流程图看这些结果。换句话说,在你知道前面的东西后,你就可以按照约定编程了,你是不需要知道博主是怎么做的,对吧。因为我们有约定,按约定就可以了,何必知道那么底层呢?这也是设计者的思维,设计者希望开发者的东西越简单就越好。吐舌头
那么博主是怎么做到将你开发的服务和拦截器织入到流程图中的呢?一切的奥妙当然是我给你的ProxyBean了,
不过在此之前你需要学习一下动态代理,可以看看博主的文章:
MyBATIS插件原理第一篇——技术基础(反射和JDK动态代理)
ps:在Spring中可以使用JDK动态代理,也可以玩CGLIB动态代理,在默认的情况下Spring会以这样的规则来服务:存在接口,使用JDK动态代理,因为JDK动态代理,必须需要接口,而不存在接口则使用CGLIB动态代理。
好了,我们现在公布ProxyBean的代码:(要懂得动态代理才能看懂,菜鸟补习去吧)
package com.learn.spring.aop.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyBean implements InvocationHandler { private Object target = null; private Interceptor interceptor = null; /** * 绑定代理对象 * @param target 被代理对象 * @param interceptor 拦截器 * @return 代理对象 */ public static Object getProxyBean(Object target, Interceptor interceptor) { ProxyBean proxyBean = new ProxyBean(); // 保存被代理对象 proxyBean.target = target; // 保存拦截器 proxyBean.interceptor = interceptor; // 生成代理对象 Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), proxyBean); // 返回代理对象 return proxy; } /** * 处理代理对象方法逻辑 * @param proxy 代理对象 * @param method 当前方法 * @param args 运行参数 * @return 方法调用结果 * @throws Throwable 异常 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //异常标志 boolean exceptionFlag = false; Object retObj = null; //前置通知 this.interceptor.before(); try { //判断是否启用环绕通知 if (this.interceptor.useAround()) { //环绕通知 retObj = this.interceptor.around(target, method, args); } else { //调用目标对象原有方法 retObj = method.invoke(target, args); } } catch (Exception ex) { //异常标志 exceptionFlag = true; } //事后通知 this.interceptor.after(); if (exceptionFlag) { //异常返回通知 this.interceptor.afterThrowing(); return null; } else { //正常返回通知 this.interceptor.afterReturning(); return retObj; } } }
首先,在getProxyBean方法中,博主将你的服务对象target和拦截器保存起来,然后生成一个代理对象,并且委托给ProxyBean的invoke方法去执行,于是当你用proxy对象调用方法的时候,就会进入到invoke方法中了,然后博主把你的代码,按约定的那样织入到流程图中,于是你就可以按照我们的约定编程了。这里强烈建议初学的同学对invoke方法,这段代码进行一步步调试。这样你理解AOP就快很多。
好了,今天谈了约定编程,如果是我来设计,我倒不希望你知道ProxyBean的源码的内容,值需要知道我和你约定的内容,就可以了,这就叫做封装掉,最难理解的内容。
你可以看到,按照约定的规则,博主就可以将你的代码织入到约定的流程中,同样的Spring也是可以做到的,这就是AOP的内容,而实际上你调试这个例子,你就基本懂得了AOP,下一篇,我们把这篇的概念换一下,你就知道原来AOP也就是那么回事。