1.什么是代理?
生活中的代理是很常见的,比如代购、律师、中介
等,他们都有一个共性就是帮助被代理人处理一些前前后后的事情
。而被代理人只需要专注做自己要做的那部分事情就可以了
。当然这里的被代理人不可能是只有一个,假如只有一个那根本养不活这群代理。
Java中的代理也是类似的,代理可以帮助被代理者完成一些前期的准备工作和后期的善后工作,但是核心的业务逻辑仍然是由被代理者完成。就好比中介找房,你想要什么房中介都可以帮你找,但是钱还是得你出,房还是得你亲自看。
2.为什么要使用代理?
从生活的角度上来说,租房为什么需要中介呢?我们直接找房东不好吗,其实中介他起到的作用是汇集房源,假如我们直接找房东,先不说房东好不好找,房子肯定是有数的,一个房东顶多也就几套房,而一个中介可能顶上好几个房东的房源了。说直白点,租房是我们的目的,在不改变自己的目的情况下,快速的找到房源,那就是通过中介!
从代码角度上来说,在不修改源码的基础上对方法进行加强。当然不是所有代码涉及到增强就需要使用代理,而是很多方法都涉及到了增强,才会统一使用一个代理
,举例:我们要给所有的接口添加操作日志,这时候不可能说在每个接口上添加操作日志,这样会导致重复代码一大堆,所以这时候我们从中间抽出来一个代理,被代理类只需要专注于自己的核心代码即可,日志记录交给代理类就可以了,优点:使得代码更加简洁,分工明确。
3静态代理
代理模式分为动态代理
和静态代理
。两者的差别还是很大的,不过思想都是一样的,起到一个服务中介的作用。
所谓静态代理也就是在程序运行前就已经存在代理类的字节码文件,而动态代理是通过某种方式来生成的代理字节码文件。
3.1.静态代理的示例
以下是通过一个买车示例来演示静态代理的思想,实际开发当中我们可能不会这么写,但是更多的是需要理解他的思想!
(1)定义接口,接口当中有一个抽象方法
/**
* 买车接口
* @author guo
*
*/
public interface StaticMy {
void lawsuit();
}
(2)定义被代理类,实现StaticMy接口
package com.gzl.static1;
/**
* 被代理方 我
* @author guo
*
*/
public class StaticMyImpl implements StaticMy{
public void lawsuit() {
System.out.println("我要买车");
}
}
(3)代理类同样也需要实现StaticMy接口,将被代理类通过构造器的方式传入代理类,由代理类对被代理类进行加强
package com.gzl.static1;
public class Shop implements StaticMy{
private StaticMyImpl staticMy;
public Shop(StaticMyImpl staticMy) {
super();
this.staticMy = staticMy;
}
public void lawsuit() {
System.out.println("厂子进车");
this.staticMy.lawsuit();
System.out.println("交车");
}
}
(4)测试
package com.gzl.static1;
public class Test {
public static void main(String[] args) {
// 创建被代理类
StaticMyImpl shop = new StaticMyImpl();
// 将被代理类传入代理类当中
Shop shop2 = new Shop(shop);
// 由代理类来执行
shop2.lawsuit();
}
}
执行结果
4.动态代理
动态代理就是,在程序运行期,创建目标对象的代理对象
,并对目标对象中的方法进行功能性增强的一种技术。在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法。可以理解为运行期间,对象中方法的动态拦截,在拦截方法的前后执行功能操作
。
静态代理是直接在代码中声明好的代理对象,而动态代理中的代理对象,并不是事先在Java代码中定义好的。而是在运行期间,根据我们在动态代理对象中的“指示”,动态生成的。也就是说,你想获取哪个对象的代理,动态代理就会为你动态的生成这个对象的代理对象。动态代理一般有两种实现方式,cglib和jdk。
4.1.cglib和jdk动态代理的区别
- JDK代理使用的是反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
- CGLIB代理使用字节码处理框架ASM,对代理对象类的class文件加载进来,通过修改字节码生成子类。
- JDK创建代理对象效率较高,执行效率较低;
- CGLIB创建代理对象效率较低,执行效率高。
- JDK动态代理机制是委托机制,只能对实现接口的类生成代理,通过反射动态实现接口类;
- CGLIB则使用的继承机制,针对类实现代理,被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,因为是继承机制,不能代理final修饰的类。
JDK代理是不需要依赖第三方的库,只要JDK环境就可以进行代理,需要满足以下要求:
- 实现InvocationHandler接口,重写invoke()
- 使用Proxy.newProxyInstance()产生代理对象
- 被代理的对象必须要实现接口
CGLib 必须依赖于CGLib的类库,需要满足以下要求:
- 实现MethodInterceptor接口,重写intercept()
- 使用Enhancer对象.create()产生代理对象
4.2.cglib动态代理示例
jdk动态代理只能为接口创建代理,使用上有局限性。实际的场景中我们的类不一定有接口,此时如果我们想为普通的类也实现代理功能,我们就需要用到cglib来实现了。
cglib是一个强大、高性能的字节码生成库,它用于在运行时扩展Java类和实现接口;本质上它是通过动态的生成一个子类去覆盖所要代理的类
(非final修饰的类和方法
)。Enhancer可能是CGLIB中最常用的一个类,和jdk中的Proxy不同的是,Enhancer既能够代理普通的class,也能够代理接口
。Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。Enhancer不能够拦截final方法
,例如Object.getClass()方法,这是由于Java final方法语义决定的。基于同样的道理,Enhancer也不能对final类进行代理操作
。
CGLIB底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。除了CGLIB库外,脚本语言(如Groovy和BeanShell)也使用ASM生成字节码。ASM使用类似SAX的解析器来实现高性能。
(1)cglib并不是java当中自带的,所以使用的话需要引入jar包
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
</dependencies>
(2)创建一个类,写入两个方法
package com.itheima.cglib;
/**
* 一个生产者
*/
public class Producer {
/**
* 销售
* @param money
*/
public void saleProduct(float money){
System.out.println("销售产品,并拿到钱:"+money);
}
/**
* 售后
* @param money
*/
public void afterService(float money){
System.out.println("提供售后服务,并拿到钱:"+money);
}
}
(3)动态代理类
在源码当中看到MethodInterceptor就是使用的cglib动态代理
package com.gzl.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 模拟一个消费者
*/
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
/**
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于子类的动态代理:
* 涉及的类:Enhancer
* 提供者:第三方cglib库
* 如何创建代理对象:
* 使用Enhancer类中的create方法
* 创建代理对象的要求:
* 被代理类不能是最终类
* create方法的参数:
* Class:字节码
* 它是用于指定被代理对象的字节码。
*
* Callback:用于提供增强的代码
* 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
* 此接口的实现类都是谁用谁写。
* 我们一般写的都是该接口的子接口实现类:MethodInterceptor
*/
Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行producer的任何方法都会经过该方法
* @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;
}
});
cglibProducer.saleProduct(12000f);
}
}
执行结果
4.3.JDK动态代理示例
(1)创建一个接口
package com.gzl.proxy;
/**
* 对生产厂家要求的接口
*/
public interface IProducer {
/**
* 销售
* @param money
*/
public void saleProduct(float money);
/**
* 售后
* @param money
*/
public void afterService(float money);
}
(2)实现类
package com.gzl.proxy;
/**
* 一个生产者
*/
public class Producer implements IProducer{
/**
* 销售
* @param money
*/
public void saleProduct(float money){
System.out.println("销售产品,并拿到钱:"+money);
}
/**
* 售后
* @param money
*/
public void afterService(float money){
System.out.println("提供售后服务,并拿到钱:"+money);
}
}
(3)代理类
package com.gzl.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 模拟一个消费者
*/
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
/**
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于接口的动态代理:
* 涉及的类:Proxy
* 提供者:JDK官方
* 如何创建代理对象:
* 使用Proxy类中的newProxyInstance方法
* 创建代理对象的要求:
* 被代理类最少实现一个接口,如果没有则不能使用
* newProxyInstance方法的参数:
* ClassLoader:类加载器
* 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
* Class[]:字节码数组
* 它是用于让代理对象和被代理对象有相同方法。固定写法。
* InvocationHandler:用于提供增强的代码
* 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
* 此接口的实现类都是谁用谁写。
*/
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;
}
});
proxyProducer.saleProduct(12000f);
}
}
执行结果