AOP——面向切面编程(基于SpringAOP)
引言
在长久的学习中,我慢慢的理解OOP对于开发的便利和思想的进步,这种思维也逐渐影响了我对于开发逻辑的认知。所以一开始接触到AOP这个名词的时候,我便对这个新思维给予了足够的关注度。但是实际上在后期使用中我自认为对于SpringAOP的使用率低的主要原因还是不够熟练,所以决定做个记录和回忆
怎样理解AOP
什么是AOP
这里是摘自百度百科对AOP的解释:
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
在百度给出的解释中,称其是
“通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术”
这里我们要关注的有两个词:第一个是“代理”,第二个是“统一维护”
我们先从第二个来看,AOP归根结底也是一种隔离逻辑部分从而实现松散耦合的技术思想,那么怎么理解统一维护呢?
举个例子,当我们在项目开发中,想在功能实现时添加一些输出日志或者权限鉴定的功能,我们可能基于OOP要找到登陆注册购物车个人信息等等页面跳转的方法并添加权限判断和控制台输出。但是这种情况在大型项目中需要开发人员非常熟悉整体代码,并且一个相同的方法需要在不同的类中进行实现,这大大增加了代码的冗余。这还不是最致命的,在多人开发的过程中,这就不可避免的要修改其他人的代码,这可能会导致出现较大的问题。而“统一维护”即是把这些重用的方法抽象出来,抽象成插入程序的一个面。比如我想在登陆的时候输出日志,那么我需要关注的就是:登陆就是我关注的切入点,然后把日志功能切进去。
在我开始学习AOP的时候,我觉得这种思想在之前的学习中或多或少就已经有接触了,比如我们在写前端代码的时候会把常用的banner和footer单独创建然后包含到页面中,再比如我们在写后端代码的时候会把一些常用的工具类抽象出Util以便于使用和降低重复。
代理模式
刚才说了“统一维护”,接下来需要重点关注的就是“代理”。代理分为静态代理和动态代理,静态代理,顾名思义,就是你自己写代理对象,动态代理,则是在运行期,生成一个代理对象。我们分别来看:
之前看到一个抽象例子,即用租房来形容代理模式
我觉得这个有点太抽象了不好理解,在我看来,更简洁的表达也许更能容易理解:
之前A类自己做一件事,在使用代理之后,A类不直接去做,而是由A类的代理类B来去做。代理类其实是在之前类的基础上做了一层封装。
静态代理
在我学习代理模式的过程中,参照网上的一些文章和解析。我认为代理模式主要体现在两个方面:
-
代理类 与 委托类 具有相似的行为(共同)
-
代理类增强委托类的行为
看到个解释的时候,我第一反应的是当初在看《Java编程思想》的时候的第一章详细分析的java继承和实现的特性。而静态代理便是通过以开发者为第一人称实现的类的继承和重写,这样通过继承就可以实现原有类中的方法,并且在重写的过程中可以加入新的业务功能而不对原有的类进行任何侵入。
但是,因为静态代理的主动权是完全掌握在开发者手中,所以如果需要很多代理类,全部都是需要手动创建的并且是写死的,灵活性并不高。
动态代理
动态代理的实现离不开两个JDK提供的包
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
首先来看InvocationHandler,源码给出这是一个接口,其作用在文档中的介绍是这样的:
public interface InvocationHandler {
/**
* Processes a method invocation on a proxy instance and returns
* the result. This method will be invoked on an invocation handler
* when a method is invoked on a proxy instance that it is
* associated with.
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
翻译过来就是:处理代理实例上的方法调用并返回结果。该方法将在调用处理程序上调用在代理实例上调用方法时有关联。这个接口中只包含一个invoke方法,传入代理类,方法和参数。就算没有实际使用过,也能大概猜到这个接口被继承后重写invoke方法的作用是处理代理类的实际使用。
看看源码:
/**
* the invocation handler for this proxy instance.
* @serial
*/
protected InvocationHandler h;
/**
* Prohibits instantiation.
*/
private Proxy() {
}
/**
* Constructs a new {@code Proxy} instance from a subclass
* (typically, a dynamic proxy class) with the specified value
* for its invocation handler.
*
* @param h the invocation handler for this proxy instance
*
* @throws NullPointerException if the given invocation handler, {@code h},
* is {@code null}.
*/
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
其实proxy类中最需要注意的是getProxyClass()方法:
@CallerSensitive
public static Class<!--?--> getProxyClass(ClassLoader loader,
Class<!--?-->... interfaces)
throws IllegalArgumentException
{
final Class<!--?-->[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
return getProxyClass0(loader, intfs);
}
getProxyClass(ClassLoader, interfaces),只要你给它传入类加载器和一组接口,它就给你返回代理Class对象。其实这一步就相当于完全封装了由用户主动实例化代理对象的任务,最后再交由invoke重写。其实不管在实现过程中还是在引入包名的时候我们都不难发现,动态代理的实现底层就是映射。
这是我在知乎看到的一张思路比较清晰的图,直接拿来用了: