静态代理

在动态代理之前,先说一下静态代理,静态代理角色分析:
抽象角色 : 一般使用接口或者抽象类来实现;
真实角色 : 被代理的角色;
代理角色 : 代理真实角色 ,代理真实角色后 , 一般会做一些附属的操作 ;
使用者 : 使用代理角色来进行一些操作 ;

抽象角色(租房的操作)

  1. package com.wjh;
  2. /**
  3. * @author wjh
  4. * @date 2021/7/17 15:33
  5. * @Package com.wjh
  6. */
  7. public interface Rent {
  8. void rent();
  9. }

真实角色(房东,要把房子租出去)

  1. package com.wjh;
  2. /**
  3. * @author wjh
  4. * @date 2021/7/17 15:34
  5. * @Package com.wjh
  6. */
  7. public class Host1 implements Rent{
  8. @Override
  9. public void rent() {
  10. System.out.println("我是房东,这里有一间房子,现在租给你了");
  11. }
  12. }

代理角色(中介,会进行附加操作,比如:收费)

  1. import com.wjh.Host1;
  2. import com.wjh.Rent;
  3. /**
  4. * @author wjh
  5. * @date 2021/7/17 15:33
  6. * @Package PACKAGE_NAME
  7. */
  8. public class ProxyHost1 implements Rent {
  9. private Host1 host1;
  10. public ProxyHost1(Host1 host1) {
  11. this.host1 = host1;
  12. }
  13. @Override
  14. public void rent() {
  15. fare();
  16. host1.rent();
  17. }
  18. private void fare(){
  19. System.out.println("我是房租中介,进行了收费");
  20. }
  21. }

顾客(使用中介执行租房的操作)

package com.wjh;

/**
 * @author wjh
 * @date 2021/7/17 15:34
 * @Package com.wjh.service
 */
public class Customer {
    public static void main(String[] args) {
        ProxyHost1 proxyHost1 =new ProxyHost1(new Host1());
        proxyHost1.rent();
    }
}

通过上面的代码可以看到,顾客没有直接调用房东的rent方法,而是调用了中介的rent方法,并且中介的rent方法还进行了收费的附加操作,且中介和房东都实现的是Rent接口,重写的rent方法,而顾客调用的是中介的rent方法,没有直接调用房东的rent方法,而是通过中介的rent方法间接的调用房东的rent方法。这样的好处就是房东只需要纯粹的完成“租房出去”这个操作,不需要“找租客”、“谈价钱”…. 这些琐事,而这些琐事只需要交给中介去做就可以了,也就是所谓的附加操作。

通过上面的代码,由于ProxyHost1只能代理Host1,不能代理其它类,这就导致如果有其它房东Host2(也是实现的Rent接口),就没有办法代理Host2了,那就必须再写一个代理类去代理Host2,如此一来,代理类就会越来越多,由此可见,这样的代理是无法应用到项目开发中的,而动态代理就可以很好的解决这一问题。

动态代理

动态代理的角色和静态代理的一样,动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的,动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理。了解一些常用的代理:
JDK动态代理:基于接口的动态代理,被代理的对象至少实现一个接口;
cglib:基于类的动态代理,通过“继承”可以继承父类所有的公开方法,然后可以重写这些方法,在重写时对这些方法增强;

本节说的是jdk动态代理,和jdk动态代理的两个类是InvocationHandler和Proxy,它们都位于java.lang.reflect包下面,动态代理类需要实现InvocationHandler接口,重写其中的invoke方法,对于invoke方法,仅对其三个参数进行简单的介绍(其原理较为复杂):
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
proxy: 指代我们所代理的那个真实对象,也就是所谓的代理对象
method: 指代的是我们所要调用真实对象的某个方法
args: method方法的参数,以数组形式表示

