Spring AOP是什么?你都拿它做什么?

转自:我叫刘半仙
https://my.oschina.net/liughDevelop/blog/1457097

为什么会有面向切面编程(AOP)?我们知道Java是一个面向对象(OOP)的语言,但它有一些弊端,比如当我们需要为多个不具有继承关系的对象引入一个公共行为,例如日志、权限验证、事务等功能时,只能在在每个对象里引用公共行为。这样做不便于维护,而且有大量重复代码。AOP的出现弥补了OOP的这点不足。

为了阐述清楚Spring AOP,我们从将以下方面进行讨论:

  1. 代理模式

  2. 静态代理原理及实践

  3. 动态代理原理及实践

  4. Spring AOP原理及实战

1. 代理模式

代理模式:为其他对象提供一种代理以控制对这个对象的访问。这段话比较官方,但我更倾向于用自己的语言理解:比如A对象要做一件事情,在没有代理前,自己来做;在对 A 代理后,由 A 的代理类 B 来做。代理其实是在原实例前后加了一层处理,这也是 AOP 的初级轮廓。

2. 静态代理原理及实践

静态代理模式:静态代理说白了,就是在程序运行前就已经存在代理类的字节码文件、代理类和原始类的关系在运行前就已经确定。废话不多说,我们看一下代码。为了方便阅读,博主把单独的 class 文件合并到接口中,读者可以直接复制代码运行:

package test.staticProxy; // 接口public interface IUserDao {    void save();    void find();} //目标对象class UserDao implements IUserDao{    @Override    public void save() {        System.out.println("模拟:保存用户!");    }    @Override    public void find() {        System.out.println("模拟:查询用户");    }} /**  * 静态代理  * 特点:  * 2\. 目标对象必须要实现接口  * 2\. 代理对象,要实现与目标对象一样的接口 */class UserDaoProxy implements IUserDao{     // 代理对象,需要维护一个目标对象    private IUserDao target = new UserDao();     @Override    public void save() {        System.out.println("代理操作: 开启事务...");        target.save();   // 执行目标对象的方法        System.out.println("代理操作:提交事务...");    }     @Override    public void find() {        target.find();    }}

测试结果:

image

静态代理虽然保证了业务类只需关注逻辑本身,代理对象的一个接口只服务于一种类型的对象。如果要代理的方法很多,势必要为每一种方法都进行代理。再者,如果增加一个方法,除了实现类需要实现这个方法外,所有的代理类也要实现此方法。增加了代码的维护成本。那么要如何解决呢?答案是使用动态代理。

3. 动态代理原理及实践

动态代理模式:动态代理类的源码是在程序运行期间,通过 JVM 反射等机制动态生成。代理类和委托类的关系是运行时才确定的。实例如下:

package test.dynamicProxy; import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy; // 接口public interface IUserDao {    void save();    void find();} //目标对象class UserDao implements IUserDao{     @Override    public void save() {        System.out.println("模拟: 保存用户!");    }     @Override    public void find() {        System.out.println("查询");    }} /** * 动态代理: * 代理工厂,给多个目标对象生成代理对象! * */class ProxyFactory {     // 接收一个目标对象    private Object target;     public ProxyFactory(Object target) {        this.target = target;    }     // 返回对目标对象(target)代理后的对象(proxy)    public Object getProxyInstance() {        Object proxy = Proxy.newProxyInstance(            target.getClass().getClassLoader(),  // 目标对象使用的类加载器            target.getClass().getInterfaces(),   // 目标对象实现的所有接口            new InvocationHandler() {            // 执行代理对象方法时候触发                 @Override                public Object invoke(Object proxy, Method method, Object[] args)                        throws Throwable {                     // 获取当前执行的方法的方法名                    String methodName = method.getName();                    // 方法返回值                    Object result = null;                    if ("find".equals(methodName)) {                        // 直接调用目标对象方法                        result = method.invoke(target, args);                    } else {                        System.out.println("开启事务...");                        // 执行目标对象方法                        result = method.invoke(target, args);                        System.out.println("提交事务...");                    }                    return result;                }            }        );        return proxy;    }}

测试结果如下:

image
IUserDao proxy = (IUserDao)new ProxyFactory(target).getProxyInstance();

其实是 JDK 动态生成了一个类去实现接口,隐藏了这个过程:

class $jdkProxy implements IUserDao{}

使用 JDK 生成的动态代理的前提是目标类必须有实现的接口。但这里又引入一个问题,如果某个类没有实现接口,就不能使用 JDK 动态代理。所以 CGLIB 代理就是解决这个问题的。

CGLIB 是以动态生成的子类继承目标的方式实现,在运行期动态的在内存中构建一个子类,如下:

CGLIB 使用的前提是目标类不能为 final 修饰。因为 final 修饰的类不能被继承。

现在,我们可以看看 AOP 的定义:面向切面编程,核心原理是使用动态代理模式在方法执行前后或出现异常时加入相关逻辑

