前言
为了更好的理解代理模式,首先根据生活中实际场景进行模拟,让我们在生活中去体验设计思想的美妙。
场景描述
“病从口入”这句成语告诉我们注意饮食健康,小六同学想吃苹果,在吃苹果之前需要清洗一下苹果和洗一下手,吃完苹果后,需要洗一下手保持个人卫生;十分钟后。。。小六同学又想吃一个大鸭梨,清洗鸭梨--洗手--吃鸭梨--吃完洗手。
代码模拟
苹果和鸭梨都属于食物,创建一个食物接口
public interface Foods {
void eatApple();
void eatpear();
}
复制代码
小六同学吃苹果和鸭梨的动作,相当于实现类
public class People implements Foods {
@Override
public void eatApple() {
System.out.println("eat apple");
}
@Override
public void eatpear() {
System.out.println("eat pear");
}
}
复制代码
小六同学谨记“病从口入”这句成语,所以在吃食物之前需要清洗食物洗手,吃完食物后需要洗手。so easy~~直接在People实现类上加上这两个动作就可以,但是小五同学说我吃苹果和鸭梨之前只洗手不洗食物,为了实现小五这个动作需要重新写接口,重写实现类。那可不可以在不改变实现类的前提下实现呢,答案是肯定的,那就用到静态代理来实现。
public static void main(String []args){
People people = new Perople();
System.out.println("吃前:洗食物洗手");
people.eatApple();
System.out.println("吃后:洗手");
System.out.println("吃前:洗食物洗手");
people.eatpear();
System.out.println("吃后:洗手");
}
复制代码
小六变胖了
小六同学最近变胖了,原因是越来越能吃了,一天需要吃苹果、鸭梨、香蕉、樱桃、橘子、橙子。。。等一百种水果才能吃饱!虽然饮食控制不住,但小六同学还是每次吃食物之前都洗食物洗手,吃完食物后洗手的好习惯,随之食量的增大,每次都需要洗食物洗手,费力费时间,小六心生一计,不如干脆找个管家,每次想吃东西时只需喊一声,管家帮忙洗食物洗手,自己只负责吃,棒极了。
动态代理的两种实现方式
Java 实现动态代理有两种方式,一种是 Java 自带的 JDK 动态代理,还有一种是使用字节码增强技术实现的 CGLIB 库动态代理。
两种方法同时存在,各有优劣。jdk动态代理是由Java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。
JDK动态代理
小六委托管家来代理洗食物和洗手,小六属于委托对象,管家属于代理对象。
JDK动态代理主要两个相关类:
- Proxy(java.lang.reflect包下的),主要负责管理和创建代理类的工作。
- InvocationHandler 接口,只拥有一个invoke方法,主要负责方法调用部分,是动态代理中我们需要实现的方法
每一个代理实例都必须要实现InvocationHandler这个接口,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用,所以要想对方法(吃食物)加强(洗食物洗手)就需要在invoke方法中实现。
public class FoodsHandler implements InvocationHandler{
private Object object;//委托对象(小六同学)
public FoodsHandler(Object object){
this.object = object;
}
/*invoke方法的三个参数:
proxy:  指代我们所代理的那个真实对象
method:  指代的是我们所要调用真实对象的某个方法的Method对象
args:  指代的是调用真实对象某个方法时接受的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
System.out.println("吃前:洗食物洗手");
//当代理对象调用真实对象的方法时,会自动跳转到代理对象关联的handler对象的invoke方法来进行调用
Object result = method.invoke(object, args);
System.out.println("吃后:洗手");
return result;
}
}
复制代码
public static void main(String []args){
//委托对象(小六同学)
People people = new People();
//我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
FoodsHandler inter = new FoodsHandler(people);
//加上这句将会产生一个$Proxy0.class文件,这个文件即为动态生成的代理类文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
//获取代理类实例foods
Foods foods = (Foods)(Proxy.newProxyInstance(Foods.class.getClassLoader(), new Class[] {Foods.class}, fh));
//通过代理类对象调用代理类方法,实际上会转到invoke方法调用
foods.eatApple();
foods.eatpear();
}
/*newProxyInstance方法三个参数的解释如下
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
h:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
*/
复制代码
Cglib动态代理
上面的 JDK 动态代理需要定义一个接口,实现类实现接口中的方法,如果实现类不能实现接口时,我们就需要 CGLIB 动态代理。
使用 CGLIB 动态代理之前需要导入相关 jar 包,可以单独导入 cglib-.jar 包和 asm-.jar 包,也可以只导入一个 cglib-nodep-.jar 包(包含了 asm)。下载链接
public class People {
public void eatApple() {
System.out.println("eat apple");
}
public void eatpear() {
System.out.println("eat pear");
}
}
复制代码
public class PeopleCglib implements MethodInterceptor {
@Override
// object 代表要增强的对象,method代表要拦截的方法,objects 代表方法中的参数,methodProxy 代表对方法的代理
public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable{
System.out.println("吃前:洗食物洗手");
methodProxy.invokeSuper(object,objects);
System.out.println("吃后:洗手");
return object;
}
}
复制代码
public class Main {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer(); // 增强类对象
enhancer.setSuperclass(People.class); // 设置要增强的类(People)
PeopleCglib peopleCglib = new PeopleCglib();
enhancer.setCallback(peopleCglib); // 设置要增强的方法(peopleCglib)
People people = (People) enhancer.create(); // 生成增强过的子类对象
people.eatApple(); // 调用方法实际为增强过的方法
people.eatApple();
}
}
复制代码
输出结果
吃前:洗食物洗手
eat apple
吃后:洗手
吃前:洗食物洗手
eat apple
吃后:洗手
复制代码