Java三种代理模式-静态代理,动态代理和cglib代理

【README】
本文阐述了3种代理模式的定义,并编写了代码测试案例;

代理其实是一种设计模式,可以在访问目标对象的方法上下文添加处理逻辑(扩展目标对象的功能),是 切面编程的基石;

【举个例子】 抖音直播带货就是一种代理模式;主播代理了工厂对象,提供了购买商品的方法;

主播可以下调商品价格(因为走量),类似于在购买商品方法上文添加了逻辑;

主播可以赠送其他商品,类似于在购买商品方法下文添加了逻辑;

【代理模式URL】

1.定义一个带有代理方法的接口;

2.定义目标对象实现这个接口;

3.定义代理对象实现这个接口,且代理对象调用目标对象的对应方法;

4.客户端调用代理对象的方法,代理对象接着调用目标对象的方法,且代理对象可以在上下文添加逻辑(代理模式的目的所在)

 【1】静态代理(编译时生成代理类class文件)
1,代码结构: 需要代理对象和目标对象实现相同接口;

2,优点:可以在不修改目标对象的前提下,扩展其功能;

3,缺点:

冗余:每次代理都要定义一个代理类,会产生过多代理类;
不移维护: 一旦接口方法有增删改,则代理对象和目标对象都需要修改;
【1.1】静态代理代码示例

// 接口
public interface IUserDao {
    public void sayHello();
}
 
// 目标类
public class UserDaoImpl implements IUserDao {
    @Override
    public void sayHello() {
        System.out.println("i am method named sayHello.");
    }
}
 
// 代理类 
public class UserDaoStaticProxy implements IUserDao {
 
    private IUserDao target;
    public UserDaoStaticProxy(IUserDao target) {
        this.target = target;
    }
 
    @Override
    public void sayHello() {
        System.out.println("before");
        target.sayHello();
        System.out.println("after");
    }
}

main方法

public class StaticProxyTest {
    public static void main(String[] args) {
        // 目标对象
        IUserDao target = new UserDaoImpl();
        // 代理对象
        UserDaoStaticProxy proxy = new UserDaoStaticProxy(target);
        // 调用代理方法
        proxy.sayHello();
    }
}

执行结果 

before
i am method named sayHello.
after

【2】动态代理(运行时生成代理对象)
1,为了解决静态代理会有多个代理类,不易维护的缺点,我们引入了动态代理;

2,动态代理利用了JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能。动态代理又被称为JDK代理或接口代理。
3,静态代理与动态代理的区别主要在:

静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件;
动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中;
4,优点: 不需要代理对象实现接口,但目标对象必须实现接口;

5,底层实现: 底层调用了

// 生成代理对象 
Proxy.newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
  • loader: 目标对象的类加载器;
  • interfaces: 目标对象实现的接口class数组;
  • InvocationHandler:将目标方法调用分配到的调用处理器对象;

6,调用处理类实现接口 InvocationHandler ;

类描述:InvocationHandler 是代理对象关联的调用处理程序所实现的接口。
每个代理对象都有一个关联的调用处理程序。
当在代理对象上调用方法时,方法调用被编码并分派到其调用处理程序的调用方法。

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

 方法描述:处理代理对象上的方法调用并返回结果。

当在与其关联的代理对象上调用方法时,将在调用处理程序上调用本方法。

【2.1】动态代理代码示例

// 目标对象的接口
public interface IUserDao2 {
    public String sayHello();
}
 
// 目标对象所属类
public class UserDaoImpl2  implements IUserDao2 {
    @Override
    public String sayHello() {
        String msg = "i am method named sayHello. by dynamic proxy"; 
        System.out.println(msg);
        return msg;
    }
}
 
// 生成代理对象工厂
public class DynamicProxyFactory {
    private DynamicProxyFactory(){}
 
    public static Object getProxyInstance (Object target) {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("before");
                        // 执行目标方法
                        Object result = method.invoke(target, args);
                        System.out.println("after");
                        return result;
                    }
                });
    }
} 

main方法;

public class DynamicProxyTest {
    public static void main(String[] args) {
        // 目标对象
        UserDaoImpl2 target = new UserDaoImpl2();
        // 代理对象
        IUserDao2 proxy = (IUserDao2) DynamicProxyFactory.getProxyInstance(target);
        // 调用代理方法
        String result = proxy.sayHello();
        System.out.println(result);
    }
}

运行结果:

before
i am method named sayHello. by dynamic proxy
after
i am method named sayHello. by dynamic proxy

【3】cglib代理
1,cglib定义, cglib 是一个强大、高性能和高质量的代码生成库。 它可以扩展 JAVA 类并在运行时实现接口;

2,特点

JDK的动态代理有一个限制,就是使用动态代理的目标对象必须实现一个或多个接口。如果想目标对象不实现接口,就可以使用CGLIB实现;
CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口;它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的 interception(拦截);
CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉;
3,cglib与动态代理最大的区别:

使用动态代理的对象必须实现一个或多个接口;使用cglib代理的对象则无需实现接口,达到代理类无侵入;
【3.1】cglib代理代码示例
1,maven 引入 cglib 依赖;

<dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.5</version>
        </dependency>

2,没有实现接口的目标类

public class UserDaoImpl3 {
    public String sayHello() {
        String msg = "i am method named sayHello. test cglib  proxy.";
        System.out.println(msg);
        return msg;
    }
}

3,代理工厂

public class CglibProxyFactory implements MethodInterceptor {
    private Object target;
 
    public CglibProxyFactory(Object target) {
        this.target = target;
    }
 
    public Object getProxyInstance () {
        this.target = target;
        // 工具类
        Enhancer enhancer = new Enhancer();
        // 设置父类
        enhancer.setSuperclass(target.getClass());
        // 设置回调函数
        enhancer.setCallback(this);
        // 创建子类对象代理
        return enhancer.create();
    }
 
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("before");
        Object result = method.invoke(target, args);
        System.out.println("after");
        return result;
    }
}

4,main方法

public class CglibProxyTest {
    public static void main(String[] args) {
        // 目标对象
        UserDaoImpl3 target = new UserDaoImpl3();
        // 代理对象
        UserDaoImpl3 proxy = (UserDaoImpl3) new CglibProxyFactory(target).getProxyInstance();
        // 调用代理方法
        String result = proxy.sayHello();
        System.out.println(result);
    }
}

执行结果:

before
i am method named sayHello. test cglib  proxy.
after
i am method named sayHello. test cglib  proxy. 

【4】java代理实现小结
1)静态代理

1.1)实现较简单,只要代理对象对目标对象进行包装,即可实现增强功能,但静态代理只能为一个目标对象服务,如果目标对象过多,则会产生很多代理类。
1.2)静态代理在编译时产生class字节码文件,可以直接使用,效率高。
2)动态代理

2.1)需要目标对象实现业务接口,代理对象关联的调用处理类需要实现InvocationHandler接口。
2.2)动态代理必须实现InvocationHandler接口,通过反射代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。
3)cglib代理

无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标对象,需要重写方法,所以目标对象不能为final类。

CGLIB动态代理和JDK动态区别
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
Spring中。
1.如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
2.如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3.如果目标对象没有实现接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

————————————————

https://segmentfault.com/a/1190000011291179https://segmentfault.com/a/1190000011291179


版权声明:本文为CSDN博主「PacosonSWJTU」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/PacosonSWJTU/article/details/122692170

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值