通过定义和前面代码我们可以发现3点:

  • AOP 是基于动态代理模式。

  • AOP 是方法级别的。

  • AOP 可以分离业务代码和关注点代码(重复代码),在执行业务代码时,动态的注入关注点代码。切面就是关注点代码形成的类。

4. Spring AOP

前文提到 JDK 代理和 CGLIB 代理两种动态代理。优秀的 Spring 框架把两种方式在底层都集成了进去,我们无需担心自己去实现动态生成代理。那么,Spring是如何生成代理对象的?

  1. 创建容器对象的时候,根据切入点表达式拦截的类,生成代理对象。

  2. 如果目标对象有实现接口,使用 JDK 代理。如果目标对象没有实现接口,则使用 CGLIB 代理。然后从容器获取代理后的对象,在运行期植入“切面”类的方法。通过查看 Spring 源码,我们在 DefaultAopProxyFactory 类中,找到这样一段话。

image

简单的从字面意思看出:如果有接口,则使用 JDK 代理,反之使用 CGLIB ,这刚好印证了前文所阐述的内容。Spring AOP 综合两种代理方式的使用前提有会如下结论:如果目标类没有实现接口,且 class 为 final 修饰的,则不能进行 Spring AOP 编程!

知道了原理,现在我们将自己手动实现 Spring 的 AOP:

package test.spring_aop_anno; import org.aspectj.lang.ProceedingJoinPoint; public interface IUserDao {    void save();} // 用于测试 CGLIB 动态代理class OrderDao {    public void save() {        //int i =1/0; 用于测试异常通知        System.out.println("保存订单...");    }} //用于测试 JDK 动态代理class UserDao implements IUserDao {    public void save() {        //int i =1/0; 用于测试异常通知        System.out.println("保存用户...");    }} //切面类class TransactionAop {     public void beginTransaction() {        System.out.println("[前置通知]  开启事务..");    }     public void commit() {        System.out.println("[后置通知] 提交事务..");    }     public void afterReturing() {        System.out.println("[返回后通知]");    }     public void afterThrowing() {        System.out.println("[异常通知]");    }     public void arroud(ProceedingJoinPoint pjp) throws Throwable {        System.out.println("[环绕前:]");        pjp.proceed(); // 执行目标方法        System.out.println("[环绕后:]");    }}

Spring 的 XML 配置文件:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xmlns:context="http://www.springframework.org/schema/context"    xmlns:aop="http://www.springframework.org/schema/aop"    xsi:schemaLocation="        http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://www.springframework.org/schema/context        http://www.springframework.org/schema/context/spring-context.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop.xsd">    <!-- dao实例加入容器 -->    <bean id="userDao" class="test.spring_aop_anno.UserDao"></bean>    <!-- dao实例加入容器 -->    <bean id="orderDao" class="test.spring_aop_anno.OrderDao"></bean>    <!-- 实例化切面类 -->    <bean id="transactionAop" class="test.spring_aop_anno.TransactionAop"></bean>    <!-- Aop相关配置 -->    <aop:config>        <!-- 切入点表达式定义 -->        <aop:pointcut expression="execution(* test.spring_aop_anno.*Dao.*(..))" id="transactionPointcut"/>        <!-- 切面配置 -->        <aop:aspect ref="transactionAop">            <!-- 【环绕通知】 -->            <aop:around method="arroud" pointcut-ref="transactionPointcut"/>            <!-- 【前置通知】 在目标方法之前执行 -->            <aop:before method="beginTransaction" pointcut-ref="transactionPointcut" />            <!-- 【后置通知】 -->            <aop:after method="commit" pointcut-ref="transactionPointcut"/>            <!-- 【返回后通知】 -->            <aop:after-returning method="afterReturing" pointcut-ref="transactionPointcut"/>            <!-- 异常通知 -->            <aop:after-throwing method="afterThrowing" pointcut-ref="transactionPointcut"/>        </aop:aspect>    </aop:config></beans>

切入点表达式不在这里介绍。参考 Spring AOP 切入点表达式

代码的测试结果如下:

image

到这里,我们已经全部介绍完Spring AOP。回到开篇的问题,我们拿它做什么?

  1. Spring声明式事务管理配置:请参考博主的另一篇文章:分布式系统架构实战 demo:SSM+Dubbo

  2. Controller层的参数校验:参考 Spring AOP拦截Controller做参数校验

  3. 使用 Spring AOP 实现 MySQL 数据库读写分离案例分析

  4. 在执行方法前,判断是否具有权限

  5. 对部分函数的调用进行日志记录:监控部分重要函数,若抛出指定的异常,可以以短信或邮件方式通知相关人员。

  6. 信息过滤,页面转发等等功能

博主一个人的力量有限,只能列举这么多,欢迎评论区对文章做补充。

Spring AOP还能做什么,实现什么魔幻功能,就在于我们每一个平凡而又睿智的程序猿!

参考文章

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容