Spring之代理模式与动态代理(jdk和CGLib实现)

这一篇文章来说一说aop的前置:代理模式的思想

代理模式

一张图:
Spring之代理模式与动态代理(jdk和CGLib实现)_第1张图片

代理类被授权用来代表普通类,从而实现对普通类功能上的增强。

  1. 代理存在的意义就是代表另一个事物。
  2. 代理至少需要完成(或实现)它所代表的事物的功能。

静态代理

静态代理定义

静态代理是指已经创建好的代理类,代理类在编译期就已经是确定存在的。

  • 抽象角色:通常是接口,定义一组行为规范
  • 真实角色:实现接口的普通类
  • 代理角色:实现同一接口,并且对普通类进行增强的代理类

Spring之代理模式与动态代理(jdk和CGLib实现)_第2张图片

静态代理实例

以卖房子为例:

共同接口House

/**
 * 有关房子功能的接口
 */
public interface House {
    void sale();
}

普通用户类Person

/**
 * 用户类
 */
public class Person implements House {

    private String name;

    public Person(String name) {
        this.name = name;
    }

    public void sale(){
        System.out.println(name + "=>卖房子");
    }
}

代理类Agent

/**
 * 中介事务所 - 帮忙代理卖房子
 * 静态代理
 */
public class Agent implements House {

    /**
     * 代理对象
     */
    Person target;

    public Agent(Person target) {
        this.target = target;
    }

    @Override
    public void sale() {
        target.sale();
        System.out.println("卖房完成,收取了中介费");
    }
}

测试运行类Main

public class Main {

    public static void main(String[] args) {
        Person person = new Person("且听风吟丶");
        Agent proxy = new Agent(person);
        proxy.sale();
    }
}

输出结果:
Spring之代理模式与动态代理(jdk和CGLib实现)_第3张图片

静态代理缺点

我们不常用静态代理,主要是因为:

  • 静态代理在编译期就确定了其代理对象,但是如果这个代理对象是未知的,就无法使用静态代理
  • 每个已知的普通类都需要有其专用的静态代理类,这样如果有100个类,即使是进行同一个操作,也需要创建额外的100个静态代理类

动态代理

动态代理是在运行期利用反射机制生成代理类

动态代理技术的实现有两种:

  • JDK动态代理
  • CGLib动态代理

jdk动态代理

jdk动态代理介绍

jdk动态代理只能代理接口,所以目标对象必须有接口,如果没有接口就不能实现jdk动态代理

代理类和目标类都实现同一个接口,那么代理类和目标类的方法名就一样了
Spring之代理模式与动态代理(jdk和CGLib实现)_第4张图片

jdk动态代理实例

为了体现代理接口而实现的动态代理特性,这里假设有两个接口被代理:

House接口和Car接口,其下都有sale方法:

/**
 * 有关房子功能的接口
 */
public interface House {
    void sale();
}
/**
 * 有关车功能的接口
 */
public interface Car {
    void sale();
}

接下来创建两个不同的普通类Person1和Person2,分别实现House和Car接口:

/**
 * 用户1
 */
public class Person1 implements House {

    private String name;

    public Person1(String name) {
        this.name = name;
    }

    @Override
    public void sale(){
        System.out.println(name + "=>卖房子");
    }
}

/**
 * 用户2
 */
public class Person2 implements Car {

    private String name;

    public Person2(String name) {
        this.name = name;
    }

    @Override
    public void sale(){
        System.out.println(name + "=>卖车");
    }
}

接下来创建一个动态代理类Agent,该类拦截并检测名为sale的方法,并且收取中介费:

/**
 * 基于jdk动态代理实现的代理类
 */
@SuppressWarnings("all")
public class Agent implements InvocationHandler {

    /**
     * 要代理的对象 - 这里已经可以是多个实现了统一接口的类,而不是单独某个类
     */
    private Object target;

    public Agent(Object target) {
        this.target = target;
    }

	/**
	 * 以当前类信息为基础,创建代理对象并且返回
	 */
    public <T> T getProxy(){
        return (T) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        /*
            proxy表示动态代理类实例,method表示拦截的方法,args表示拦截方法的参数
         */
        Object returnValue = null;

        if ("sale".equals(method.getName())){

            returnValue = method.invoke(target, args);
            
            System.out.println("检测到执行sale,收取中介费");

            System.out.println("代理类实例:" + proxy);
            System.out.println("拦截方法:" + method);
            System.out.println("方法参数:" + Arrays.toString(args));
        }
        return returnValue;
    }
}

