代理模式是一种设计模式,提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能,解耦。
一、静态代理
静态代理相当于是多写了一个代理类,在调用的时候调用的是代理类,在代理类中的处理还是原生的处理逻辑,不过在前后添加上需要添加的代码。
缺点:需要为每一个被代理的对象都创建一个代理类。
白话:
代理角色和真实角色都需要实现同一个接口
真实角色专注于自己的事情
代理角色目的就是帮助真实角色完成一件事情
实现一个接口Runnable 使用的就是"静态代理"的思想
//接口
public interface DemoService {
void test();
}
//实现类
@Service("demoService")
@Slf4j
public class DemoServiceImpl implements DemoService {
@Override
@SneakyThrows
public void test() {
try {
System.err.println("原逻辑不变");
}catch (Exception e){
e.printStackTrace();
}
}
}
//代理类
@Slf4j
@Service("demoProxy")
public class DemoServiceProxy implements DemoService {
private DemoService service;
public DemoServiceProxy(DemoServiceImpl service){
this.service= service;
}
@Override
public void test() {
service.test();
System.err.println("代理增强逻辑");
}
}
使用静态代理时,会出现一个spring注入的问题
启动时报错:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
原因分析
@Autowired是通过类型来注入的。以上边的示例来说,@Autowired注入一个接口,此时Spring会查找所有实现了接口的bean,此时发现了两个:然后就会报错。
解决方案一
@Primary注解
将@Primary放到接口的某个实现类上边。这样如果此接口有多个实现类,则会注入有@Primary的实现类。那我们只需要放在 !=代理实现类上即可,当然这种方式不具备灵活性
解决方案二
@Service(“demoProxy”)
指定注入实现类
@Autowired@Qualifier(“demoService”)private DemoService demoService;
二、JDK动态代理和CGLIB代理区别
- 基于接口 vs 基于继承:
- JDK动态代理只能代理实现了接口的类,它是基于接口的代理。代理对象实现了与目标对象相同的接口,并通过InvocationHandler接口来处理方法调用。
- CGLIB(Code Generation Library)则通过生成子类来实现代理,不需要目标对象实现接口。CGLIB使用继承的方式创建代理对象,因此无法代理被标记为final的方法。
- 性能差异:
- 由于CGLIB是通过生成子类的方式实现代理,相对于JDK动态代理而言,创建代理对象的过程更加耗时和占用内存,因此在创建大量代理对象时,CGLIB的性能可能会更差。
- JDK动态代理在执行方法调用时,由于底层使用了反射机制,因此在一些特定场景下,可能比CGLIB慢一些。
- 目标对象类型:
- JDK动态代理要求目标对象实现接口,因此它适用于对接口进行代理的场景。
- CGLIB可以代理没有实现接口的类,因此更加灵活,适用于对类进行代理的场景。
- 应用场景:
- JDK动态代理适合于对服务层接口进行代理,如Spring AOP中的代理对象。
- CGLIB适合于对具体业务类进行代理,尤其是没有实现接口的类。
总结来说,JDK动态代理适用于需要代理接口的情况,它的优势在于标准化、原生支持和无需额外依赖。而CGLIB适用于代理类的情况,它的优势在于能够代理没有实现接口的类,但代理过程相对较慢且占用较多内存。选择使用哪种代理方式取决于具体的需求和场景。
三、JDK动态代理
分别根据jdk,cglib实现重试机制案例
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
@Slf4j
public class RetryInvocationHandler implements InvocationHandler {
private final Object subject;
public RetryInvocationHandler(Object subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
int maxAttempts = 3; // 最大重试次数
int attempts = 0;
while (attempts < maxAttempts) {
try {
attempts++;
return method.invoke(subject, args); // 调用真实对象的方法
} catch (Exception e) {
log.error("Method invocation failed. Retry attempt: {}/{}", attempts, maxAttempts);
if (attempts >= maxAttempts) {
throw e;
}
Thread.sleep(1000);
}
}
throw new RuntimeException("Method invocation failed after " + maxAttempts + " attempts");
}
public static Object getProxy(Object realSubject) {
InvocationHandler handler = new RetryInvocationHandler(realSubject);
return Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);
}
}
这里使用的是JDK动态代理,因此就存在一个天然的缺陷,如果想要被代理的类,没有实现任何接口,那么就无法为其创建代理对象。
@SneakyThrows
@GetMapping("/jdkAgent")
public void jdkAgent() {
DemoService jdkProxy = (DemoService) RetryInvocationHandler.getProxy(demoService);
System.err.println("contract");
jdkProxy.test();
}
四、CgLib代理
使用 JDK 动态代理对被代理的类有要求,不是所有的类都能被代理,而 CGLib 动态代理则刚好解决了这个问题
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
public class CGLibRetryProxyHandler implements MethodInterceptor {
private Object target;
private Logger logger = LoggerFactory.getLogger(CGLibRetryProxyHandler.class);
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
int maxAttempts = 3;
int attempts = 0;
while (attempts < maxAttempts) {
try {
attempts++;
return method.invoke(target, objects);
} catch (Exception e) {
logger.error("Method invocation failed. Retry attempt: {}/{}", attempts, maxAttempts);
if (attempts >= maxAttempts) {
throw e;
}
Thread.sleep(1000);
}
}
throw new RuntimeException("Method invocation failed after " + maxAttempts + " attempts");
}
public Object getCglibProxy(Object objectTarget){
this.target = objectTarget;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(objectTarget.getClass());
enhancer.setCallback(this);
Object result = enhancer.create();
return result;
}
}
@GetMapping("/cglbAgent")
@SneakyThrows
public void cglbAgent() {
CgLibTest cgLibTest = new CgLibTest();
CgLibTest cglibProxy = (CgLibTest) new CGLibRetryProxyHandler().getCglibProxy(cgLibTest);
System.err.println("contract");
cglibProxy.test();
}