8

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

 2 years ago
source link: https://www.cnblogs.com/nefu-wangxun/p/16614357.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
  • 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的原生实现原理

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK