什么是动态代理
什么是代理模式(Proxy)
定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用
在代理模式中,是需要代理对象和目标对象实现同一个接口(如果是不同的接口,那就是适配器模式了)
代理模式是Java中常见的一种模式,英文名字叫走Proxy或者Surrogate,代理的本意是一个人代表另一个人,或者一个机构代表另一个机构,采取行动,因而,代理和现实生活中的中介有很大的类似,你买房子、卖房子,可以自己去操作,但是需要了解和买卖房产无关的细节,如契税等,而找一个中介,则不用关心这些与买卖房产无直接关系的中间细节,只关心业务本身。
为什么要用代理
最最最主要的原因就是,在不改变目标对象方法的情况下对方法进行增强,比如,我们希望对方法的调用增加日志记录,或者对方法的调用进行拦截,等等…
动态代理
在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。
动态代理和静态代理的区别
(1)Proxy类的代码被固定下来,不会因为业务的逐渐庞大而庞大;
(2)可以实现AOP编程,这是静态代理无法实现的;
(3)解耦,如果用在web业务下,可以实现数据层和业务层的分离。
(4)动态代理的优势就是实现无侵入式的代码扩展。
静态代理这个模式本身有个大问题,如果类方法数量越来越多的时候,代理类的代码量是十分庞大的。所以引入动态代理来解决此类问题
动态代理的两种方式以及区别
JDK动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
CGlib动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
区别:JDK代理只能对实现接口的类生成代理;CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。
总结:1.JDK代理使用的是反射机制实现aop的动态代理,CGLIB代理使用字节码处理框架asm,通过修改字节码生成子类。所以jdk动态代理的方式创建代理对象效率较高,执行效率较低,cglib创建效率较低,执行效率高;2.JDK动态代理机制是委托机制,具体说动态实现接口类,在动态生成的实现类里面委托hanlder去调用原始实现类方法,CGLIB则使用的继承机制,具体说被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口。
基于接口的动态代理(JDK动态代理)
/**
* 动态代理
* 特点:字节码随用随创建,随用随加载,不用上来就创建类
* 作用:不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理:涉及的类 Proxy JDK官方提供
* 基于子类的动态代理
*
* 基于接口的动态代理:
* 如何创建代理对象: 使用Proxy类中的newProxyInstance方法
* 创建代理对象的要求: 被代理对象最少实现一个接口,如果没有则不能使用
* newProxyInstance方法的参数:
* ClassLoader: 类加载器,它用于加载代理对象字节码,和被代理对象使用相同的加载器(固定写法)
* Class[]: 字节码数组,用于让代理对象和被代理对象有相同的方法(固定写法)
* InvocationHandle: 用于提供增强的代码
* 它是让我们写如何代理,我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的
* 此接口的实现类都是谁用谁写
*/
接口 IProducer.java
package com.proxy;
/**
* 对生产厂家要求的接口
*/
public interface IProducer {
public void saleProduct(float money);
public void afterService(float money);
}
实现类 Producer.java
package com.proxy;
/**
* 生产者
*/
public class Producer implements IProducer{
public void saleProduct(float money) {
System.out.println("销售产品,并拿到钱"+money);
}
public void afterService(float money) {
System.out.println("提供售后服务,并拿到钱"+money);
}
}
代理增强 Client.java
package com.proxy;
import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Client {
@Test
public void test1() {
final Producer producer = new Producer();
//producer.saleProduct(10000f);
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() {
/**
* 作用: 执行被代理对象的任何接口方法都会经过该方法
* @param proxy: 代理对象的引用
* @param method: 当前执行的方法
* @param args: 当前执行方法所需的参数
* @return 和被代理对象方法有相同的返回值
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1、获取方法执行的参数
Float money = (Float) args[0];
//2、判断当前方法是不是销售
if ("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer,money*0.8f);
}
return returnValue; //上面的producer需要用final修饰,在匿名内部类中使用必须用final修饰
}
});
proxyProducer.saleProduct(10000f); //这里的10000不加f也能运行成功
}
}
运行结果
基于子类的动态代理(CGlib动态代理)
首先导入jar包依赖
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<!--用于动态代理中的基于子类的动态代理-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
/**
* 动态代理
* 特点:字节码随用随创建,随用随加载,不用上来就创建类
* 作用:不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理:涉及的类 Proxy JDK官方提供
* 基于子类的动态代理:涉及的类 Enhancer 第三方cglib库提供
*
* 基于子类的动态代理:
* 如何创建代理对象: 使用Enhancer类中的create 方法
* 创建代理对象的要求: 被代理类不能是最终类,即不能被final修饰
* create方法的参数:
* Class: 字节码,它用于指定被代理对象的字节码
* Callback: 用于提供增强的代码
* 它是让我们写如何代理,我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的
* 此接口的实现类都是谁用谁写
* 我们一般写的都是该接口的子接口实现类:MethodInterceptor
*/
实现类 Producer.java
package com.cglib;
/**
* 生产者
*/
public class Producer {
public void saleProduct(float money) {
System.out.println("销售产品,并拿到钱"+money);
}
public void afterService(float money) {
System.out.println("提供售后服务,并拿到钱"+money);
}
}
代理增强 Client.java
package com.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.junit.Test;
import java.lang.reflect.Method;
public class Client {
@Test
public void test1() {
final Producer producer = new Producer();
Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行被代理对象的任何接口方法都会经过该方法
* @param proxy
* @param method
* @param args
* 以上三个参数和基于接口的动态代理中的invoke方法的参数是一样的
* @param methodProxy:当前执行方法的代理对象
* @return 一样的
* @throws Throwable
*/
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1、获取方法执行的参数
Float money = (Float) args[0];
//2、判断当前方法是不是销售
if ("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer,money*0.8f);
}
return returnValue; //上面的producer需要用final修饰,在匿名内部类中使用必须用final修饰
}
});
cglibProducer.saleProduct(12000f);
}
}
运行结果