前言
网上有很多讲动态代理和静态代理的写法作为区分原因,但是都没有讲到本质上
本文主要从应用层自上而下看待这个问题
应用场景
静态代理
// 数据准备
Order order = new Order();order.setUserId(1);
// 创建静态代理
OrderServiceStaticProxy orderServiceStaticProxy = new OrderServiceStaticProxy();
// 使用静态代理保存订单号操作, 静态代理代理了saveOrder方法
orderServiceStaticProxy.saveOrder(order);
动态代理
// 数据准备
Order order = new Order();order.setUserId(2);
// 创建动态代理对象
IOrderService orderProxy = (IOrderService) new ServiceDynamicProxy(new OrderServiceImpl()).bind();
// 使用动态代理保存订单号操作
orderProxy.saveOrder(order);
看代码区别
// 创建静态代理
OrderServiceStaticProxy orderServiceStaticProxy = new OrderServiceStaticProxy();
// 创建动态代理对象
IOrderService orderProxy = (IOrderService) new ServiceDynamicProxy(new OrderServiceImpl()).bind();
误区(不完全对): 静态代理不是面向接口编程,证明:
修改静态代理代码
public class StaticProxy {
private Object target;
public StaticProxy(Object target) {
this.target = target;
}
public Object bind() {
return target;
}
public int saveOrder(Order order) throws Throwable {
IOrderService service;
if (target instanceof IOrderService) {
service = (IOrderService) target;
} else {
throw new RuntimeException("目标类不支持该方法");
}
// 省略增强逻辑
return service.saveOrder(order);
}
}
修改后的静态代理应用层代码,跟动态代理 一模一样,下文将提到一个代理类代理多个被代理类时,静态代理的面向接口特性实质是存在bug的
IOrderService orderProxy = (IOrderService) new StaticProxy(new OrderServiceImpl()).bind();
本质区别 1. 拓展被代理方法的难易程度
1.1 代理同一个类,并且只代理一个方法
动态代理和静态代理的代码量差不多,但是动态代理的效率更低,因为动态代理Proxy.newProxyInstance()会生成class文件开销较大。
1.2 代理同一个类,拓展代理多个方法
静态代理
public class StaticProxy {
private Object target;
public StaticProxy(Object target) {
this.target = target;
}
public Object bind() {
return target;
}
public int saveOrder(Order order) throws Throwable {
IOrderService service;
if (target instanceof IOrderService) {
service = (IOrderService) target;
} else {
throw new RuntimeException("目标类不支持该方法");
}
// 省略增强逻辑
return service.saveOrder(order);
}
/**
* 对代理类新增一个需要被代理的方法,每增加一个方法,就要再写一个同名方法出来
*
*/
public int deleteOrder(Order order) throws Throwable {
IOrderService service;
if (target instanceof IOrderService) {
service = (IOrderService) target;
} else {
throw new RuntimeException("目标类不支持该方法");
}
// 省略增强逻辑
return service.deleteOrder(order);
}
}
动态代理的实现
十分方便得代理了一个类的所有方法,设置了before 和 after 方法。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
beforeMethod(argObject);
// 使用反射得到的方法和参数
Object object = method.invoke(target, args); // 被代理类的原本逻辑,获得的返回值
afterMethod();
return object;
}
如果只代理一个类的几个方法,有两种实现
第一种
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("XXX")) {
// before after
} else if...
else {
Object object = method.invoke(target, args); // 被代理类的原本逻辑,获得的返回值
}
return object;
}
第二种
容器可以写在类里,最好写在配置文件里面
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (容器.是否有(method.getName())) {
// before after
} else {
Object object = method.invoke(target, args); // 被代理类的原本逻辑,获得的返回值
}
return object;
}
小结 : 拓展同一个类的多个方法,动态代理远优于静态代理
本质区别 2. 拓展被代理类的难易程度
2.1 同一个代理类代理多个被代理类
静态代理 --> instanceof判断避免误用,多次方法拷贝,bind()方法会有Bug
public Object bind() {
return target;
}
public int saveOrder(Order order) throws Throwable {
IOrderService service;
if (target instanceof IOrderService) { // 需要类型检查
service = (IOrderService) target;
} else {
throw new RuntimeException("目标类不支持该方法");
}
return result;
}
/**
* 新增一个被代理类StorageService
*
*/
public int reduceStorage(Order order) throws Throwable {
StorageService service;
if (target instanceof StorageService) {
service = (StorageService ) target;
} else {
throw new RuntimeException("目标类不支持该方法");
}
// 省略增强逻辑
return service.reduceStorage(order);
}
IOrderService orderProxy = (IOrderService) new StaticProxy(new OrderServiceImpl()).bind();
// 有隐患的实现
StaticProxy staticProxy = (StaticProxy) new StaticProxy(new OrderServiceImpl()).bind();
staticProxy.reduceStorage(); // 这个语句会暴露给调用者,调用即抛异常
动态代理 --> 根据传参确认需要代理的接口,bind()方法传入的接口即确定了需要代理几个类
/**
* 直接通过传入接口的Class对象,增加代理的类
*/
public Object bind(){
// 获取代理类的class文件
Class cls = target.getClass();
ClassLoader classLoader = cls.getClassLoader();
Class[] interfaces = cls.getInterfaces(); // 也可以作为参数传入
return Proxy.newProxyInstance(classLoader, interfaces,this);
}
以上动态代理返回的代理类,消除了静态代理的隐患。因为动态代理是代理接口,静态代理代理的是具体的类,如果要一个代理,代理两个类,静态代理存在隐患。jdk动态代理消除隐患的前提是,两个类都是接口实现类。如果不是,用cglib
的动态代理能补偿jdk
动态代理的局限。
/**
* invoke 就是所有被代理方法的路由,不用进行instanceof 的合法性校验
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("XXX")) {
// before after
} else if...
else {
Object object = method.invoke(target, args); // 被代理类的原本逻辑,获得的返回值
}
return object;
}
小结 :动态代理能在应用层传入参数的情况下就能做到增加被代理类,业务逻辑的复杂度两者并没有很大的差距,关键在于拓展代理类的能力上,动态代理远优于静态代理
动态代理的优势
1. 仅在业务逻辑中代理单个对象,并增强方法
静态代理实现起来较为复杂,动态代理把代码集中在filter即可完成
// filter 中拦截评论中的脏话,代理request
HttpServletRequest proxyInstance = (HttpServletRequest) Proxy.newProxyInstance(
req.getClass().getClassLoader(),
new Class[]{HttpServletRequest.class}, // 代理传入servlet的request
new InvocationHandler() { // 匿名代理实现
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("getParameter")) {
String value = (String) method.invoke(req, args); // req是filter拦截到的请求
// 去除所有空格, 注意返回值
value = value.replace(" ", "");
// 将脏话按照长度替换成*号, list是存储脏话的集合
for (String s : list) {
if (value.contains(s)) {
StringBuilder temp = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
temp.append("*");
}
value = value.replace(s, temp.toString());
}
}
return value;
}
return method.invoke(req, args);
}
});
2. 可以对方法进行深层次的处理
静态代理
无法修改被代理类的方法的内部实现
public class StaticProxy {
private Connection connection;
public StaticProxy(Connection connection) {
this.connection = connection;
}
public Object bind(){
return connection;
}
/**
* 无法在代理内修改close()的内部实现
*/
public void close() throws SQLException {
beforeMethod();
connection.close();
afterMethod();
}
}
动态代理 --> 提取参数,修改内部实现
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
if (method.getName().equals("close")) {
before();
if(method.getParameterCount() == 2) {
result = method.invoke(target, args[0], null); // 屏蔽入参
}
if(method.getParameterCount() == 3) {
// 修改内部实现
result = new AnotherInst();
}
after(args[args.length - 1]);
return result ;
} else {
return method.invoke(target, args);
}
}
3. 不需要适配器就可以代理整个类
静态代理 --> 代理整个类(数据库连接),代价大
如果要代理MySQL的Connection, 还需要把实现拷贝在代理类中,是个十分繁杂的过程
// 转化为Connection的对象,要正常使用需要很大的代价,相当于要引入适配器
proxyConnection = (Connection) proxy;
动态代理
// 建造者模式创造出来的代理类
proxy = new ConnectionProxy.Builder(connection)
.buildConnectionPool(this)
// 把自己的接口和Connection接口融合,一起给jdk动态代理代理
.buildAimClass(Connection.class, MyInterface.class)
.build();
// 转化为Connection的对象,可被用于jdbc的api
proxyConnection = (Connection) proxy;