Spring AOP术语解释和动态代理

1 AOP术语

1.1 连接点(JoinPoint)

类或者方法中具有边界性质的特定点(eg:类初始化前后,方法调用前后,方法抛出异常后),可以理解成为位置,只不过该位置是针对方法并且范围不精确。Spring仅支持方法级的连接点即仅能在方法调用前后,方法抛出异常时将对应的增强织入。连接点包含两个方面的信息:1.用方法表示的程序执行点即方法 2.用执行点的相对位置表示方位即方法调用前的位置。add(...)表示程序执行点,那么方位就是调用add()前的位置。

连接点有如下特点:1.边界性 2.执行点即调用方法的地方 3.方位即调用方法前后者的位置

1.2 切点(Pointcut)

定位特定的连接点。使用类和方法结合的方式查找具体特的连接点。在Spring中Pointcut表示切点。通俗的来说它就是一个条件。连接点表示位置,那么采用切点就可以找到一个精确的位置。切点含有定位连接点的信息

1.3 增强(Advice)

增强时织入目标类连接点的一段代码。增强除了用于描述一段代码外,还拥有另外一个和连接点相关的信息即执行点的方位,结合连接点和切点的信息,就可以找到特定的连接点。

连接点,切点,增强的关系描述如下:

通过特定的条件(切点)找到特定的位置(连接点)并将对应的逻辑(增强)织入。

1.4 目标对象(Target)

增强逻辑织入的目标类。

1.5 引介(Introduction)

也属于增强。能够为类添加属性和行为。可以动态的给业务类添加接口的实现逻辑,让业务类成为该接口的实现类

1.6 织入(Weaving)

将增强逻辑添加到目标对象特定连接点的动作。织入分为:编译器织入,类装载织入,动态代理织入:采用在运行时为目标类添加增强并生成子类

1.7 代理(Proxy)

增强织入后的结果类,它融合了目标对象和增强逻辑的代理类。根据实现的不同,代理类有可能实现了和目标对象相同的接口,也有可能是目标对象的子类

1.8 切面(Aspect)

有切点和增强组成,包含增强逻辑的定义也包含了连接点的定义。是切点,增强,连接点的综合

2 JDK动态代理

JDK动态代理底层采用的反射机制,该模式下两个比较重要的类

2.1 InvocationHandler

是一个接口,通过实现该接口定义增强,通过反射机制调用目标对象的逻辑,动态的将目标对象逻辑和增强编织在一起,该接口只有一个方法。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

proxy:为代理对象实例

Method:目标对象方法

args:目标对象方法参数

参数介绍中没有目标对象,可以通过构造方法或者setter设置目标对象

2.2 Proxy

利用InvocationHandler动态生成复合某个接口的实例,生成目标类的代理实例

protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
    Objects.requireNonNull(h);
    this.h = h;
}

public static Object newProxyInstance(ClassLoader loader,
                                      Class[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
   //clone所有接口
    final Class[] intfs = interfaces.clone();
    Class cl = getProxyClass0(loader, intfs);//返回代理类
    //创建实例
    return cons.newInstance(new Object[]{h});
}

private static Class getProxyClass0(ClassLoader loader,
                                       Class... interfaces) {
    //通过ProxyClassFactory生成接口代理类
    return proxyClassCache.get(loader, interfaces);
}

Proxy持有InvocationHandler实例并通过构造函数实例化

通过一个例子来说明JDK动态代理的逻辑

public class User {
}
public interface IUserDao {
    public void insertUser(User user);
}
public class UserDaoImpl implements IUserDao{
    //模拟数据插入动作
    @Override
    public void insertUser(User user) {
        System.out.println("插入User:" + user.getClass().getSimpleName());
    }
}
/**
 * 模拟Log插入数据库
 */
public class LogInvocationHandler implements InvocationHandler {
    //目标对象
    private Object targetObj;
    /通过构造方法初始化目标对象
    public LogInvocationHandler(Object obj) {
        this.targetObj = obj;
    }

    /**
     *
     * @param proxy 代理类实例
     * @param method 目标类方法
     * @param args 目标类方法参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        try {
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

        method.invoke(targetObj , args);

        System.out.println("插入DB信息: " + targetObj.getClass().getSimpleName() + "," +method.getName());

        return targetObj;
    }
}
public class Client {
    public static void main(String[] args) {

        IUserDao userDao = new UserDaoImpl();

        LogInvocationHandler logInvocationHandler = new LogInvocationHandler(userDao);
        //生成接口代理类
        IUserDao userDaoProxy = (IUserDao) Proxy.newProxyInstance(Client.class.getClassLoader(),
                userDao.getClass().getInterfaces(),
                logInvocationHandler);
        //实际调用的实际接口代理的API
        userDaoProxy.insertUser(new User());

        writeProxyClassToHardDisk("/Users/mark_lic/workspace/beanlife/aa.class" );



    }

    //输出接口代理类字节码
    public static void writeProxyClassToHardDisk(String path) {
        /*
第一种方法,这种方式在刚才分析ProxyGenerator时已经知道了
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", true);
第二种方法
获取代理类的字节码
*/

        byte[] classFile = ProxyGenerator.generateProxyClass("$proxy", UserDaoImpl.class.getInterfaces());

        FileOutputStream out = null;

        out = new FileOutputStream(path);
        out.write(classFile);
    }
}

