设计模式05-代理模式

代理模式

为什么需要代理模式

在日常的接口或者功能性代码中,在不改变原有功能逻辑的前提下,通常会有一些非功能性需求,比如:请求日志、耗时统计、缓存、鉴权、事物等,如果跟业务代码糅合在一起的话,代码可读性差、违背单一原则,也会增加重复性的代码块

代理模式的分类

静态代理

  • 在编译时就已经产生的代理类
  • 对象有接口:可以让代理类与被代理类实现相同接口,然后通过组合的方式,在代理类中实现接口方法,并调用被代理类对应的实现
  • 对象无接口,是普通类:可以让代理类继承被代理类,通过覆写对应的方法的方式实现代理逻辑
代码举例

非接口方式实现


public class PayServiceImpl {

    public String pay(String accountId, BigDecimal amount) {
        // TODO: 2021/12/15
        return "";
    }
}

@Slf4j
public class PayServiceLogProxyWithExtends extends PayServiceImpl {
    @Override
    public String pay(String accountId, BigDecimal amount) {
        log.info("pay_req:{}, {}", accountId, amount);
        String result = super.pay(accountId, amount);
        log.info("pay_result:{}", result);
        return result;
    }
}

接口方式实现

public interface PayService {
    String pay(String accountId, BigDecimal amount);
}

@Slf4j
public class PayServiceLogProxyWithInterface implements PayService {

    private PayService targetObject;
    public PayServiceLogProxyWithInterface(PayService targetObject) {
        this.targetObject = targetObject;
    }

    @Override
    public String pay(String accountId, BigDecimal amount) {
        log.info("pay_req:{}, {}", accountId, amount);
        String result = targetObject.pay(accountId, amount);
        log.info("pay_result:{}", result);
        return result;
    }
}

优点

针对不是很复杂的需求时实现简单

缺点
  • 对不断新增的需求,代理类的个数膨胀的比较快
  • 针对接口的方式,代理类不能定制化针对特定接口实现代理逻辑(必须实现所有接口)

动态代理

  • 在编译时不产生代理类,运行时动态创建的
代码举例
public class PayServiceTimeProxy {

    public static void main(String[] args) {
        PayServiceImpl payService1 = new PayServiceImpl();
        PayService payService = (PayService) Proxy.newProxyInstance(PayService.class.getClassLoader(), new Class[]{PayService.class}, new TimeProxyHandler(payService1));
        payService.pay("6001", BigDecimal.valueOf(100));
    }

}

@Slf4j
class TimeProxyHandler implements InvocationHandler {
    private Object target;

    public TimeProxyHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start = System.currentTimeMillis();
        Object invoke = method.invoke(target, args);
        TimeUnit.SECONDS.sleep(10);
        long end = System.currentTimeMillis();
        log.info("invoke cost:{}", end - start);
        return invoke;
    }
}

优点

代理类在运行时生成,不会造成类的数量的膨胀,代理规则可以定制,可以使编码人员更多地区专注业务代码的实现

缺点

相对比较复杂

动态代理的实现原理

  • 基于接口实现(JDK)

public class Proxy {
    private static final String RN = "\r\n";

    /**
     * 1\. 根据接口生成代理类字符串
     * 2\. 讲代理类字符串保存到文件
     * 3\. 编译代理类
     * 4\. 加载代理类
     * 5\. 生成代理类对象
     *
     * @param interfaces
     * @param handle
     * @return
     * @throws
     */
    public static Object newProxyInstance(Class interfaces, InvocationHandler handle) throws Exception {
        // 1\. 生成代理类源码字符串 约定代理类名称为:接口名称$Proxy
        String packageName = "com.example.dp.proxy";
        String proxyClassName = getShortClazzName(interfaces.getName()) + "$Proxy";
        String proxySourceCode = genProxySourceCode(packageName, proxyClassName, interfaces, handle);
        System.out.println(proxySourceCode);
        // 2\. 讲代理类源码字符串生成java文件
        String fullClassName = packageName + "." + proxyClassName;
        String path = fullClassName.replaceAll("\\.", "/") + ".java";
        File file = new File("D:/workspace/idea_projects/dp/src/main/java", path);
        FileWriter fw = new FileWriter(file);
        fw.write(proxySourceCode);
        fw.flush();
        fw.close();
        // 3\. 编译代理类
        //int run = ToolProvider.getSystemJavaCompiler().run(null, null, null, file.getPath());
        // 4\. 实例化并返回代理类对象
        System.out.println(fullClassName);
        Class<?> aClass = Class.forName(fullClassName);
        Constructor<?> constructor = aClass.getConstructor(InvocationHandler.class);
        Object newInstance = constructor.newInstance(handle);
        return newInstance;
    }

