Java代理模式

代理模式是一种设计模式,提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能,解耦。

一、静态代理

静态代理相当于是多写了一个代理类,在调用的时候调用的是代理类,在代理类中的处理还是原生的处理逻辑,不过在前后添加上需要添加的代码。
缺点:需要为每一个被代理的对象都创建一个代理类。
白话:
代理角色和真实角色都需要实现同一个接口
真实角色专注于自己的事情
代理角色目的就是帮助真实角色完成一件事情
实现一个接口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代理区别

  1. 基于接口 vs 基于继承
    • JDK动态代理只能代理实现了接口的类,它是基于接口的代理。代理对象实现了与目标对象相同的接口,并通过InvocationHandler接口来处理方法调用。
    • CGLIB(Code Generation Library)则通过生成子类来实现代理,不需要目标对象实现接口。CGLIB使用继承的方式创建代理对象,因此无法代理被标记为final的方法。
  2. 性能差异
    • 由于CGLIB是通过生成子类的方式实现代理,相对于JDK动态代理而言,创建代理对象的过程更加耗时和占用内存,因此在创建大量代理对象时,CGLIB的性能可能会更差。
    • JDK动态代理在执行方法调用时,由于底层使用了反射机制,因此在一些特定场景下,可能比CGLIB慢一些。
  3. 目标对象类型
    • JDK动态代理要求目标对象实现接口,因此它适用于对接口进行代理的场景。
    • CGLIB可以代理没有实现接口的类,因此更加灵活,适用于对类进行代理的场景。
  4. 应用场景
    • 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();
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kkkouz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值