Java设计模式之静态代理模式与动态代理模式的区别(附源码)
先看定义,代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
在大部分场景中,都是在原有的业务逻辑代码前后设置前后置通知,在前后添加统一处理逻辑,利用代理来处理统一操作,当然也可以在通知中添加条件是否跳过业务逻辑代码的执行。
实际的应用场景有:日志、aop、事务处理、异常处理等功能,需要从业务代码逻辑中抽离出来的部分一般用代理类去操作。
静态代理:
举一个交班费的例子
public interface Person {
// 上交班费
void giveMoney();
}
public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void giveMoney() {
System.out.println(name + "上交班费50元");
}
}
public class StudentsProxy implements Person {
// 被代理的学生
Student stu;
public StudentsProxy(Person stu) {
// 只代理学生对象
if (stu.getClass() == Student.class) {
this.stu = (Student) stu;
}
}
// 代理上交班费,调用被代理学生的上交班费行为
public void giveMoney() {
System.out.println("准备交班费了!");//设置前置通知
stu.giveMoney();
System.out.println("班费交好了");//后置通知
}
}
测试
public class StaticProxyTest {
public static void main(String[] args) {
// 被代理的学生张三,他的班费上交有代理对象monitor(班长)完成
Person zhangsan = new Student("张三");
// 生成代理对象,并将张三传给代理对象
Person monitor = new StudentsProxy(zhangsan);
// 班长代理上交班费
monitor.giveMoney();
}
}
动态代理
代理类实现逻辑不一样
public class StuInvocationHandler<T> implements InvocationHandler {
// invocationHandler持有的被代理对象
T target;
public StuInvocationHandler(T target) {
this.target = target;
}
/**
* proxy:代表动态代理对象
* method:代表正在执行的方法
* args:代表调用目标方法时传入的实参
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理执行" +method.getName() + "方法");
//代理过程中插入监测方法,计算该方法耗时
MonitorUtil.start();
Object result = method.invoke(target, args);
MonitorUtil.finish(method.getName());
return result;
}
}
添加监控类(通知类)
public class MonitorUtil {
private static ThreadLocal<Long> tl = new ThreadLocal<>();
public static void start() {
tl.set(System.currentTimeMillis());
}
//结束时打印耗时
public static void finish(String methodName) {
long finishTime = System.currentTimeMillis();
System.out.println(methodName + "方法耗时" + (finishTime - tl.get()) + "ms");
}
}
测试
public class ProxyTest {
public static void main(String[] args) {
//创建一个实例对象,这个对象是被代理的对象
Person zhangsan = new Student("张三");
//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new StuInvocationHandler<Person>(zhangsan);
//创建一个代理对象stuProxy来代理zhangsan,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(),
new Class<?>[] { Person.class }, stuHandler);
//代理执行上交班费的方法
stuProxy.giveMoney();
}
}
学生类和person类不变
代码库:https://gitee.com/SlienceDemo/java-ee.git
java-ee /proxy 模块
可以看出代理类StuInvocationHandler通过实现InvocationHandler接口,重写invoke方法,利用反射将对象实例化。
总结
静态代理要在代理类中写死代理的对象,即便它可以抽象成接口,使得实现此接口的类都能使用代理,但是对于已有的业务逻辑再去为了这些功能去实现接口并不友好。
而动态代理通过泛型和反射来导入要代理的类,不用指定具体的类或接口,更具有扩展性,但也会因为调用时通过反射来实例化对象而影响性能。
总的来说,静态代理适用于已经开发好的程序,确定需要代理的类来扩展通知功能,并且以后不再扩展新的类或者接口。
动态代理适用于未开发完成的项目,后期可能其他类也需要此代理。建议用动态代理,毕竟需求变更这事儿就更天气一样。