浅谈CGLIB,JDK的动态代理和SpringAOP实现的代理方式

前言

自己一直以来对于动态代理,静态代理和Spring的AOP一直搞不清楚所以然,一直有困惑,最近花了点时间,将这些概念区分了一下。


代理是什么?

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介

应用

最经典的应用就是Spring中的AOP(面向切面编程)。AOP对于看Java程序员应该都不陌生吧!不是很清楚也没关系,下面也会讲到!!!

众所周知,代理分为静态代理动态代理

1. 静态代理


我的理解是:静态代理 == 代理模式

代理模式

在编码的时候指定客户端调用代理类,而在代理类中使用硬编码的方式调用代理类,并在调用之前和之后可以加入增强的逻辑。这样的模式就是代理模式。看一张类图

静态代理就是代理模式的实现,优点和缺点也很明显。优点是简单好理解,缺点是当要被代理的类很多的时候就会增加很多的代理类,不容易维护。所以当只有被代理的类比较少 的时候可以使用静态代理

2. 动态代理

动态代理有两种:JDK的动态代理和CGLIB的动态代理

JDK动态代理

JDK的动态代理使用的时候反射技术,在程序运行的时候使用反射去增强被代理的方法,显然这种方式只能代理实现了接口的类。show code!!

被代理类的接口

/**
 * @Description 被代理类的接口
 * @Date 2021/4/15 17:26
 * @Author wdg
 */
public interface SayHello{
    void say();
}

实际被代理的类。要实现接口

/**
 * @Description 实际实现的类
 * @Date 2021/4/15 17:27
 * @Author wdg
 */
public class SayHelloImpl implements SayHello{
    @Override
    public void say() {
        System.out.println("Hello啊!!!");
    }
}

代理类。实现InvocationHandler接口并重写invok方法(这里可以看出:JDK代理使用的是反射技术)

/**
 * @Description 代理类,实现InvocationHandler接口
 * @Date 2021/4/15 17:29
 * @Author wdg
 */
public class MyInvocationHandler implements InvocationHandler {


    //要试用这个对象去调用被代理的原方法
    private Object target;
    //带参构造方法,参数是实际的被代理类的实例
    MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("看见JDK叔叔了");
        Object invoke = method.invoke(target, args);
        System.out.println("打过招呼了");
        return invoke;
    }
}

客户端调用

/**
 * @Description
 * @Date 2021/4/15 17:29
 * @Author wdg
 */
public class main {
    public static void main(String[] args) {
        //被委托类的具体实现实例
        SayHello sayHello = new SayHelloImpl();
        //代理类
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(sayHello);
        //获得代理的对像,动态编译class,在内存中操作
        SayHello newProxyInstance = (SayHello) Proxy.newProxyInstance(sayHello.getClass().getClassLoader(), sayHello.getClass().getInterfaces(), myInvocationHandler);
        //调用代理的方法
        newProxyInstance.say();
    }
}

输出

看见JDK叔叔了
Hello!!!
打过招呼了

如果你不使用接口的话调用会出现,类型不匹配异常(从这里可以知道JDK的动态代理只能代理有接口的类。如果我们要代理没有接口的类,这时候就会比较麻烦,此时一个叫做CGLIB的靓仔路过)

Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to com.com.jdkdproxy.SayHelloImpl
	at com.com.jdkdproxy.main.main(main.java:17)
CGLIB动态代理

CGLIB动态代理可以不要求被代理的类实现接口就可以实现代理,使用的使用FastClass的技术,比使用反射要效率更高。show code!!!

被代理的类

/**
 * @Description 被代理的类
 * @Date 2021/4/15 19:40
 * @Author wdg
 */
public class SayHello {
    public void say(){
        System.out.println("hello CGLIB...");
    }
}

代理类(重写的是intercept,可以不是invoke哦!)

/**
 * @Description 代理类实现MethodInterceptor(方法拦截器)
 * @Date 2021/4/15 19:41
 * @Author wdg
 */
public class InterceptedHandler implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("看见CGLIB叔叔来了");

        return methodProxy.invokeSuper(o,objects);
    }
}

客户端调用

/**
 * @Description 
 * @Date 2021/4/15 19:38
 * @Author wdg
 */
public class main {

