代理模式
为什么需要代理模式
在日常的接口或者功能性代码中,在不改变原有功能逻辑的前提下,通常会有一些非功能性需求,比如:请求日志、耗时统计、缓存、鉴权、事物等,如果跟业务代码糅合在一起的话,代码可读性差、违背单一原则,也会增加重复性的代码块
代理模式的分类
静态代理
- 在编译时就已经产生的代理类
- 对象有接口:可以让代理类与被代理类实现相同接口,然后通过组合的方式,在代理类中实现接口方法,并调用被代理类对应的实现
- 对象无接口,是普通类:可以让代理类继承被代理类,通过覆写对应的方法的方式实现代理逻辑
代码举例
非接口方式实现
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)