• Spring 08: AOP面向切面编程 + 手写AOP框架


    核心解读

    • AOP:Aspect Oriented Programming,面向切面编程
    • 核心1:将公共的,通用的,重复的代码单独开发,在需要时反织回去
    • 核心2:面向接口编程,即设置接口类型的变量,传入接口类型的参数,返回接口类型的返回值,调用接口类型的变量自身的实现方法
    • 图示

    image

    图示分析:例如,在开发中普遍需要使用到日志输出功能,会将日志输出功能大量耦合到项目的不同位置,如上图左侧所示。
    而日志输出功能与其实与项目本身的核心业务逻辑无关,我们只是为了不时的查看项目的运行状态。
    则可以将日志功能单独提出去开发,在需要的地方将日志输出功能(所谓:日志功能切面)反织回去即可,如上图右侧所示。

    手写AOP框架

    • 下面将手写5个版本的AOP框架,在版本的不断优化中,逐步理解AOP面向切面编程的核心,最后一个版本最接近Spring中AOP的原生实现
    • 业务功能和切面功能用简单的输出语句来模拟,主要是为了简洁直观的演示AOP核心思想
    • 手写的AOP框架的业务背景:图书购买业务

    项目结构

    • 5个AOP版本分别放在proxy01 ~ proxy05这5个包下,下图左侧为项目结构,右侧为各版本用到的接口和实现类

    image

    AOP版本1

    实体类

    • 整个版本1,只是一个BookService实体类,业务功能和切面功能严重耦合
    package com.example.proxy01;
    
    /**
     * 图书购买功能和事务切面功能耦合在一个类中
     */
    public class BookService {
        public void buy(){
            try{
                System.out.println("开启事务....");
                System.out.println("图书购买业务....");
                System.out.println("提交事务....");
            }catch (Exception e){
                System.out.println("回滚事务....");
            }
        }
    }
    

    测试

    • 调用BookService实体对象中的buy()方法即可

    AOP版本2

    优化原理

    • 通过子类代理来实现将业务功能和切面功能初步拆分解耦

    实体类

    • 实体类BookService及其子类SubBookService

    • BookService实体类

    package com.example.proxy02;
    
    /**
     * 图书购买功能
     */
    public class BookService {
        public void buy(){
            System.out.println("图书购买功能....");
        }
    }
    
    • SubBookService子类
    package com.example.proxy02;
    
    /**
     * 子类代理:将图书购买功能和事务切面划分到不同类中
     */
    public class SubBookService extends BookService{
        @Override
        public void buy() {
            try{
                System.out.println("开启事务事务....");
                super.buy();
                System.out.println("提交事务....");
            }catch (Exception e){
                System.out.println("回滚事务....");
            }
        }
    }
    

    测试

    • 调用SubBookService实体类对象中的buy()方法即可

    AOP版本3

    优化原理

    • 通过静态代理,可以进行受代理对象的灵活切换

    接口

    • Service接口
    package com.example.proxy03;
    
    /**
     * 静态代理接口
     */
    public interface Service {
        //定义业务功能
        void buy();
    }
    

    实现类

    • BookServiceImpl实现类
    package com.example.proxy03;
    
    /**
     * 目标对象
     */
    public class BookServiceImpl implements Service{
        @Override
        public void buy() {
            System.out.println("图书购买业务....");
        }
    }
    
    • ProductServiceImpl实现类
    package com.example.proxy03;
    
    /**
     * 另外一种业务功能的目标对象
     */
    public class ProductServiceImpl implements Service{
        @Override
        public void buy() {
            System.out.println("产品购买业务....");
        }
    }
    
    • 静态代理对象
    package com.example.proxy03;
    
    /**
     * 静态代理对象
     */
    public class Agent implements Service{
        //接口类型的参数
        Service target;
    
        //传入接口类型的参数,灵活调用多种Service接口的实现类
        public Agent(Service target){
            this.target = target;
        }
    
        @Override
        public void buy() {
            try{
                System.out.println("开启事务....");
                target.buy();
                System.out.println("提交事务....");
            }catch (Exception e){
                System.out.println("关闭事务....");
            }
        }
    }
    

    测试

    • 面向接口编程,可灵活代理多种Service接口的实现类,灵活切换受代理对象
    package com.example.test;
    
    import com.example.proxy03.Agent;
    import com.example.proxy03.ProductServiceImpl;
    import com.example.proxy03.Service;
    import org.junit.Test;
    
    public class TestProxy03 {
        @Test
        public void testProxy03(){
            //可以灵活切换受代理对象,因为接口类型的参数都能接住
            //Service agent = new Agent(new BookServiceImpl());
            Service agent = new Agent(new ProductServiceImpl());
            agent.buy();
        }
    }
    

    AOP版本4

    优化原理

    • AOP版本3中,虽然受代理对象可以灵活切换,但是不同的受代理对象被绑定到相同的切面功能,切面功能无法灵活切换

    • 可以将上述切面功能上升到接口层次,针对不同切面功能有不同实现类

    • 核心:就像Agent代理对象持有Service接口类型的变量一样,若持有切面接口类型的变量,则可以接收不同切面接口的实现类,实现不同切面功能的灵活切换

    • 考虑到切面功能出现在业务功能的前后关系,以及异常处理等情况,可以根据切面出现的时机定义切面接口中的方法

    • 推导出需要定义切面接口以及如何定义接口中方法的思路图示

    image

    接口

    • 业务接口:Service接口
    package com.example.proxy04;
    
    /**
     * 静态代理接口
     */
    public interface Service {
        //定义业务功能
        void buy();
    }
    
    • 切面接口:Aop接口
    package com.example.proxy04;
    
    /**
     * 自定义Aop,切面接口
     */
    public interface Aop {
        default void before(){}	//default关键字,jdk8的新特性,可以提供空实现,不强迫接口实现类实现所有接口中的方法
        default void after(){}      //实现类需要实现哪个方法就实现哪个方法
        default void exception(){}
    }
    

    实现类

    • 业务功能实现类:BookServiceImpl和ProductServiceImpl
    package com.example.proxy04;
    
    /**
     * 目标对象
     */
    public class BookServiceImpl implements Service {
        @Override
        public void buy() {
            System.out.println("图书购买业务....");
        }
    }
    
    
    package com.example.proxy04;
    
    public class ProductServiceImpl implements Service {
        @Override
        public void buy() {
            System.out.println("产品生产业务....");
        }
    }
    
    • 切面功能实现类:TransAopImpl和LogAopImpl
    package com.example.proxy04;
    
    public class TransAopImpl implements Aop{
        @Override
        public void before() {
            System.out.println("开启事务....");
        }
    
        @Override
        public void after() {
            System.out.println("提交事务....");
        }
    
        @Override
        public void exception() {
            System.out.println("回滚事务....");
        }
    }
    
    package com.example.proxy04;
    
    //切面接口中定义方法时使用了default,不必实现所有接口方法,按需实现方法即可
    public class LogAopImpl implements Aop{
        @Override
        public void before() {
            System.out.println("前置日志输出....");
        }
    }
    

    测试

    • 可以实现业务功能和切面功能的灵活组合
    • 而且就像下面第2个测试一样,因为代理对象也是Service的一个实现类,所以代理对象还可以再次被代理,一个业务功能被多个切面包围,实现多切面
    • 此时的版本已经很灵活,也已经揭示出了AOP面向切面编程的核心
    package com.example.test;
    
    import com.example.proxy04.*;
    import org.junit.Test;
    
    public class TestProxy04 {
    
        //测试:单个业务功能 + 单个切面功能
        @Test
        public void testProxy04(){
            //分别传入要完成的业务功能和要切入的功能,可以灵活组合,这里就可以有业务功能和切面功能的4种组合:2 x 2
            //Service agent = new Agent(new ProductServiceImpl(), new LogAopImpl());
            //Service agent = new Agent(new ProductServiceImpl(), new TransAopImpl());
            //Service agent = new Agent(new BookServiceImpl(), new LogAopImpl());
            Service agent = new Agent(new BookServiceImpl(), new TransAopImpl());
            agent.buy();
        }
    
        //测试:单个业务功能 + 多个切面功能(本例为:日志切面 + 事务切面)
        @Test
        public void testManyProxies(){
            Service agent = new Agent(new ProductServiceImpl(), new LogAopImpl());
            Service agent2 = new Agent(agent, new TransAopImpl());
            agent2.buy();
        }
    }
    

    AOP版本5

    优化原理

    • 静态代理可以做到受代理对象的灵活切换,但是不可以做到代理功能的灵活切换,就像我们用动态代理优化静态代理一样,还可以用jdk动态代理继续优化上述AOP版本4
    • 在Spring原生的AOP框架中,底层就是使用的jdk动态代理,AOP版本5最接近Spring原生AOP框架

    实体类

    • AOP版本5中除了用ProxyFactory代理工厂来动态获取代理对象外(不再写AOP版本4中的Agent类,4版本是静态的,现在不用写了),其他接口和实现类与AOP版本4完全一致,不再赘述

    • 新增ProxyFactory类,代替AOP版本4中的Agent类

    • 参数比较多,看起来有些乱(包涵 包涵),若对jdk动态代理不是很熟悉,可以参考mybatis博客集(mybatis 01 对jdk动态代理有详细讨论)

    package com.example.proxy05;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * 代理工厂,获取动态代理对象
     */
    public class ProxyFactory {
        
        //静态方法获取jdk动态代理对象:传入业务功能对象 + 切面功能对象
        public static Object getProxy(Service target, Aop aop){
            
            //该方法有三个参数,第三个参数是一个匿名内部类
            return Proxy.newProxyInstance(
                    //参数1               
                    target.getClass().getClassLoader(),
                    //参数2
                    target.getClass().getInterfaces(),
                
                    //参数3:匿名内部类重写的方法又有三个参数
                    //其中method用来反射调用外部调用的那个方法,args是调用目标方法时要传的参数
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
                            //用来存放目标对象被调用的方法的返回值
                            Object res = null;
                            try{
                                //切面功能
                                aop.before();
                                //业务功能,根据外部调用的功能,动态代理目标对象被调用的方法
                                res = method.invoke(target, args);
                                //切面功能
                                aop.after();
                            }catch (Exception e){
                                //切面功能
                                aop.exception();
                            }
                            //返回目标对象被调用的方法的返回值给外部调用者
                            return res;
                        }
                    }
            );
        }
    }
    

    测试

    package com.example.test;
    
    import com.example.proxy05.BookServiceImpl;
    import com.example.proxy05.ProxyFactory;
    import com.example.proxy05.Service;
    import com.example.proxy05.TransAopImpl;
    import org.junit.Test;
    
    public class TestProxy05 {
        //测试:AOP版本5
        @Test
        public void testProxy05(){
            //获取动态代理对象,传入业务功能对象和切面功能对象,这里传入的业务对象和切面对象可以有多种组合
            Service agent = (Service) ProxyFactory.getProxy(new BookServiceImpl(), new TransAopImpl());
            //完成业务功能和切面功能的组合
            agent.buy();
        }
    }
    

    测试输出

    开启事务....
    图书购买业务....
    提交事务....
    
    Process finished with exit code 0
    

    扩展测试

    • 为了体现动态代理的优点,并测试有参数和有返回值的方法都可被代理,为Service接口扩展功能:order(预定图书的功能)

    接口

    • Service接口新增order方法
    package com.example.proxy05;
    
    /**
     * 静态代理接口
     */
    public interface Service {
        //定义业务功能
        void buy();
        
        //新扩展一个预定功能
        default String order(int orderNums){return null;}
        //default,不强制实现类都实现该方法,按需实现
    }
    

    实现类

    • 让BookServiceImpl实现该新增的方法
    package com.example.proxy05;
    
    /**
     * 目标对象
     */
    public class BookServiceImpl implements Service {
        @Override
        public String order(int orderNums) {
            System.out.println("新预定图书: " + orderNums + " 册");
            return "预定成功";
        }
        @Override
        public void buy() {
            System.out.println("图书购买业务....");
        }
    
    
    }
    

    测试

    package com.example.test;
    
    import com.example.proxy05.BookServiceImpl;
    import com.example.proxy05.ProxyFactory;
    import com.example.proxy05.Service;
    import com.example.proxy05.TransAopImpl;
    import org.junit.Test;
    
    public class TestProxy05 {
        @Test
        public void testProxy05(){
            //获取动态代理对象,传入业务功能对象和切面功能对象
            Service agent = (Service) ProxyFactory.getProxy(new BookServiceImpl(), new TransAopImpl());
            //完成业务功能和切面功能的组合
            String res = agent.order(10);
            System.out.println("返回结果: " + res);
        }
    }
    

    测试结果

    开启事务....
    新预定图书: 10 册
    提交事务....
    返回结果: 预定成功
    
    Process finished with exit code 0
    

    小结

    • 在AOP版本3优化了业务功能(静态代理)
    • 在AOP版本4优化了切面功能(AOP面向切面编程)
    • 在AOP版本5优化了代理功能(jdk动态代理)
    • 此时手写的AOP版本5可以做到被代理对象的灵活切换,代理功能的灵活切换,业务功能和切面功能的灵活组合
    • AOP版本5最接近Spring中AOP的原生实现原理
  • 相关阅读:
    中国人保为新希望医疗器械承保产品责任险,为消费者保驾护航!
    矩阵的投影、线性拟合与最小二乘法
    LeetCode
    腾讯云-服务违规封禁提醒解决
    郑州共享美容院小程序开发如何操作?
    Unity Xlua热更新框架(六):场景管理
    Ubuntu1604创建SVN服务器
    物联网应用中蓝牙模块怎么选?_蓝牙模块厂家
    GitLab拉取分支代码命令
    音频的“隐形保镖”——音频数字水印
  • 原文地址:https://www.cnblogs.com/nefu-wangxun/p/16614357.html