动态代理模式,是二十三种设计模式之一,巧了我之前使用C#学习过静态代理模式,动态代理模式,我还真就没学过。
刚好,这次连静态代理模式也复习一下。
一:静态代理模式
1:比如说,我现在需要卖演唱会门票,那么我需要先定义一个卖票的接口
public interface Ticket
{
/**
* 卖票接口
*/
void sell();
}
2:卖票接口有了,我现在需要卖演唱会的门票,所以,我需要定义一个买演唱会门票的类实现卖票接口。
public class MusicTicket implements Ticket
{
/**
* 卖演唱会门票
*/
@Override
public void sell()
{
System.out.println("卖演唱会门票");
}
}
3:但是呢,现在有一个小问题,我买票,但是呢,我没有票,我得委托印刷公司在我卖票之前给我把票做出来。我还要委托检票团队,在演唱会开启之前,检票。以上操作,被成为代理。
public class MusicConductor implements Ticket
{
/**
* 卖演唱会门票对象
*/
private MusicTicket ticket;
public MusicConductor(MusicTicket ticket)
{
this.ticket = ticket;
}
/**
* 卖票
*/
@Override
public void sell()
{
before();
this.ticket.sell();
after();
}
/**
* 委托印刷公司印票
*/
private void before()
{
System.out.println("静态代理 - 方法前增强");
}
/**
* 委托检票团队检票
*/
private void after()
{
System.out.println("静态代理 - 方法后增强");
}
}
4:测试一下
public static void main(String[] args)
{
MusicConductor conductor = new MusicConductor(new MusicTicket());
conductor.sell();
}
运行代码,控制台输出:
静态代理 - 方法前增强
卖演唱会门票
静态代理 - 方法后增强
优点: 可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
缺点: 静态代理模式有一些小小的问题,问题就出在静态两个字上,由于其显式的声明了对象类型,因此,他只能代理已声明的对象类型。也就是说我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
二:JDK实现动态代理模式
动态配置和替换被代理对象,通俗的说就是可以代理任意一类对象,甚至是任意对象。
注意:JDK 代理时被代理类必须实现接口
动态代理的例子跟静态代理是一样的,只是动态代理没有显式的声明类型。
jdk实现的动态代理由两个重要的成员组成,分别是Proxy、InvocationHandler
Proxy:是所有动态代理的父类,它提供了一个静态方法来创建动态代理的class对象和实例
InvocationHandler:每个动态代理实例都有一个关联的InvocationHandler,在代理实例上调用方法是,方法调用将被转发到InvocationHandler的invoke方法
1:声明卖票接口
public interface Ticket
{
/**
* 卖票接口
*/
void sell();
}
2:实例化卖演唱会门票类
public class MusicTicket implements Ticket
{
/**
* 卖演唱会门票
*/
@Override
public void sell()
{
System.out.println("卖演唱会门票");
}
}
3:实例化卖运动会门票类
public class SportTicket implements Ticket
{
/**
* 卖运动会门票
*/
@Override
public void sell()
{
System.out.println("卖运动会门票");
}
}
4:创建动态代理类
public class TicketConductor implements InvocationHandler
{
/**
* 被代理对象
*/
private Object target;
/**
* 可以代理任意门票,所以为 {@link Object}
*
* @param target 被代理对象,但是必须有统一的接口
* @return 被代理对象
*/
public Object getInstance(Object target)
{
this.target = target;
Class<?> clazz = target.getClass(); // 获取代理对象(这里得?是精髓)
return Proxy.newProxyInstance(
clazz.getClassLoader(), // 获取类加载器
clazz.getInterfaces(), // 获取接口
this // 当前对象
);
}
/**
* 反射调用的方法
*
* @param proxy 代理对象
* @param method 被代理对象需要执行的方法
* @param args 被代理对象需要执行的方法 的参数
* @return 被代理对象需要执行的方法 的返回值
* @throws Throwable 抛出异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
before();
Object result = method.invoke(target, args);
after();
return result;
}
private void before() {
System.out.println("动态代理 - JDK动态代理 - 方法前增强");
}
private void after() {
System.out.println("动态代理 - JDK动态代理 - 方法后增强");
}
}
5:测试一下
public static void main(String[] args) {
// 代理演唱会门票
Ticket musicTicket = (Ticket) new TicketConductor().getInstance(new MusicTicket());
musicTicket.sell();
System.out.println("===========================================");
// 代理运动会门票
Ticket sportTicket = (Ticket) new TicketConductor().getInstance(new SportTicket());
sportTicket.sell();
}
运行代码,控制台输出:
动态代理 - JDK动态代理 - 方法前增强
卖演唱会门票
动态代理 - JDK动态代理 - 方法后增强
===========================================
动态代理 - JDK动态代理 - 方法前增强
卖运动会门票
动态代理 - JDK动态代理 - 方法后增强
JDK原生动态代理时java原生支持的、不需要任何外部依赖、但是它只能基于接口进行代理(因为它已经继承了proxy了,java不支持多继承)
三:CGLIB实现动态代理模式
CGLIB是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承的方式实现代理。
CGLIB的实现也有两个重要的成员组成,Enhancer、MethodInterceptor,其实这两个的使用和jdk实现的动态代理的Proxy、InvocationHandler非常相似
Enhancer:来指定要代理的目标对象,实际处理代理逻辑的对象,最终通过调用create()方法得到代理对象、对这个对象所有的非final方法的调用都会转发给MethodInterceptor
MethodInterceptor:动态代理对象的方法调用都会转发到intercept方法进行增强
下边我们使用代码来实现一下:
1:创建演唱会门票类
public class MusicTicket
{
public void sellTicket()
{
System.out.println("演唱会票");
}
}
2:创建运动会门票类
public class SportTicket
{
public void sellTicket()
{
System.out.println("运动会票");
}
}
3:创建CGLIB动态代理类
public class TicketConductor implements MethodInterceptor
{
/**
* CGLIB动态代理类,售票员(不论什么票都卖)
*/
public Object getInstance(Class<?> clazz)
{
// Enhancer 相当于 JDK 动态代理的 Proxy 类
Enhancer enhancer = new Enhancer();
// 设置动态生成的对象的父类为传进来的 被代理类
enhancer.setSuperclass(clazz);
// MethodInterceptor 继承 Callback 接口
enhancer.setCallback(this);
return enhancer.create();
}
/**
* 代理对象执行的所有方法都会走这个方法
*
* @param o 被代理的对象
* @param method 被代理对象需要执行的方法
* @param objects 被代理对象需要执行的方法 参数
* @param methodProxy 触发父类的方法对象
* @return 被代理对象需要执行的方法 返回值
* @throws Throwable 抛出的异常信息
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable
{
before();
// 调用生成代理对象的父类方法
Object result = methodProxy.invokeSuper(o, objects);
after();
return result;
}
private void before() {
System.out.println("动态代理 - CGLIB动态代理 - 方法前增强");
}
private void after() {
System.out.println("动态代理 - CGLIB动态代理 - 方法后增强");
}
}
4:测试一下
public static void main(String[] args)
{
MusicTicket musicTicket = (MusicTicket) new TicketConductor().getInstance(MusicTicket.class);
musicTicket.sellTicket();
System.out.println("==================================");
SportTicket sportTicket = (SportTicket) new TicketConductor().getInstance(SportTicket.class);
sportTicket.sellTicket();
}
运行代码,控制台输出:
动态代理 - CGLIB动态代理 - 方法前增强
演唱会票
动态代理 - CGLIB动态代理 - 方法后增强
==================================
动态代理 - CGLIB动态代理 - 方法前增强
运动会票
动态代理 - CGLIB动态代理 - 方法后增强
CGLIB通过继承的方式进行代理、无论目标对象没有没实现接口都可以代理,但是无法处理final的情况(final修饰的方法不能被覆写)
四:JDK 动态代理与 CGLIB 动态代理的区别
- JDK 动态代理生成的代理对象是实现了被代理对象的接口,CGLIB 动态代理生成的代理对象是继承了被代理对象。
- JDK 和 CGLIB 都是在运行期生成字节码,JDK 是直接写 class 字节码,CGLIB 使用 ASM 框架写 class 字节码,CGLIB 代理实现更复杂,CGLIB 生成代理类的效率比 JDK 生成代理类效率低。
- JDK 调用代理方法,是通过反射机制调用,CGLIB 是通过 FastClass 机制直接调用方法,CGLIB 的被代理类执行效率比 JDK 的被代理类更高。
以上大概就是java实现的动态代理。
有好的建议,请在下方输入你的评论。