最后是测试运行类Main:

public class Main {
    public static void main(String[] args) {
        // 创建两个普通类对象Person1,Person2
        House person1 = new Person1("用户111");
        Car person2 = new Person2("用户222");

        // 代理类的类型是接口类型
        House proxy1 = new Agent(person1).getProxy();
        Car proxy2 = new Agent(person2).getProxy();

        proxy1.sale();
        System.out.println("==========");
        proxy2.sale();
    }
}

需要注意的是:创建的普通类需要使用接口类型接收,否则在动态代理过程中会报如下转型错误:

java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to XXX

运行结果如下:

用户111=>卖房子
检测到执行sale,收取中介费
代理类实例:null
拦截方法:public abstract void jdkProxy.House.sale()
方法参数:null
==========
用户222=>卖车
检测到执行sale,收取中介费
代理类实例:null
拦截方法:public abstract void jdkProxy.Car.sale()
方法参数:null

可以看到,在普通类实现了接口之后,使用jdk代理就可以对不同的接口实现进行代理操作,但是前提必须是普通类实现了一个接口,没有接口是不行的。、

jdk动态代理缺点

  • 存在约束:目标对象一定要有接口,没有接口就不行实现动态代理

CGLib动态代理

CGLib动态代理介绍

cglib也叫子类代理,从内存中构建出一个子类来扩展目标对象的功能

cglib是一个开源的强大的高性能代码生成宝,可以在运行期扩展Java类与实现Java接口,广泛被AOP框架使用,如Spring,可以提供方法的拦截

cglib动态代理是代理类去继承目标类,然后重写其中目标类的方法啊,这样也可以保证代理类拥有目标类的同名方法

Spring之代理模式与动态代理(jdk和CGLib实现)_第5张图片
注意:被代理类不能是final类型的,因为无法被继承;如果目标对象的某些方法是static、final类型的,也不会被拦截。

CGLib动态代理实例

首先需要导入cglib的支持Jar包,这里直接导入了spring-core的jar包即可:

spring-core-5.2.3.RELEASE2

接下来创建普通类Person:

public class Person {
    private String name;

    public Person() {
    }

    public void sale(){
        System.out.println(name + " 卖房子");
    }

    public void setName(String name) {
        this.name = name;
    }
}

CGlib has one important restriction: the target class must provide a default constructor.

If you use property-based injection instead of constructor-based injection, the problem will go away.

翻译:必须有空构造器,不然会报错如下:

Superclass has no null constructors but no arguments were given

因为cglib每次都是使用默认构造函数来创建代理对象,因此创建实际对象的时候无论怎样赋值都是无效的

接下来创建cglib代理类Agent:

public class Agent implements MethodInterceptor {

    private Object target;

    public Agent(Object target) {
        this.target = target;
    }

    /**
     * 获取代理对象
     * @return 代理对象proxy
     */
    public Object getProxy(){

        // cglib的工具类,用来创建子类(代理对象)
        Enhancer en = new Enhancer();
        // 设置代理对象父类
        en.setSuperclass(target.getClass());
        // 设置回调函数
        en.setCallback(this);
        return en.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

        // 执行原对象的方法
        Object returnValue = methodProxy.invokeSuper(obj, args);

        // 代理执行内容
        System.out.println("收取中介费...");

        return returnValue;
    }

    public Object getTarget() {
        return target;
    }

    public void setTarget(Object target) {
        this.target = target;
    }
}

最后是测试运行类Main:

public class Main {
    public static void main(String[] args) {
        Person person = new Person("且听风吟丶");
        Person proxy = (Person) new Agent(person).getProxy();
        proxy.sale();
    }
}

输出结果如下:

null 卖房子
收取中介费...

可以看到,即使表面上使用了含有name参数的构造器,实际在创建代理对象的时候使用的仍然是空构造器,因此参数name为null

CGLib动态代理缺点

  • 需要依赖第三方jar包

Spring选择用JDK还是CGLiB的依据

  1. 当Bean实现接口时,Spring就会用JDK的动态代理

  2. 当Bean没有实现接口时,Spring使用CGlib是实现

  3. 可以强制使用CGlib

(在spring配置中加入

你可能感兴趣的:(Java-Spring,java,spring,proxy,aop,cglib)