AOP是什么:
Spring的AOP是通过动态代理模式,带来管控各个对象操作的切面环境,管理包括日志、数据库事务等操作,让我们拥有可以在反射原有对象方法之前正常返回、异常返回事后插入自己的逻辑代码的能力,有时候甚至取代原始方法。(这是来自《Java EE 互联网轻量级框架整合开发》中的说法)
用我的话来说是:不修改源代码的情况下给程序动态统一添加功能。
现在要自主实现AOP,那么先来详细探讨一下AOP的研发需求。
需求分析:
01、用户可以选择代理模式(两种代理模式:JDK和CGLib);
02、用户可以选择类及方法,进行拦截;
03、对于一个类的同一个方法,允许形成拦截器链(栈);
04、允许“前置拦截”、“后置拦截”和“异常拦截”;
05、上述三种拦截可以只实现局部;
06、前置拦截除完成用户指定功能外,允许用户终止方法的执行;
07、第6点若发生终止情况,则,拦截器链应该终止;
08、允许前置拦截更改参数值;
09、允许后置拦截更改返回值;
10、拦截器的拦截功能,应该由用户决定;
11、AOP功能应该和IoC功能合并;即,默认从BeanFactory中获取代理对象;
对于第01点,我们需要建立两种代理模式,JDK和CGLiBb,其实之前在我们的博客中,已经建立了这两种代理机制。但是,我们之前建立的代理工具存在缺陷:无法形成拦截器链。现在我们需要将他们与AOP结合起来,就需要增加拦截器链,也就意味着,我们需要增加拦截器链这一成员;
对于第02点,可以通过建立拦截器与被拦截方法的映射关系完成;
从第03点到第10点,均是围绕拦截器而言;
对于第11点,其中IoC部分,我们需要实现在BeanFactory中存储的BeanDefinition的对象,需要注意到有两种情况:原对象和代理对象。
综上所述,要实现AOP,我们现在的重点就是要形成一个拦截器,要包含前置后置拦截,还允许形成链,并且是基于代理机制的拦截器。
因此,我们先讨论拦截器的实现:
用面向对象的编程思想,分析、解构上述要求:
代理类分为:最终代理类、JDKProxy和CGLibProxy;
方法的代理执行类;
拦截器链(栈);
拦截器接口;
我们需要建立三个注解:@Before、@After、@ThrowException
被上述三个注解注解的方法,应该被当成拦截器中的对应拦截;根据要执行的方法,寻找拦截器链;
不分开前置、后置等拦截器链,封装到一起;拦截器链作为一个独立的类。拦截器链是链式结构,或者说是一个链表。拦截器本身是一个类。因为,前置拦截、后置拦截动作、所需参数都不同,所以应该加以区分。并且用接口实现前置拦截方法的描述。
将上述思路变为框图:
介绍一下这些类,正如图上所说的,有一个总拦截器类,里面包括了对于一个方法的前置拦截链和后置拦截链,每个拦截器链由若干个拦截器节点组成。每个拦截器节必须要设置拦截的方法,以便对方法进行拦截。上面的这些类,主要时实现了拦截器如何形成链,执行拦截器方法的时候,如何按照拦截器顺序执行。
请看代码:
public class IntercepterLink {
private JoinPoint joinPoint;
private BeforeIntercepterLink beforeLink;
private AfterIntercepterLink afterLink;
public IntercepterLink() {
this.beforeLink = null;
this.afterLink = null;
}
void setJoinPoint(JoinPoint joinPoint) {
this.joinPoint = joinPoint;
}
JoinPoint getJoinPoint() {
return joinPoint;
}
// 在这里先判断,是否前置拦截器链已经生成,如果没有生成,
// 将重新生成,否则,直接进行添加
public void addBeforeIntercepter(IBefore before) {
if (beforeLink == null) {
beforeLink = new BeforeIntercepterLink();
}
beforeLink.addBeforeIntercepter(before);
}
// 和增加拦截器同理
public void addAfterIntercepter(IAfter after) {
if (afterLink == null) {
afterLink = new AfterIntercepterLink();
}
afterLink.addAfterIntercepter(after);
}
// 进行拦截器方法的执行
// 先检测拦截器链是否存在,
// 如果存在,那么先进行第一个拦截器方法的执行,然后第二个拦截器,以此类推。
// 否则,直接返回true,表示,前置拦截已经结束。
boolean before() {
if (beforeLink == null) {
return true;
}
return beforeLink.before();
}
// 与迁至拦截类似
Object after(Object result) {
if (afterLink == null) {
return result;
}
return afterLink.after(result);
}
Object[] getArgs() {
return joinPoint.getArgs();
}
public void setArgs(Object[] args) {
joinPoint.setArgs(args);
}
public Object getResult() {
return joinPoint.getResult();
}
public void setResult(Object result) {
joinPoint.setResult(result);
}
}
public class BeforeIntercepterLink {
private IntercepterBeforeNode head;
public BeforeIntercepterLink() {
this.head = null;
}
// 将传递过来的IBefore接口实现类对象生成一个拦截器节点,并进行判断
// 如果拦截器的头节点还是空,那么将这个节点作为头节点,
// 否则,将这个节点增加到下一个节点之后
void addBeforeIntercepter(IBefore before) {
IntercepterBeforeNode node =
new IntercepterBeforeNode(before);
if (head == null) {
head = node;
} else {
head.addNode(node);
}
}
// 判断头节点是否存在,
// 如果存在,直接执行本拦截方法;
// 否则,返回true,表示执行完毕
boolean before() {
if (head == null) {
return true;
}
return head.doBefore();
}
}
public class IntercepterBeforeNode {
private IBefore before;
private IntercepterBeforeNode next;
IntercepterBeforeNode() {
this.next = null;
}
IntercepterBeforeNode( IBefore before) {
this.before = before;
this.next = null;
}
void setBefore(IBefore before) {
this.before = before;
}
// 判断传递过来的拦截器类型的节点能否增加到这个节点后面的方法:
// 检测这个节点的下一个是否为空,如果为空,可以将传递过来的节点放到这个节点之后;
// 否则,继续进行遍历,直到最后一个。
void addNode(IntercepterBeforeNode node) {
if (next == null) {
next = node;
return;
}
next.addNode(node);
}
// 执行本节点的拦截方法,
// 如果执行完该拦截器的方法,发现方法的返回值时false,
// 那么不管后面时候还有拦截方法未执行,直接结束拦截;
// 否则,判断这是不是最后一个拦截器,如果是,直接返回执行结果。
// (这时不用考虑是不是执行成功和失败,因为,不管成功与失败,都要进行返回,
// 至于true还是false怎么处理,那是由代理来编写的,与这一层无关。)
// 如果不是最后一个拦截器,那么继续进行下一个拦截器的拦截方法的执行,
// 直到最后一个执行完或者中途返回false;
boolean doBefore() {
boolean ok = before.before();
if (!ok) {
return ok;
}
if (next != null) {
return next.doBefore();
}
return ok;
}
}
public interface IBefore {
boolean before();
}
public class AfterIntercepterLink {
private IntercepterAfterNode head;
AfterIntercepterLink() {
this.head = null;
}
// 与前置拦截类似
public void addAfterIntercepter(IAfter after) {
IntercepterAfterNode node = new IntercepterAfterNode();
node.setAfter(after);
if (head == null) {
head = node;
return;
}
head.addNode(node);
}
// 与前置拦截类似
public Object after(Object result) {
if (head == null) {
return result;
}
return head.doAfter(result);
}
}
public class IntercepterAfterNode {
private IAfter after;
private IntercepterAfterNode next;
IntercepterAfterNode() {
this.next = null;
}
void setAfter(IAfter after) {
this.after = after;
}
// 和前置拦截的类似
void addNode(IntercepterAfterNode node) {
if (next == null) {
this.next = node;
return;
}
next.addNode(node);
}
// 这里与前置拦截不一样
// 前置拦截拦截顺序是顺着,而后置拦截是逆着
// 所以采用的是“起泡”方式拦截
// 先检测是否是最后一个拦截器节点,如果不是,继续遍历,直到最后一个为止
// 逆序执行拦截方法,将结果返回给result,直到全部执行完返回最终的result
Object doAfter(Object result) {
if (next == null) {
return after.after(result);
}
// 希望这里用“起泡”方式调用后置拦截
result = next.doAfter(result);
result = after.after(result);
return result;
}
}
public interface IAfter {
Object after(Object result);
}
上述代码的讲解,已经写在了注释里面。总结一下,上面的代码其实前置拦截与后置拦截的形成和执行过程很类似,唯一有很大区别的是前置拦截和后置拦截的执行顺序,下面画个图来说明。
至此拦截器的初始化我们已经完成。但是我们在何时调用?如何调用这些类去生成拦截器呢?
我们应该根据拦截器和其拦截的方法之间的映射,在扫描他们的映射关系时,形成拦截器链。再用代理机制来调用拦截器,对方法进行拦截。这也是我们测试时候的执行顺序。
现在讨论拦截器和其拦截的方法之间的映射关系:
从哪里得到映射关系?
映射关系如何表达?
映射关系的存储结构?
首先先明确一个问题:
无论如何表达映射关系,都不能对被拦截的方法及其类做任何修改!因此,在被拦截方法及其类之外表达它们是唯一选择。在具体表达被拦截的方法及其类的同时,可能需要考虑拦截器本身的表达.即,在完成拦截器的同时,说明其拦截的方法及类。
再考虑到:同一个方法的拦截器可能包括前置、后置等多个具体拦截操作,每一个操作其实是一个方法。
例如:
@Before
public void fun1(JointPoint jp) {
...
}
@After
public void fun2() {
...
}
@Before
public void fun3() {
...
}
对于上述三个拦截器方法,可以都对同一个被拦截方法进行拦截;也可以对不同的被拦截方法进行拦截。所以无法制造以被拦截方法为key,拦截器链为值的映射关系。Spring的execution的字符串其实是用了“正则表达式”技术进行匹配。
正则表达式的作用是:字符串匹配。
因此,对于映射关系,不再能使用以方法为键,以拦截器链为值的Map。我们需要对拦截器方法进行配置(用注解或xml进行配置);而对于配置,需要用正则表达式。
其实,写到映射关系的时候,我不得不佩服spring的开发者们的细致、耐心、思维的缜密。因为,现在写到这里,我已经发现了很多疑问:
拦截器的方法所需参数传递过来的时候,参数个数不确定?有可能少传递一个参数?有可能类型不正确?如何检测?
如何将被拦截的方法的信息(包括方法名字,参数类型,参数的值等等)传递过来?
如何将拦截后的方法的返回值信息传递回去?等等……
在看了一些spring实现有关映射关系的源码的时候,看到了一些实现方式,我大致写一下我所看到的实现方式:
先实现注解配置:
@PointCut("") 这个value属性的类型是字符串,且表达所拦截
的方法及其类;
@Before("") 这个value属性的类型是字符串,且这个字符串与上述
PointCut所注解的方法对应。
@After("") 这个value属性的类型是字符串,且这个字符串与上述
PointCut所注解的方法对应。
这三个注解的应用方式如下:
// 这个类是最终要被拦截的类和方法:
public class Some {
// 这是要被拦截的第一个方法
String doSome(int num, String str, Complex c) {
// ...
return "...";
}
// 这是要被拦截的第二个方法
String doSome(String str, int sum) {
// ...
return ...;
}
}
// 这是包含了拦截方法的类
@Component
public class MyIntercept {
@PointCut("*execution( * com.mec.*.doSome(...))")
public void cutOne() {
}
// 第一种实现方案
@Before("cutOne()")
public boolean bInter(int number, String str) {
// 这里进行参数类型判断,有可能类型不正确,,
// 这时决定是否拦截。
if (number < 0) {
return false;
}
return true;
}
// 第二种实现方案
@Before("cutOne()")
public boolean bInter(JoinPoint joinPoint) {
// 通过JoinPoint,来得到具体的方法参数类型等,
// 然后检测这些是否满足拦截的要求,然后决定是否拦截
Method method = joinPoint.getMethod();
String methodName = method.getName();
if (methodName.equals("doSome")) {
int number = (int) joinPoint.getArgs[0];
if (number < 0) {
return false;
}
}
return true;
}
@After("cutOne()")
public String afterDoSome(JoinPoint joinPoint) {
// 先识别拦截的方法和类是否是我的菜
// 同样通过JoinPoint,来传递要拦截的方法的信息。然后进行检测
// 检测没问题再进行拦截
}
@After("cutOne()")
public String afterDoSomeone() {
// ...
}
@After("cutOne()")
public String afterDoSome(ReturnValue returnValue) {
// ReturnValue类有两个成员,一个是返回值类型,一个是返回值,
// 这里可以“干扰”返回值结果
}
}
这些代码就是一个对源码实现的一个简易实现。其中它定义了JoinPoint类,用来表示所拦截的方法的信息。上面的代码其实已经展现了好多种情况,把这些情况分别进行处理是非常繁琐的。
如果要把拦截器链和拦截的方法形成一个键值对的话。我觉得应该将PointCut的字符串可以作为键,其值是拦截器链!
现在,我强行规定:After注解的方法,其参数只能有三种可能性:(难道Before没有这个问题)
1、无参;
2、JoinPoint
3、ReturnValue
// 后面已经完成,就差前面这点了
在spring中,切点方法所在的类可以是拥有接口的类,也可以是没有接口的类,所以是否拥有接口不是使用SpringAOP的一个强制要求。在之前的博客代理机制(工具思想)介绍动态代理模式中介绍过,使用JDK动态代理时,必须拥有接口,而使用CGLib则不需要,于是Spring就提供了一个规则:当类的实现存在接口的时候,Spring将提供JDK动态代理,从而织入各个通知;而当类不存在接口的时候没有办法使用JDK动态代理,Spring会采用CGLIB来生成代理对象。
于是,我们要将我们之前写的代理工具拿过来,并要进行修改,因为代理工具里面不能形成拦截器链。这里我们要将拦截器链与代理机制捆绑起来。
首先,我们要形成我们两种代理机制。
public class JDKProxy {
private INewInstanceMaker maker;
private IIntercepter intercepter;
public JDKProxy() {
}
public void setMaker(INewInstanceMaker maker) {
this.maker = maker;
}
public void setIntercepter(IIntercepter intercepter) {
this.intercepter = intercepter;
}
@SuppressWarnings("unchecked")
public <T> T getProxy(Class<?> klass) {
try {
Object target = null;
target = maker == null
? klass.newInstance()
: maker.getNewInstance(klass);
return (T) getProxy(target);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
@SuppressWarnings("unchecked")
public <T> T getProxy(T target) {
Class<?> klass = target.getClass();
ClassLoader classLoader = klass.getClassLoader();
Class<?>[] interfaces = klass.getInterfaces();
return (T) Proxy.newProxyInstance(classLoader, interfaces,
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
MethodInvoker methodInvoker =
new MethodInvoker(target, method, args);
methodInvoker.setIntercepter(intercepter);
Object result = methodInvoker.methodInvoke();
return result;
}
});
}
}
public class CGLibProxy {
private INewInstanceMaker maker;
private IIntercepter intercepter;
public CGLibProxy() {
}
public void setMaker(INewInstanceMaker maker) {
this.maker = maker;
}
public void setIntercepter(IIntercepter intercepter) {
this.intercepter = intercepter;
}
@SuppressWarnings("unchecked")
public <T> T getProxy(Class<?> klass) {
try {
Object target = null;
target = maker == null
? klass.newInstance()
: maker.getNewInstance(klass);
return (T) getProxy(target);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
@SuppressWarnings("unchecked")
public <T> T getProxy(T target) {
Class<?> klass = target.getClass();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(klass);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object object, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
MethodInvoker methodInvoker =
new MethodInvoker(target, method, args);
methodInvoker.setIntercepter(intercepter);
Object result = methodInvoker.methodInvoke();
return result;
}
});
return (T) enhancer.create();
}
}
这两个类与之前博客写过的大致相似代理机制(工具思想)。只是比原来多了两个成员,INewInstanceMaker 和 IIntercepter 。其中,INewInstanceMaker 这个接口,主要实现以下方法。
public interface INewInstanceMaker {
Object getNewInstance(Class<?> klass);
}
其实这个就是可以获取到被代理的类的对象,如果没有获取到,就重新生成一个,如果获取到了,就直接使用这个对象。而IIntercepter 接口,实现以下方法:
public interface IIntercepter {
boolean before(Object object, Object[] args);
Object after(Object result);
}
很明显可以看出,这个接口定义了前置拦截方法和后置拦截方法,但是它们不是真正的拦截器链,只是用来调用拦截器链的方法。
接下来要生成调用代理机制的类:MecProxy:
ublic class MecProxy {
public static final int JDK_PROXY = 0;
public static final int CGLIB_PROXY = 1;
private int proxyType = JDK_PROXY;
private INewInstanceMaker maker;
private IIntercepter intercepter;
public MecProxy() {
}
public void setMaker(INewInstanceMaker maker) {
this.maker = maker;
}
public void setIntercepter(IIntercepter intercepter) {
this.intercepter = intercepter;
}
public void setProxyType(int proxyType) {
if (proxyType != JDK_PROXY && proxyType != CGLIB_PROXY) {
proxyType = JDK_PROXY;
}
this.proxyType = proxyType;
}
@SuppressWarnings("unchecked")
public <T> T getProxy(Class<?> klass) {
Object target = null;
if (maker == null) {
try {
target = klass.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} else {
target = maker.getNewInstance(klass);
}
return (T) getProxy(target);
}
@SuppressWarnings("unchecked")
public <T> T getProxy(Object target) {
if (proxyType == JDK_PROXY) {
JDKProxy jdkProxy = new JDKProxy();
jdkProxy.setMaker(maker);
jdkProxy.setIntercepter(intercepter);
return (T) jdkProxy.getProxy(target);
}
CGLibProxy cgLibProxy = new CGLibProxy();
cgLibProxy.setMaker(maker);
cgLibProxy.setIntercepter(intercepter);
return (T) cgLibProxy.getProxy(target);
}
}
这个类其实在之前的博客也写到过,与上面的两种代理机制一样,都增加了两个成员,其实代理机制类里面的两个成员,都是由这个MecProxy类在调用代理机制时设置进去的。很好理解,这里不再过多解释。
我们还需要一个执行刚才IIntercepter 接口里面所定义的两个方法的类:MethodInvoker类:
public class MethodInvoker {
private Object object;
private Method method;
private Object[] paras;
public MethodInvoker(Object object, Method method, Object[] paras) {
this.object = object;
this.method = method;
this.paras = paras;
}
public void setObject(Object object) {
this.object = object;
}
public void setMethod(Method method) {
this.method = method;
}
public void setParas(Object[] paras) {
this.paras = paras;
}
public Object methodInvoke() throws Throwable {
// 这里的代码应该由想要实现AOP功能的程序员来编写
IntercepterLink intercepterLink = new IntercepterLink();
// 上述intercepterLink应该是根据当前method,
// 在拦截器映射中找到的,而不是现在这样new出来的!
JoinPoint joinPoint = new JoinPoint();
joinPoint.setKlass(object.getClass());
joinPoint.setMethod(method);
joinPoint.setParaTypes(method.getParameterTypes());
joinPoint.setArgs(paras);
intercepterLink.setJoinPoint(joinPoint);
if (intercepterLink != null) {
boolean ok = intercepterLink.before();
if (!ok) {
return null;
}
}
Object result = method.invoke(object, paras);
joinPoint.setResult(result);
if (intercepterLink != null) {
result = intercepterLink.after(result);
}
// 这里的代码应该由想要实现AOP功能的程序员来编写
return result;
}
}
intercepterLink 的实现方式应该是由映射关系得到,由于现在我们对于映射关系没有实现,只是梳理清楚了思路,这里只能先进行new(),表示前置拦截和后置拦截默认执行成功。
写完这些类,我们的思路更加明确了,我们已经实现了整体思路:首先,我们需要根据拦截器与被拦截的方法的映射关系进行拦截器的初始化,然后用户可以选择不同的代理机制,对方法进行拦截,只需要我们在MethodInvoker类调用methodInvoke()方法即可执行。因为methodInvoke()方法里面已经形成了拦截器链,而且他会自动的进行前置拦截和后置拦截。
最后,我们的自主实现基于代理机制的拦截器算是真正实现了,我们也讨论了一下aop中拦截器和其拦截的方法之间的映射关系,并且也试着去实现,但是发现其中的内容太过于繁杂,需要考虑的因素太多,以至于最后没有代码实现。我们大概理清了aop的实现思路,同时也发现了它其中很难实现的地方,为什么会很难实现,这对我们以后编程也提供了思考和借鉴。