接口代理类源码

public final class $proxy extends Proxy implements IUserDao {
    private static Method m3;
    //通过构造函数初始化
    public $proxy(InvocationHandler var1) throws  {
        super(var1);
    }

    
    public final void insertUser(User var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});//调用InvocationHandler子类中方法,子类中的方法含有增强逻辑以及目标对象方法的调用
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    static {
            m3 = Class.forName("proxy.IUserDao").getMethod("insertUser", Class.forName("proxy.User"));
    }

}

JDK动态代理流程:

1.根据接口动态生成接口代理类字节码文件,该代理类是Proxy和接口的子类

public final class $proxy extends Proxy implements IUserDao

2.在构造代理类实例时,通过静态块获得接口的Method实例并初始化InvocationHandler

return cons.newInstance(new Object[]{h})
static {
            m3 = Class.forName("proxy.IUserDao").getMethod("insertUser", Class.forName("proxy.User"));
    }
    public $proxy(InvocationHandler var1) throws  {
        super(var1);
    }

3.采用代理类实例调用接口方法,实际上调用的是InvocationHandler.invoke(....),此时的InvocationHandler是自定义的,该对象中包含了增强逻辑以及目标对象

super.h.invoke(this, m3, new Object[]{var1});

其中h就是自定的InvocationHandler对象

通过Proxy的方法可以看出,采用JDK代理的局限性:目标对象必须要实现对应的接口即这只能为接口创建代理

public static Object newProxyInstance(ClassLoader loader,
                                          Class[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException

3 CGLi b动态代理

底层采用的字节码技术,可以为一个类动态的创建子类,在子类中采用方法拦截的方式拦截父类方法的调用并将顺势织入增强

//定义自己的方法拦截器
public class UserServiceProxy implements MethodInterceptor {

    /**
     * @param obj "this", the enhanced object 实现MethodInterceptor接口的类的实例
     * @param method intercepted Method    需要拦截的方法
     * @param args argument array; primitive types are wrapped 拦截方法参数列表
     * @param proxy used to invoke super (non-intercepted method); may be called
     * as many times as needed 触发父类方法的对象
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("before");
        Object ret = proxy.invokeSuper(obj, args);
        System.out.println("after");
        return ret;
    }
}
public class UserService {
    public int update(int id){
        try {
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("update" + id);
        return id;
    }
}
public class Client {
    public static void main(String[] args) {
       
        Enhancer enhancer = new Enhancer();
        //设置生成类的父类
        enhancer.setSuperclass(UserService.class);
        //设置回调方法器参数为自定义拦截器器,最后增强目标类调用的是代理类对象intercept方法
        enhancer.setCallback(new UserServiceProxy());
        System.out.println(enhancer.create().getClass().getSuperclass());
        //创建代理对象
        UserService us = (UserService) enhancer.create();
        //调用该方法是会触发拦截器,控制权先交给拦截器,连接器执行完成后将控制权交给当前进程
        //也就是说在拦截器中实现了目标类的调用
        us.update(100);
        System.out.println("End");

    }
}

CGLib动态生成子类字节码暂时没有办法获取,所以无法分析具体的原理。好遗憾!

4 动态代理的缺点

1.目标类的所有方法都会得到增强

2.通过硬编码的方式指定织入逻辑即在目标方法执行前后都织入了逻辑

3.当不同的类需要增强时,需要编写对应的增强类,无法通用

你可能感兴趣的:(Spring,Spring基础学习笔记)