likes
comments
collection
share

Java-代理设计模式

作者站长头像
站长
· 阅读数 10

代理设计模式

  1. 通过对目标对象(target)进行封装, 对程序进行加强处理。例如:获得目标对象方法的运行总时长、Spring的面向切面编程。
  2. 实现原理:创建一个代理类(proxy),并持有一个目标对象,代理类不会自己实现真正服务,通过调用目标对象的相关方法来提供服务。类似于现实中介。
  3. 通过一个代理类间接访问目标对象。

Java-代理设计模式

静态代理

直接编写一个代理类,把源码直接编写好,编译后生成一个.class文件

简单实现

  1. 创建一个Person接口
public interface Person{
    // 租房
    public void rentRoom();
}
  1. 创建租客类
public class Renter implements Person{
    @Override
    public void rentRoom(){
		租房子
    }
}
  1. 创建代理类,即租客通过中间找房子
public class RenterProxy implements Person{
	private Renter renter;
    @Override
    public void rentRoom(){
        中介找房子,转租给租客....
        租房子
        中介给租客钥匙,租客入住...
    }
}

租客想租房子,但是自己没有实力,就通过找一个代理(中介)来替他找房子,并租下。实际上来说最终还是租客租房子,只不过通过其他人来代替自己完成该任务,自己领包入住即可。

弊端

通过编写源码来实现代理模式,1000个类就会有一千个代理类,会产生类爆炸。

动态代理

在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题

  • JDK动态代理技术:只能代理接口。
  • CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)
  • Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架

JDK动态代理

  • 只能代理接口。

  • 在程序运行过程中创建代理类。

  • 在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

代码实现

  1. 创建一个Person接口,提供方法
/**
 * 被实现的接口
 * @author hly
 * @version 1.0
 */
public interface Person {
    /**
     * 相亲方法
     *
     * */
     void blindDate();
}
  1. 创建Person的Impl实现类,并重写方法。
/**
 * 一位男士要去相亲
 * @author hly
 * @version 1.0
 */
public class Man implements Person {
    /**
     * 相亲
     */
    @Override
    public void blindDate() {
        System.out.println("开始相亲.....");
    }
}
  1. 创建InvocationHandle实现类
    • 代理对象的所有方法调用都会被转发到 InvocationHandler 接口的 invoke() 方法中
/**
 * @author hly
 * @version 1.0
 */
public class MyInvocationHandler implements InvocationHandler {
    // 持有目标对象(被代理的对象)
    private Object target;
    // 有参构造器,给target赋值
    public MyInvocationHandler(Object target){
        this.target = target;
    }

    /**
     * 代理类底层执行的方法。即当代理类调用目标方法时,jvm底层转发到InvocationHandle接口实现类的invoke方法
     * 再通过该方法,利用反射机制调用被代理对象的目标方法。
     * 当代理类调用该方法时就可以做一些增强操作。
     * 即代理对象就是媒婆,帮你约好人,相亲还是你自己去
     * @param proxy 代理对象,后期需要用时,直接用
     * @param method 被代理对象(目标对象)的目标方法
     * @param args 目标方法的参数列表
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 调用目标对象方法前的操作
        System.out.println("媒婆帮你找了一个相亲对象");
        // 将反射机制调用目标对象的目标方法(相亲方法)
        Object result = method.invoke(target, args);
        // 调用目标对象方法后的操作
        return result;
    }
}
  1. 通过java.lang.reflect.Proxy创建代理对象
/**
 * @author hly
 * @version 1.0
 */
public class TestPerson {
    @Test
    public void test(){
        // 创建目标对象
        Person target = new Man();
        /*
         * 男人条件有点差,没能约到相亲对象
         * 他找了个媒婆帮他找到他的相亲对象
         * 媒婆就是那个所谓的代理类
         */
        /*
            Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),调用处理器对象)
            第一个参数:目标对象的类加载器,
            第二个参数:目标对象接口类型。代理类和目标类实现相同的接口,所以要通过这个参数告诉JDK动态代理生成的类要实现哪些接口
            第三个参数:调用处理器。这是一个JDK动态代理规定的接口,接口全名:java.lang.reflect.InvocationHandler
         */
        //强转接口,而不是实现对象。
        // 运行类型为class jdk.proxy2.$Proxy7
        Person manProxy = ((Person) Proxy.newProxyInstance((target.getClass().getClassLoader()), target.getClass().getInterfaces(), new MyInvocationHandler(target)));
        manProxy.blindDate();
    }
}
  1. 运行效果

Java-代理设计模式

