1、静态代理
开发一个接口IDeveloper,该接口包含一个方法writeCode,写代码。
public interface IDeveloper { public void writeCode(); }
创建一个Developer类,实现该接口。
public class Developer implements IDeveloper{ private String name; public Developer(String name){ this.name = name; } @Override public void writeCode() { System.out.println("Developer " + name + " writes code"); } }
测试代码:创建一个Developer实例,名叫Jerry,去写代码!
public class DeveloperTest { public static void main(String[] args) { IDeveloper jerry = new Developer("Jerry"); jerry.writeCode(); } }
现在问题来了。Jerry的项目经理对Jerry光写代码,而不维护任何的文档很不满。假设哪天Jerry休假去了,其他的程序员来接替Jerry的工作,对着陌生的代码一脸问号。经全组讨论决定,每个开发人员写代码时,必须同步更新文档。
为了强迫每个程序员在开发时记着写文档,而又不影响大家写代码这个动作本身, 我们不修改原来的Developer类,而是创建了一个新的类,同样实现IDeveloper接口。这个新类DeveloperProxy内部维护了一个成员变量,指向原始的IDeveloper实例:
public class DeveloperProxy implements IDeveloper{ private IDeveloper developer; public DeveloperProxy(IDeveloper developer){ this.developer = developer; } @Override public void writeCode() { System.out.println("Developer is Write documentation..."); this.developer.writeCode(); } }
这个代理类实现的writeCode方法里,在调用实际程序员writeCode方法之前,加上一个写文档的调用,这样就确保了程序员写代码时都伴随着文档更新。
静态代理方式的优点
1. 易于理解和实现
2. 代理类和真实类的关系是编译期静态决定的,和下文马上要介绍的动态代理比较起来,执行时没有任何额外开销。
静态代理方式的缺点
每一个真实类都需要一个创建新的代理类。还是以上述文档更新为例,假设老板对测试工程师也提出了新的要求,让测试工程师每次测出bug时,也要及时更新对应的测试文档。那么采用静态代理的方式,测试工程师的实现类ITester也得创建一个对应的ITesterProxy类。
public interface ITester { public void doTesting(); } public class Tester implements ITester { private String name; public Tester(String name){ this.name = name; } @Override public void doTesting() { System.out.println("Tester " + name + " is testing code"); } } public class TesterProxy implements ITester{ private ITester tester; public TesterProxy(ITester tester){ this.tester = tester; } @Override public void doTesting() { System.out.println("Tester is preparing test documentation..."); tester.doTesting(); } }
正是因为有了静态代码方式的这个缺点,才诞生了Java的动态代理实现方式。
Java动态代理实现方式一:InvocationHandler
InvocationHandler的原理我曾经专门写文章介绍过:Java动态代理之InvocationHandler最简单的入门教程
通过InvocationHandler, 我可以用一个EnginnerProxy代理类来同时代理Developer和Tester的行为。
public class EnginnerProxy implements InvocationHandler {
Object obj;
public Object bind(Object obj)
{
this.obj = obj;
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
{
String originName = method.getName();
//System.out.println("originName:"+originName);
if(originName.equals("writeCode")){
System.out.println("Developer is Write documentation...");
}else if(originName.equals("doTesting")){
System.out.println("Tester is preparing test documentation...");
}
//JVM通过这条语句执行原来的方法(发射机制)
Object res = method.invoke(obj, args);
return res;
}
}
真实类的writeCode和doTesting方法在动态代理类里通过反射的方式进行执行。
测试代码:
package com.example.designpatterns.proxy.invocationHandler;
import com.example.designpatterns.proxy.*;
/**
* 动态代理方式一:
* 基于接口:JDK的InvocationHandler
*
* 优点:可以同时代理多个对象
* 缺点:如果被代理的类未实现任何接口,那么不能采用通过InvocationHandler动态代理的方式去代理它的行为
*/
public class TestMain {
public static void main(String[] args) {
IDeveloper developer = new Developer("Tom");
ITester tester = new Tester("Jack");
IDeveloper developerPorxy = (IDeveloper) new EnginnerProxy().bind(developer);
ITester testerProxy = (ITester) new EnginnerProxy().bind(tester);
developerPorxy.writeCode();
testerProxy.doTesting();
//产品经理类(ProductOwner) 没有实现任何接口,我们仍然采取EnginnerProxy代理类去代理它,编译时不会出错,运行时会报错。所以局限性就是:如果被代理的类未实现任何接口,那么不能采用通过InvocationHandler动态代理的方式去代理它的行为。
/*ProductOwner productOwner = new ProductOwner("Rose");
ProductOwner productProxy = (ProductOwner) new EnginnerProxy().bind(productOwner);
productProxy.dofineBackLog();*/
}
}
Java动态代理实现方式二:CGLIB
假设有个产品经理类(ProductOwner) 没有实现任何接口。
public class ProductOwner {
private String name;
/**
* Spring通过CGLIB生成代理类对象时,并没有将目标对象的构造函数的参数及其类型进行设定,导致了CGLIB在生成代理类对象时,会使用默认的构造函数生成,结果目标对象类没有默认构造函数,CGLIB生成子类时,也没有加入默认构造函数,所以,异常的发生成为必然。
* 目标对象,定义一个无参数构造函数
*/
public ProductOwner() {
}
public ProductOwner(String name) {
this.name = name;
}
public void dofineBackLog() {
System.out.println("PO: " + name + " defines Backlog.");
}
}
使用CGLIB API来创建代理类
package com.example.designpatterns.proxy.cglib;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 动态代理之CGLIB
*
*/
public class EnginnerCGLibProxy implements MethodInterceptor{
Object object;
public Object bind(Object target){
this.object = target;
//创建加强器,用来创建动态代理类
Enhancer enhancer = new Enhancer();
//为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
enhancer.setSuperclass(object.getClass());
//设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("Enginner 2 writes document");
//调用业务类(父类中)的方法
Object res = methodProxy.invokeSuper(o,objects);
return res;
}
}
测试代码:
package com.example.designpatterns.proxy.cglib;
import com.example.designpatterns.proxy.ProductOwner;
/**
* 动态代理方式二:
* 基于类:CGLIB创建代理类的原理
*
* 优点:可以同时代理多个对象
* 缺点:通过CGLIB成功创建的动态代理,实际是被代理类的一个子类。那么如果被代理类被标记成final,也就无法通过CGLIB去创建动态代理
*/
public class TestMain {
public static void main(String[] args) {
ProductOwner productOwner = new ProductOwner("Ross");
ProductOwner productProxy = (ProductOwner) new EnginnerCGLibProxy().bind(productOwner);
productProxy.dofineBackLog();
}
}
用CGLIB实现Java动态代理的局限性
如果我们了解了CGLIB创建代理类的原理,那么其局限性也就一目了然。我们现在做个实验,将ProductOwner类加上final修饰符,使其不可被继承