大白话JAVA中的动态代理

1、何为代理模式

首先代理模式是一种设计模式。让别人帮忙去做一些事情,有点像中介,代替房东直接面对租户或者买东西找代购或者某些代理商,代替厂家对接客户。

简单来说就是使用代理对象代替真实对象的一系列操作,这样的好处可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。

代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。

2、静态代理

2.1、何为静态代理呢?

相当于在目标类上套了一层,这样就可以不改变目标类对原有功能进行扩展。【不过】他对目标对象每个方法的增强都是手动的,也就是说接口一旦有改动,目标类和代理类都要进行代码的维护,对于开发者来说这很麻烦且不优雅。

2.2、具体实现

2.2.1、定义买咖啡的接口

/**
 * 静态代理演示
 * 定义一个买咖啡的接口
 *
 * @author 阿尔卑斯大煎蛋
 */
public interface BuyCoffeeService {

    /**
     * 买杯椰云拿铁
     * @param latte 拿铁
     * @return 椰云拿铁
     */
    String hadBuy(String latte);
}

2.2.2、实现买咖啡的接口

/**
 * 静态代理 - 实现类
 *
 * @author 阿尔卑斯大煎蛋
 */
public class BuyCoffeeServiceImpl implements BuyCoffeeService {
    @Override
    public String hadBuy(String latte) {
        System.out.println("买了一杯:" + latte);
        return latte;
    }
}

2.2.3、创建代理类代替实现类实现买咖啡的接口

/**
 * 静态代理 - 代理类
 * 代理类实现买椰云拿铁的操作
 *
 * @author 阿尔卑斯大煎蛋
 */
public class BuyCoffeeServiceProxy implements BuyCoffeeService {

    private final BuyCoffeeServiceImpl buyCoffeeServiceImpl;

    public BuyCoffeeServiceProxy(BuyCoffeeServiceImpl buyCoffeeServiceImpl) {
        this.buyCoffeeServiceImpl = buyCoffeeServiceImpl;
    }

    @Override
    public String hadBuy(String latte) {
        System.out.println("买咖啡的前置操作.....");

        buyCoffeeServiceImpl.hadBuy(latte);

        System.out.println("买咖啡的后置操作.....");
        return null;
    }
}

2.2.4、进行实际测试

public static void main(String[] args) {
        BuyCoffeeServiceImpl buyCoffeeServiceImpl = new BuyCoffeeServiceImpl();
        BuyCoffeeServiceProxy proxy = new BuyCoffeeServiceProxy(buyCoffeeServiceImpl);
        proxy.hadBuy("耶云拿铁");
}

2.2.5、静态代理输出

在这里插入图片描述

3、JDK动态代理

首先动态代理的好处:我们不需要针对每个目标类都单独创建一个代理类,并且也不需要必须实现接口

JDK动态代码顾名思义,JDK提供的,接下来看看JDK动态代理的具体实现。

3.1、方法参数描述

Java动态代理类位于java.lang.reflect包下,有两个极其重要的参数

  • InvocationHandler接口
  • Proxy类

最核心的实现则是在Proxy类中的.newProxyInstance()方法,生成一个代理对象。

通过查看源码可知,他有三个参数

  • loader :类加载器,加载代理对象
  • interfaces : 被代理类实现的接口
  • h : 实现了 InvocationHandler 接口的对象

源码如下:

@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
        Objects.requireNonNull(h);

        final Class<?> caller = System.getSecurityManager() == null
                                    ? null
                                    : Reflection.getCallerClass();

        /*
         * Look up or generate the designated proxy class and its constructor.
         */
        Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);

        return newProxyInstance(caller, cons, h);
    }

通过源码可知,要实现动态代理,还必须要有实现InvocationHandler接口的对象,这个接口提供了invoke()方法,当要进行动态代理对象进行操作时,实际也是invoke代替我们去调用被代理对象的原生方法,并且可以在此方法里加入我们自己的逻辑,实现某些前置后置处理等。

invoke()源码如下,可以看到他同样有三个参数

  • proxy :动态生成的代理类
  • method : 与代理类对象调用的方法相对应
  • args : 当前 method 方法的参数
public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

3.2、具体实现

3.2.1、定义买咖啡接口

/**
 * @author 阿尔卑斯大煎蛋
 */
public interface BuyCoffeeService {
    String hadBuy(String latte);
}

3.2.2、买咖啡接口实现类

/**
 * 买咖啡接口 - 实现类
 *
 * @author 阿尔卑斯大煎蛋
 */
public class BuyCoffeeServiceImpl implements BuyCoffeeService {
    @Override
    public String hadBuy(String latte) {
        System.out.println("买了一杯:" + latte);
        return latte;
    }
}

3.2.3、定义JDK动态代理类

/**
 * JDK 动态代理实现类
 *
 * @author 阿尔卑斯大煎蛋
 */
public class JdkProxyInvocationHandler implements InvocationHandler {

    // 定义目标对象
    private final Object target;

