设计模式(七):代理模式
目录
文章目录
概述
为什么我可以去做这件事情但是非要代理类去做?例如办理签证,手续相当复杂,一般会选择把材料寄过去,由别人代理办理然后寄过来,代理的好处自然就是提高效率。、
从代码的角度上来讲应该算是职责的分离已经公共业务的封装。例如spring AOP中的代理模式就将jdbc的连接以及事务的管理交给了代理类处理,使我们只需要关系业务代码,相同的逻辑交给代理类统一封装。
静态代理
场景介绍
- 首先创建用户接口
public interface UserDao {
void insertUser();
}
- 然后创建被代理类
public class UserDaoImpl implements UserDao{
@Override
public void insertUser() {
System.out.println("一条用户数据插入完成");
}
}
- 创建代理类,
public class UserDaoProxy implements UserDao{
private UserDao userDao;
public UserDaoProxy() {
//代理的对象很明确,可以在这里直接创建
this.userDao = new UserDaoImpl();
}
@Override
public void insertUser() {
System.out.println("代理类正在记录日志,开启事务");
userDao.insertUser();
System.out.println("插入成功,提交事务");
}
}
- 创建测试类
public class StaticProxyTest {
public static void main(String[] args) {
UserDao proxy = new UserDaoProxy();
proxy.insertUser();
}
}
总结
和装饰器的区别
写完我的第一反应就是装饰器模式很像啊,
相同点:
- 同源(实现同一接口)
- 都对原有类做功能的增强,持有被代理或装饰对象的引用
不同点:
- 原有类经过装饰后可能会改变原有的功能(覆盖原有的方法,例如前面举例的茶加了果汁改变了原有的味道)。代理更多的是不改变原有的实现,在实现以外的环节做功能的增强。
- 经过装饰后有更多额外的功能(比如茶加了果汁还提供了维他命)。而代理类的方法签名和被代理类一致。
静态代理的优缺点
优点
- 在对原有类没有侵入的情况下实现了扩展
缺点
- 当需要代理同一个类的多个方法时重复代码很多
- 当需要被代理的类很多时重复代码很多
动态代理
JDK实现动态代理
场景介绍
现在新增需求,执行UserDao所有方法的前后都需要各个打印一句话,比如请求参数和返回结果用于日志记录。这一需求刚好体现了静态代理的不足。
首先写一个包含多个方法的接口,再创建实现类
public interface DynamicUserDao {
int insert(String name);
String getName(int id);
boolean deleteById(int id);
}
public class DynamicUserDaoImpl implements DynamicUserDao{
@Override
public int insert(String name) {
System.out.println("插入成功");
return 1;
}
@Override
public String getName(int id) {
System.out.println("查询成功");
return "user123";
}
@Override
public boolean deleteById(int id) {
System.out.println("删除成功");
return true;
}
}
创建代理类
public class DynamicUserDaoProxy implements InvocationHandler{
private DynamicUserDao dynamicUserDao;
public Object getInstance(DynamicUserDao dynamicUserDao) throws Exception{
this.dynamicUserDao = dynamicUserDao;
Class<?> clazz = dynamicUserDao.getClass();
//创建代理类对象
return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//开始打印请求参数
System.out.println("代理类开始打印请求参数");
for(Object o : args){
System.out.println("请求参数:"+o);
}
Object res = method.invoke(this.dynamicUserDao, args);
if(null != res){
System.out.println("打印返回结果:"+res);
}
return res;
}
}
创建测试类
public class DynamicProxyTest {
public static void main(String[] args) throws Exception {
DynamicUserDao proxy = (DynamicUserDao) new DynamicUserDaoProxy().getInstance(new DynamicUserDaoImpl());
//通过反编译工具可以查看源代码,jd-gui或者javap -public $Proxy0.class
byte [] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{DynamicUserDao.class});
try (FileOutputStream os = new FileOutputStream("E://$Proxy0.class")) {
os.write(bytes);
os.close();
}
System.out.println(proxy.getClass());
//class com.sun.proxy.$Proxy0
proxy.getName(123);
/**
* 代理类开始打印请求参数
* 请求参数:123
* 查询成功
* 打印返回结果:user123
*/
System.out.println("-----------------");
proxy.deleteById(123);
/**
* 代理类开始打印请求参数
* 请求参数:123
* 删除成功
* 打印返回结果:true
*/
}
}
总结
优点显而易见,一个动态代理类代理了所有方法。如果代理类传入的是Object对象就可以代理所有对象了。
实现原理
先来查看一下生成的动态代理类
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.fh.iquery.design.proxy.dynamic.DynamicUserDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements DynamicUserDao {
private static Method m1;
private static Method m4;
private static Method m5;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final int insert(String var1) throws {
try {
return (Integer)super.h.invoke(this, m4, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final boolean deleteById(int var1) throws {
try {
return (Boolean)super.h.invoke(this, m5, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String getName(int var1) throws {
try {
return (String)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m4 = Class.forName("com.fh.iquery.design.proxy.dynamic.DynamicUserDao").getMethod("insert", Class.forName("java.lang.String"));
m5 = Class.forName("com.fh.iquery.design.proxy.dynamic.DynamicUserDao").getMethod("deleteById", Integer.TYPE);
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.fh.iquery.design.proxy.dynamic.DynamicUserDao").getMethod("getName", Integer.TYPE);
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
动态代理的核心就是Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this)
这个方法,实现的核心在于利用字节码重组技术生成了一个新的类com.sun.proxy.$Proxy0
,该类继承了java.lang.reflect.Proxy
实现了DynamicUserDao
。利用反射代理被代理类的所有方法。
CGLIB实现动态代理
先导入两个jar包:asm-3.3.1.jar,cglib-2.2.2.jar
为了实现效果增加一个两个参数的方法,同时修改实现类
public interface DynamicUserDao {
int insert(String name);
String getName(int id);
boolean deleteById(int id);
//新增方法
boolean updateById(int id, String name);
}
public class DynamicUserDaoImpl implements DynamicUserDao{
@Override
public int insert(String name) {
System.out.println("插入成功");
return 1;
}
@Override
public String getName(int id) {
System.out.println("查询成功");
return "user123";
}
@Override
public boolean deleteById(int id) {
System.out.println("删除成功");
return true;
}
@Override
public boolean updateById(int id, String name) {
System.out.println(String.format("将编号为[%s]的姓名更新为[%s]", id, name));
return true;
}
}
创建代理类
public class CglibProxy implements MethodInterceptor{
public Object getInstance(Class<?> clazz) throws Exception{
Enhancer enhancer = new Enhancer();
//要把哪个设置为即将生成的新类父类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//开始打印请求参数
System.out.println("代理类开始打印请求参数");
for(int i=0;i<method.getParameterCount();i++){
System.out.println(String.format("第[%s]个请求参数类型是[%s],对应的值是[%s]", i, method.getParameterTypes()[i], objects[i]));
}
Object res = methodProxy.invokeSuper(o,objects);
if(null != res){
System.out.println("打印返回结果:"+res);
}
return res;
}
}
创建测试类
public class CglibProxyTest {
public static void main(String[] args) throws Exception {
DynamicUserDao proxy = (DynamicUserDao) new CglibProxy().getInstance(DynamicUserDaoImpl.class);
proxy.updateById(1,"123");
/**
* 代理类开始打印请求参数
* 第[0]个请求参数类型是[int],对应的值是[1]
* 第[1]个请求参数类型是[class java.lang.String],对应的值是[123]
* 将编号为[1]的姓名更新为[123]
* 打印返回结果:true
*/
}
}
总结
cglib的实现方式是生成的代理类继承了被代理类,同时覆盖了其中所有的方法,所以被代理类中声明为final的方法是不能被代理的
JDK动态代理却不受影响
JDK VS CGLIB
区别
- JDK生成的代理类继承Proxy实现了被代理的接口,所有被代理的类必须实现某个接口,不能针对类,cglib可以代理类
- CGLIB基于生成子类继承被代理,覆盖重写被代理类的方法,所以不能够代理声明为final的方法,jdk动态代理却可以(因为不基于继承实现)