    private static String genProxySourceCode(String packageName, String proxyClassName, Class clazz, InvocationHandler handle) {
        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(packageName).append(";").append(RN);
        sb.append("public class ").append(proxyClassName).append(" implements ").append(clazz.getName()).append(" {").append(RN);
        sb.append("    private ").append(InvocationHandler.class.getName()).append(" ").append("handler;").append(RN);
        sb.append("    public ").append(proxyClassName).append("(").append(InvocationHandler.class.getName()).append(" h) {").append(RN);
        sb.append("        this.handler = h;").append(RN);
        sb.append("    }").append(RN);
        for (Method method : clazz.getDeclaredMethods()) {
            String returnType = method.getReturnType().getName();
            String methodName = method.getName();
            String argTypeKeys = String.join(",", getArgTypeClassStr(method));
            String argValueKeys = String.join(",", getArgValueKeys(method));
            sb.append("    public ").append(returnType).append(" ").append(methodName).append("(").append(getArgTypeAndValueKeyPairStr(method)).append(") {").append(RN);
            sb.append("        try {").append(RN);
            sb.append("            java.lang.reflect.Method method = ").append(clazz.getName()).append(".class.getMethod(\"").append(methodName).append("\",").append(argTypeKeys).append(");").append(RN);
            sb.append("            return (" + returnType + ")this.handler.invoke(this, method,").append(argValueKeys).append(");").append(RN);
            sb.append("        } catch(Throwable e) {").append(RN);
            sb.append("            e.printStackTrace();").append(RN);
            sb.append("        }").append(RN);
            sb.append("        return null;").append(RN);
            sb.append("    }").append(RN);
        }
        sb.append("}").append(RN);
        return sb.toString();
    }

    private static String getArgTypeAndValueKeyPairStr(Method method) {
        String[] types = getArgTypes(method);
        String[] valueKeys = getArgValueKeys(method);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < types.length; i++) {
            sb.append(types[i]).append(" ").append(valueKeys[i]);
            if (i < types.length - 1) {
                sb.append(",");
            }
        }
        return sb.toString();
    }

    private static String[] getArgTypes(Method method) {
        Class<?>[] parameterTypes = method.getParameterTypes();
        String[] types = new String[method.getParameterCount()];
        for (int i = 0; i < parameterTypes.length; i++) {
            types[i] = parameterTypes[i].getName();
        }
        return types;
    }

    private static String[] getArgTypeClassStr(Method method) {
        Class<?>[] parameterTypes = method.getParameterTypes();
        String[] types = new String[method.getParameterCount()];
        for (int i = 0; i < parameterTypes.length; i++) {
            types[i] = parameterTypes[i].getName() + ".class";
        }
        return types;
    }

    private static String[] getArgValueKeys(Method method) {
        String[] argTypes = getArgTypes(method);
        String[] argValueKeys = new String[method.getParameterCount()];
        for (int i = 0; i < argTypes.length; i++) {
            String shortClazzName = getShortClazzName(argTypes[i]);
            //argValueKeys[i] = lowerFirstLetter(shortClazzName);
            argValueKeys[i] = "arg" + i;
        }
        return argValueKeys;
    }

    public static String lowerFirstLetter(String word) {
        String firstLetter = word.substring(0, 1);
        return firstLetter.toLowerCase() + word.substring(1);
    }

    public static String getShortClazzName(String clazzName) {
        return clazzName.substring(clazzName.lastIndexOf(".") + 1);
    }
}

public interface PayService {

    String pay(String account, BigDecimal amount);

    String refund(String account, BigDecimal amount);
}

public class PayServiceImpl implements PayService{

    @Override
    public String pay(String account, BigDecimal amount) {
        System.out.println("pay_ok");
        return "T001";
    }

    @Override
    public String refund(String account, BigDecimal amount) {
        System.out.println("refund_ok");
        return "T002";
    }
}

public class MyProxyTest {

    public static void main(String[] args) throws Exception {
        PayService target = new PayServiceImpl();
        PayService payService = (PayService) Proxy.newProxyInstance(PayService.class, new RequestLogInvocationHandler(target));
        String pay = payService.pay("laowang", BigDecimal.valueOf(100));
        System.out.println(pay);
        String refund = payService.refund("lisi", BigDecimal.valueOf(50));
        System.out.println(refund);

    }

}

class RequestLogInvocationHandler implements InvocationHandler {

    private Object target;

    public RequestLogInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object... args) throws Throwable {
        System.out.println("1\. req");
        Object result = method.invoke(this.target, args);
        System.out.println("2\. resp");
        return result;
    }
}

  • 基于字节码实现(CGLIB)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 建造者模式 Builder模式,建造者模式、构建者模式或者生成器模式。 为什么需要建造者模式? 有了构造方法,为什...
    MetaYoo阅读 871评论 0 0
  • 原型模式 如果对象的创建成本很大,而同一个类的不同对象之间差别不大(大部分属性字段相同),这种情况下我们可以根据已...
    MetaYoo阅读 1,826评论 0 0
  • 时间过得很快,转眼之间4个月的线上学习+一周的线下课程(实际课程为4天3夜)全都结束了。在10月30号早上离开酒店...
    阿睿_zhangsir阅读 11,160评论 0 1
  • 1.1、Vue组件注册 注册vue组件的步骤:创建组件构建器 注册组件 使用组件。 总结: 创建组件的基本步骤: ...
    神秘码农阅读 3,068评论 0 1
  • 前言 唉,说是Java开发,其实写的最多的还是SQL,搞得我有点迷啊,我这是SQL工程师吧。。。。不说了,还是写一...
    Fengx阅读 2,299评论 0 1