    // 拿到目标对象
    public JdkProxyInvocationHandler(Object target) {
        this.target = target;
    }

    /**
     * JDK动态代理实际也是实现invoke()方法,invoke代替我们去调用被代理对象的原生方法
     * 自定义逻辑处理,进行方法的前置后置处理
     *
     * @param o      目标对象
     * @param method 原生方法
     * @param args   相关参数
     * @return Object
     * @throws Throwable e
     */
    @Override
    public Object invoke(Object o, Method method, Object[] args) throws Throwable {
        System.out.println("前置操作......调用被代理对象的原生方法:" + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("后置操作......调用被代理对象的原生方法:" + method.getName());
        return result;
    }
}

3.2.4、通过工厂获取代理对象

/**
 * 代理对象工厂类 - newProxyInstance 获取某个类的代理对象
 *
 * @author 阿尔卑斯大煎蛋
 */
public class JdkProxyFactory {
    public static Object getProxy(Object target) {
        return Proxy.newProxyInstance(
                // 目标类的类加载
                target.getClass().getClassLoader(),
                // 代理需要实现的接口,可指定多个
                target.getClass().getInterfaces(),
                // 代理对象对应的自定义 InvocationHandler
                new JdkProxyInvocationHandler(target)
        );
    }
}

3.2.5、具体实现

public static void main(String[] args) {
        BuyCoffeeService buyCoffeeService = (BuyCoffeeService) JdkProxyFactory.getProxy(new BuyCoffeeServiceImpl());
        buyCoffeeService.hadBuy("耶云拿铁");
}

3.2.6、JDK动态代理输出

在这里插入图片描述

4、CGLIB动态代理

4.1、什么是CGLIB动态代理

CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成,通过继承让代理类去继承目标类,并重写目标类的方法,而且还可以保证代理类拥有目标类的同名方法。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模块中,如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。SpringBoot2.X的默认代理方式也是CGLIB

4.1.1、核心类及接口

CGLIB 动态代理机制中 MethodInterceptor接口Enhancer 类是核心。我们需要自定义 MethodInterceptor 并重写 intercept() 方法,intercept 用于拦截增强被代理类的方法。通过 Enhancer类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的 intercept()方法

4.2、具体实现

如果是SpringBoot2.x 版本的可直接使用import org.springframework.cglib.proxy,否则请导包:

// cglib他实际也是一个开源项目
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

4.2.1、定义买咖啡的类

/**
 * 买咖啡实现类
 *
 * @author 阿尔卑斯大煎蛋
 */
public class BuyCoffeeServiceImpl{
    public String hadBuyCglib(String latte) {
        System.out.println("cglib买了一杯:" + latte);
        return latte;
    }
}

4.2.2、实现(方法拦截器)接口

/**
 * CGLIB 动态代理实现类
 *
 * @author 阿尔卑斯大煎蛋
 */
public class CglibProxyMethodInterceptor implements MethodInterceptor {
    /**
     * 通过拦截器的形式
     * @param o           目标对象
     * @param method      原生方法
     * @param args        方法入参
     * @param methodProxy 用于调用原始方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("前置操作......调用被代理对象的原生方法:" + method.getName());
        Object object = methodProxy.invokeSuper(o, args);
        System.out.println("后置操作......调用被代理对象的原生方法:" + method.getName());
        return object;
    }
}

4.2.3、通过工厂获取CGLIB代理对象

/**
 * CGLIB 代理对象工厂类 - newProxyInstance 获取某个类的代理对象
 *
 * @author 阿尔卑斯大煎蛋
 */
public class CglibProxyFactory {

    public static Object getProxy(Class<?> clazz) {
        // 创建动态代理增强类
        Enhancer enhancer = new Enhancer();
        // 设置类加载器
        enhancer.setClassLoader(clazz.getClassLoader());
        // 设置被代理类
        enhancer.setSuperclass(clazz);
        // 设置方法拦截器
        enhancer.setCallback(new CglibProxyMethodInterceptor());
        // 创建代理类
        return enhancer.create();
    }
}

4.2.4、具体实现

 public static void main(String[] args) {
        BuyCoffeeServiceImpl buyCoffeeService = (BuyCoffeeServiceImpl) CglibProxyFactory.getProxy(BuyCoffeeServiceImpl.class);
        buyCoffeeService.hadBuyCglib("椰云拿铁");
}

4.2.5、CGLIB动态代理输出

在这里插入图片描述

4、CGLIB 和 JDK 两者差别

JDK 原生动态代理和 cglib 动态代理。JDK 原生动态代理是基于接口实现的,代理生成的对象只能赋值给接口变量。而 cglib 是基于继承当前类的子类实现的,代理对象无论是赋值给接口还是实现类,这两者都是代理对象的父类。

cglib更符合现在的业务场景,基本一个service就是一个单独的实现,不需要通过接口的方式实现,而且Cglib也是SpringBoot2.X的默认代理方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值