AOP介绍

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码降低模块间的耦合度,并有利于未来的可拓展性和可维护性

关注点分离:不同的问题交给不同的部分去解决

  • 面向切面编程AOP正是此种技术的体现
  • 通用化功能代码的实现,对应的就是所谓的切面(Aspect)
  • 业务功能代码和切面代码分开后,架构将变得高内聚低耦合
  • 确保功能的完整性:切面最终需要被合并到业务中(织入 Weave)

AOP的三种织入方式

  • 编译时织入:需要使用特殊的Java编译器,如AspectJ
  • 类加载时织入:需要使用特殊的Java编译器,如AspectJ,AspectWerkz
  • 运行时织入:Spring采用的方式,通过动态代理的方式,实现简单

@Aspect定义切面、@Pointcut定义切入点、@Before,@AfterReturning定义切入时间

AOP的主要名词概念

  • Aspect:通用代码的实现
  • Target:被织入Aspect的对象
  • Join Point:可以作为切入点的机会。所有方法都可以作为切入点
  • Pointcut:Aspect实际被应用在的Join Point,支持正则
  • Adivice:类里面的方法以及这个方法如何织入到目标方法的方式
  • 前置通知:Before
  • 后置通知:AfterReturning
  • 异常通知:AfterThrowing
  • 最终通知:After
  • 环绕通知:Around
  • Weaving:Aop的实现过程

Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib ,这时候Spring AOP会使用 Cglib 生成一个被代理对象的子类来作为代理:

Aop的实现:JdkProxy和Cglibj

JdkProxy

动态代理的主要特点就是能够在程序运行时JVM才为被代理对象生成代理对象。

常说的动态代理也叫做JDK代理也是一种接口代理,JDK中生成代理对象的代理类就是Proxy,所在包是java.lang.reflect,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class)

每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。

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

proxy:  指代我们所代理的那个真实对象
method:  指代的是我们所要调用真实对象的某个方法的Method对象
args:  指代的是调用真实对象某个方法时接受的参数

Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法,这个方法的作用就是得到一个动态的代理对象,其接收三个参数

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

loader:  一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载

interfaces:  一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了

h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

代码示例:

//接口
public interface Subject
{
    public void rent();
    
    public void hello(String str);
}
//真实对象
public class RealSubject implements Subject
{
    @Override
    public void rent()
    {
        System.out.println("I want to rent my house");
    }
    
    @Override
    public void hello(String str)
    {
        System.out.println("hello: " + str);
    }
}
//动态代理类
public class DynamicProxy implements InvocationHandler
{
    //这个就是我们要代理的真实对象
    private Object subject;
    
    //构造方法,给我们要代理的真实对象赋初值
    public DynamicProxy(Object subject)
    {
        this.subject = subject;
    }
    
    @Override
    public Object invoke(Object object, Method method, Object[] args)
            throws Throwable
    {
        //在代理真实对象前我们可以添加一些自己的操作
        System.out.println("before rent house");
        
        System.out.println("Method:" + method);
        
        //当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        method.invoke(subject, args);
        
        //在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("after rent house");
        
        return null;
    }

}
//使用
public class Client
{
    public static void main(String[] args)
    {
        //    我们要代理的真实对象
        Subject realSubject = new RealSubject();

        //    我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
        InvocationHandler handler = new DynamicProxy(realSubject);

        /*
         * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
         * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
         * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
         * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
         */
        Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);
        System.out.println(subject.getClass().getName());
        subject.rent();
        subject.hello("world");
    }
}
//输出
$Proxy0

before rent house
Method:public abstract void com.xiaoluo.dynamicproxy.Subject.rent()
I want to rent my house
after rent house

before rent house
Method:public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)
hello: world
after rent house

可能我以为返回的这个代理对象会是Subject类型的对象,或者是InvocationHandler的对象,结果却不是,首先我们解释一下为什么我们这里可以将其转化为Subject类型的对象?原因就是在newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是Subject类型,所以就可以将其转化为Subject类型了。

同时我们一定要记住,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。

Cglib代理

上面的静态代理和动态代理模式有个相同点就是都要求目标对象是实现一个接口的对象,然而并不是任何对象都会实现一个接口,也存在没有实现任何的接口的对象,

这时就可以使用继承目标类以目标对象子类的方式实现代理,这种方法就叫做:Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.

使用JDK动态代理有一个限制,就是被代理的对象必须实现一个或多个接口,若想代理没有实现接口的类,就需要使用Cglib实现.

public class CglibProxy {

    public static void main(String[] args) {
        
        int[] arr = new int[100000];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) (Math.random() * 1000);
        }
        //实例化一个增强器,也就是cglib中的一个class generator
        Enhancer enhancer = new Enhancer();
        //设置目标类
        enhancer.setSuperclass(ArraySort2.class);
        //设置拦截对象,这里直接使用匿名内部类写法
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object object , Method method, Object[] args, MethodProxy proxy) throws Throwable {
                String sortName = method.getName();
                switch (sortName) {
                case "bubbleSort":
                    sortName = "冒泡排序";
                    break;
                case "selectSort":
                    sortName = "选择排序";
                    break;
                case "quickSort":
                    sortName = "快速排序";
                    break;
                default:
                    break;
                }
                long start = System.currentTimeMillis();
                //此处一定要使用proxy的invokeSuper方法来调用目标类的方法
                proxy.invokeSuper(object, args);
                long end = System.currentTimeMillis();
                System.out.println("本次" + sortName + "的执行时间为: " + (end -start) + "ms");
                return null;
            }
            
        });
        //生成代理类并返回一个实例
        ArraySort2 arraySort = (ArraySort2) enhancer.create();
        arraySort.bubbleSort(arr);
        arraySort.selectSort(arr);
        arraySort.quickSort(arr);
    }
    
}
class ArraySort2{
    public void quickSort(int[] arr) {
        Arrays.sort(arr);
    }
    public void selectSort(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            for (int j = i+1; j < arr.length; j++) {
                if (arr[i] > arr[j]) {
                    int temp = 0;
                    temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
            }
        }
    }
    public void bubbleSort(int[] arr) {
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    int temp = 0;
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
    }
}
  • 有AopProxyFactory根据AdvisedSupport对象的配置来决定
  • 默认策略如果目标类是接口,则用JDKProxy来实现,否则使用后者
  • JDKProxy的核心:InvocationHandler接口和Proxy类
  • Cglib:以继承的方式动态生成目标类的代理
  • JDKProxy:通过java的内部反射机制实现,反射机制在生成类的过程中比较高效
  • Cglib:借助ASM实现,在生成类之后的执行过程中比较高效
Spring里的代理模式的实现
  • 代理模式:接口+真实实现类+代理类
  • 真实实现类的逻辑包含在了getBean方法里
  • getBean方法返回的实际上是Proxy的实例
  • Proxy实例是Spring采用JDKProxy或CGLIB动态生成的