    public static void main(String[] args) {
        SayHello sayHello = new SayHello();


        //获得Enhance实例。
        Enhancer enhancer = new Enhancer();
        //设置类加载器
        enhancer.setClassLoader(sayHello.getClass().getClassLoader());
        //设置代理类
        enhancer.setSuperclass(sayHello.getClass());
        //设置回调方法
        enhancer.setCallback(new InterceptedHandler());
        
        //调用
        SayHello sayHello1 = (SayHello) enhancer.create();
        sayHello1.say();
    }
}

输出

看见CGLIB叔叔来了
hello CGLIB...
动态代理的总结

上面只是对两个动态代理方式的简单的代码实现,至于其他的深入的原理感兴趣的可以自己去查资料学习。

  1. JDK的动态代理需要被代理的类实现接口,如果去代理一个没有实现接口的类会报异常
  2. CGLIB可以代理没有实现接口的类
  3. JDk使用的是反射技术
  4. CGLIB使用的是FastClass这种效率比反射要高。大致是对要被代理的类的方法建立的类索引,直接去根据索引调用被代理的方法。
Spring的AOP

首先AOP是什么? 引用一下官方回答

AOP是面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术,AOP是OOP的延续。

AOP在Spring中的也算是一个核心的东西了,它也是代理的一种实现。在AOP中JDK和CGLIB的动态代理都有使用到,而且还引入了AspectJ的一些概念,比如切点,切面和连接点以及AspectJ的注解等。但是没有使用AspectJ的的静态织入引擎。大致是因为AspectJ不受Spring控制吧。

  1. 先看看怎么使用AOP吧
    基于Mavend的项目,先导入aspect的依赖
    
        org.aspectj
        aspectjweaver
    

这里是基于注解实现方法的增强(实现controler的自定义验证和访问拦截)
创建一个自定义的注解

/**
 * @Description 自定义注解
 * @Date 2021/4/15 21:13
 * @Author wdg
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodPlus {
    String value() default "";
}

定义切面类。使用注解@Aspect使类成为一个切面类。使用@Component注解使得类被Spring管理成为Spring的bean

/**
 * @Description 切面类
 * @Date 2021/4/15 21:10
 * @Author wdg
 */
@Aspect
@Component
public class MyAspectJ {

    @Pointcut(value = "@annotation(com.aspectj.MethodPlus)")
    private void pointCut(){}

    @Around(value = "pointCut()")
    public Object before(ProceedingJoinPoint point){
        System.out.println("--环绕通知开始--");
        MethodSignature signature = (MethodSignature) point.getSignature();
        MethodPlus declaredAnnotation = signature.getMethod().getDeclaredAnnotation(MethodPlus.class);
        String value = declaredAnnotation.value();
        try {
            //反射拿到要切入的对象
            Class aClass = Class.forName(value);
            //获得方法
            Method vaildToken = aClass.getMethod("vaildToken");
            //执行方法
            vaildToken.invoke(aClass.newInstance());
            //执行后续
            Object result = point.proceed();
            return result;
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return null;
    }
}

自定义的拦截验证接口

/**
 * @Description 验证类的接口,定义方法只能实现这个方法去验证
 * @Date 2021/4/15 11:30
 * @Author wdg
 */
public interface VaildToken {

    void vaildToken();
}

实现验证接口

/**
 * @Description 实现的验证实现类
 * @Date 2021/4/15 11:36
 * @Author wdg
 */
@Component(value = "VaildTokenImpl")
public class VaildTokenImpl implements VaildToken{
    @Override
    public void vaildToken() {
        System.out.println("验证哦....");
    }
}

controller层的使用。使用value将自定义的验证类的全路径传入。即可在调用之前先使用自定义的验证类进行调用前的验证

/**
 * @Description 
 * @Date 2021/4/15 21:17
 * @Author wdg
 */
@RestController
public class AspectController {

    @MethodPlus(value = "com.aspectj.vaild.VaildTokenImpl")
    @GetMapping("getString")
    public String getString(){
        return "谁的呼唤我!!!";
    }
AOP总结
  1. AOP默认使用的是JDK的动态代理,但是当被代理的类没有接口的时候会使用CGLIB
  2. AOP使用了Aspect的一些概念,但是没有使用Aspect的静态编织引擎,感兴趣的可以了解Aspect

以上就是我自己对代理和AOP的理解,如果有错误可以指正,如果对大家有帮助,很是荣幸!

你可能感兴趣的:(spring,aop,spring,代理模式,静态代理,动态代理)