至于Proxy类,只介绍newProxyInstance方法即可:
public static Object newProxyInstance
(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException

loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载,获取被代理对象的ClassLoader即可。
interfaces: 一个Interface对象的数组,被代理对象所实现的所有接口。
h: 一个InvocationHandler对象,就是实现InvocationHandler接口的类,表示的是当动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上。

package dao;

/**
 * @author wjh
 * @date 2021/7/17 14:52
 * @Package dao
 */
public interface UserDao {
    void add(int x);
}
package dao;

/**
 * @author wjh
 * @date 2021/7/17 14:57
 * @Package dao
 */
public class UserDaoMysqlImpl implements UserDao{
    @Override
    public void add(int x) {
        System.out.println("使用MySQL数据库进行add操作");
    }
}
package dao;

/**
 * @author wjh
 * @date 2021/7/17 14:58
 * @Package dao
 */
public class UserDaoOracleImpl implements UserDao{
    @Override
    public void add(int x) {
        System.out.println("使用Oracle数据库进行add操作");
    }
}
package proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author wjh
 * @date 2021/7/17 14:53
 * @Package proxy
 */
public class MyProxy implements InvocationHandler {
    /**
     * 被代理的对象,即真实对象,只需要通过某种方式从本类外部获取即可
     */
    private Object target;

    public MyProxy() {

    }

    /**
     *
     * @param target 被代理的对象
     * @return 返回代理对象
     *
     * 我们可以通过Proxy类通过的静态方法newProxyInstance来创建被代理对象(target)的代理对象
     * target.getClass().getClassLoader()  获取被代理对象(target)的类加载器
     * target.getClass().getInterfaces()   获取被代理对象(target)实现的所有接口
     * this                                实现InvocationHandler接口的自定义类,这里就是本类
     */
    public Object getProxy(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }


    /**
     *
     * @param proxy 代理对象(代理类的实例)
     * @param method 被代理对象需要执行执行的方法
     * @param args 方法的参数
     * @return 返回被代理对象(target)执行method方法的结果
     * @throws Throwable 抛出异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(proxy.getClass());
        System.out.println(method.getName());
        System.out.println("第一个参数:"+args[0]);
        System.out.println("调用了invoke方法");
        return method.invoke(target, args);
    }
}

Test类:


public class Test {
    public static void main(String[] args) {
        UserDao userDao1 = new UserDaoMysqlImpl();
        UserDao userDao2 = new UserDaoOracleImpl();
        MyProxy myProxy = new MyProxy();
        Object proxy = myProxy.getProxy(userDao1);
        System.out.println(proxy.getClass());
    }
}

动态代理 - 图1
可以看到代理对象proxy已经被创建了,通过 Proxy.newProxyInstance 创建的代理对象是在 jvm 运行时动态生成的一个对象,它并不是我们的 InvocationHandler 类型,也不是我们定义的那组接口的类型,而是在运行时动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy 为中,最后一个数字表示对象的标号。动态代理代理的是接口,如果需要调用接口的方法,则需要先把创建出来的代理对象强制转换为接口类型,但其实际对象依然是类似“$proxy0”的形式,而且我们不能强转转化为它的实现类的类型。如:

public static void main(String[] args) {
      UserDao userDao1 = new UserDaoMysqlImpl();
      UserDao userDao2 = new UserDaoOracleImpl();
      MyProxy myProxy = new MyProxy();

      UserDao proxy1 = (UserDao) myProxy.getProxy(userDao1);
      proxy1.add(1314);
}

动态代理 - 图2

可以看到调用接口的方法真正的是调用代理类中的invoke方法(原理这里就不做解释啦),而代理类的invoke方法又是使用反射调用真正角色(被代理对象target)的add方法。

既然执行接口的add方法实质是执行代理类的invoke方法,那么我们就可以在invoke方法里面进行其它的附加操作,而执行其它的附加操作就是代理模式存在的一大用处,比如:在invoke里面进行事务处理、打印日志等等这些增强UserDaoMysqlImpl中add方法的操作,无需修改UserDaoMysqlImpl中add方法的代码,而且如果我们还需要创建UserDaoOracleImpl的代理对象,我们只需要把真实对象做为参数给代理类就能够创建它的代理对象,无需重新写一个代理类了,这一就解决的静态代理的不足。

可以使用泛型避免把代理对象强转成接口类型

 public <T> T getProxy(Object target) {
        this.target = target;
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
 }

动态代理 - 图3