  • 注意:当调用其他方法时,也有可能会调用invoke方法。
    • .getClass() 方法是获取一个对象的运行时类信息,它不会触发 invoke() 方法的执行。
    • 调用代理对象的其他方法:当你调用代理对象的非目标方法时,例如 toString(), hashCode(), equals() 等方法,都会触发 invoke() 方法的执行。
    • Object 类中的方法:如果代理的目标实现了 Object 类中的方法,如 wait(), notify(), notifyAll() 等,调用这些方法也会触发 invoke() 方法的执行。
    • 默认方法(Default Method):如果代理目标实现了接口中的默认方法,并且你调用了这些默认方法,也会触发 invoke() 方法的执行。

CGLIB代理

  • 如果被代理类没有实现接口,那么这么实现动态代理?这时候就需要用到CGLIB了。这种代理方式就叫做CGLIB代理。

  • CGLIB代理也叫作子类代理,他是通过在内存中构建一个子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,然后加入自己需要的操作。因为使用的是继承的方式,所以不能代理final 类

    代码实现

    1. 先引入cglib依赖
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.3.0</version>
    </dependency>
    
    1. 准备一个没有实现类的接口

/**
 * 没有实现接口的类
 * @author hly
 * @version 1.0
 */
public class UserService {
    public void login(){
        System.out.println("用户正在登录系统....");
    }

    public void logout(){
        System.out.println("用户正在退出系统....");
    }
}
  1. 使用CGLIB在内存中为UserService类生成代理类,并创建对象
 public  void test01(){
        // 创建字节码增加器
        Enhancer enhancer = new Enhancer();
        // 告诉cglib要继承哪个类
        enhancer.setSuperclass(UserService.class);
        // 设置回调接口
        //和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,
        // 而是:net.sf.cglib.proxy.MethodInterceptor
        enhancer.setCallback(方法拦截器对象);
        // 生成源码,编译class,加载到JVM,并创建代理对象
        UserService userService = (UserService) enhancer.create();
        userService.login();
        userService.logout();
    }
  1. 编写MethodInterceptor即方法拦截器对象,与JDK动态代理中的InvocationHandler有异曲同工之妙。
/**
 * @author hly
 * @version 1.0
 */
public class MyInvocationHandler implements InvocationHandler {
    private Object target;
    public MyInvocationHandler(Object target){
        this.target =target;
    }

    /**
     * 当代理对象调用方法时,底层会走这个方法
     * @param proxy 代理对象,方便后期使用
     * @param method 被代理对象的方法
     * @param args 方法参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理对象调用方法.......");
        //反射机制调用方法
        System.out.println("底层通过反射调用被代理对象的method:"+method);
        Object result = method.invoke(target, args);
        return result;
    }
}
  1. 调用
    @Test
    public  void test01(){
        // 创建字节码增加器
        Enhancer enhancer = new Enhancer();
        // 告诉cglib要继承哪个类
        enhancer.setSuperclass(UserService.class);
        // 设置回调接口
        //和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,
        // 而是:net.sf.cglib.proxy.MethodInterceptor
        enhancer.setCallback(new MyMethodInterceptor());
        // 生成源码,编译class,加载到JVM,并创建代理对象
        UserService userService = (UserService) enhancer.create();
        userService.login();
        userService.logout();
    }

结果

Java-代理设计模式

JDK动态代理与CGLIB的区别

  • 两者都可以实现动态代理机制,在运行过程中生成代理类写入程序。
  • JDK动态代理只能代理接口类,底层通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现
  • CGLIB既可以代理类也可以代理接口,大部分情况下代理类,通过继承被代理类实现其子类,来完成代理模式。底层通过net.sf.cglib.proxy.Enhancer类,和net.sf.cglib.proxy.MethodInterceptor;接口实现
  • 在不破坏代理类源码的情况下,两者都起到了增加作用,遵循oop原则。解决静态代理带来的类爆炸问题
  • 当代理类调用目标方法时,两者底层都转发到各自特定的一个接口实现类通过反射机制,-->method.invoke方法实现,但具体实现又不一样。一个是被代理方法,另一个是代理方法
//JDK动态代理
InvocationHandle
    invoke()
    	// 被代理方法
    	method.invoke(target,args);
// CGLIB
MethodInterceptor
    intercept()
    	// 代理方法
    	methodProxy.invokeSuper(o,objects);
转载自:https://juejin.cn/post/7282150745163726904
评论
请登录