代理模式详解

代理模式在软件设计中广泛应用,通过代理对象间接访问目标对象,以达到控制访问、增加功能或保护目标对象的目的。文章详细介绍了代理模式的定义、优点和缺点,以及静态代理和动态代理(JDK动态代理)的概念、应用场景和核心原理。动态代理通过反射机制动态生成代理对象,而静态代理需要手动编写代理类。文章还探讨了JDK动态代理的实现步骤和字节码重组过程,以及CGLib动态代理的使用。此外,代理模式在Spring源码和Mybatis中也有重要应用,如数据源切换和Dao接口的代理实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.简介

在某些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如:购买火车票不一定要去火车站买,可以通过12306网站或者去火车票代售点买。又如找女朋友、找保姆、找工作等都可以通过找中介完成。
在软件设计中,使用代理模式的例子也很多,例如:要访问的远程对象比较大,其下载需要花很多时间。还有因为安全原因需要屏蔽客户端直接访问真实对象,如某单位的内部数据库等。

2.定义

代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

3.优点

  1. 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  2. 代理对象可以扩展目标对象的功能;
  3. 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的扩展性。

4.缺点

  1. 代理模式会造成系统设计中类的数量增加
  2. 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
  3. 增加了系统的复杂度;

5.结构

代理模式的主要角色如下:

  1. 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  2. 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  3. 代理(Proxy)类:提供了真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

在代码中,一般代理会被理解为代码增强,实际上就是在源代码逻辑前后增加一些代码逻辑,而使调用者无感知。
根据代理的创建时期,代理模式分为静态代理和动态代理:

  1. 静态:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的.class文件就已经存在了。
  2. 动态:在程序运行时,运用反射机制动态创建而成。
    在这里插入图片描述

6.应用场景

当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过代理对象来间接访问。使用代理模式主要目的就是保护目标对象和增强目标对象。

主要应用场景:

  1. 远程代理:这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如:用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
  2. 虚拟代理:这种方式通常用于要创建的目标对象开销很大时。例如:下载一副很大的图像需要很长时间,因某种计算比较复杂而段时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
  3. 安全代理:这种方式通常用于控制不同种类客户对真实对象的访问权限。
  4. 智能指引:主要用于调用目标对象时,代理附加一些额外的处理功能。例如:增加计算真实对象的引用次数的功能,这样当对象没有被引用时,就可以自动释放它。
  5. 延迟加载:指为了提高系统的性能,延迟对目标的加载。例如:Hibernate中就存在属性的延迟加载和关联表的延迟加载。

7.使用样例

1.静态代理和动态代理

静态代理就是按照代理模式书写的代码,其特点是代理类和目标类在代码中是确定的。因此称为静态。
动态代理也叫JDK代理或接口代理,有以下特点:

  1. 代理对象不需要实现接口。
  2. 代理对象的生成是利用JDK的API动态的在内存中构建代理对象。
  3. 能在代码运行时动态地改变某个对象的代理,并且能为代理对象动态地增加方法、增加行为、
    一般情况下,动态代理的底层不用我们亲自去实现,可以使用线程提供的API。目前普遍使用的是JDK自带的代理和CGLib提供的类库。

JDK实现代理只需要使用newProxyInstance方法

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, invocationHandler h)

该方法在Proxy类中是静态方法,且接收的三个参数说明依次为:

  1. ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的。
  2. Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型。
  3. InvocationHandler h :事件处理,执行目标对象的方法时,会触发事件处理器的方法,把当前执行目标对象的方法作为参数传入。

静态代理和动态代理主要有以下几点区别:

  1. 静态代理只能通过手动完成代理模式,如果被代理类增加了新的方法,则代理类需要同步增加,违背开闭原则。
  2. 动态代理采用运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则。
  3. 若动态代理要对目标类的增加逻辑进行扩展,结合策略模式,只需要新增策略类便可完成。无须修改代理类的代码。

下面是代码示例:
使用静态代理实现:张三父亲替张三找辅导老师

class ZhangSan{
    public void findTeacher() {
        System.out.println("找到符合张三要求的老师");
    }
}

class ZhangSanFather{
    private ZhangSan zhangSan = new ZhangSan();
    
    public void findTeacher() {
        System.out.println("张三的老父亲开始找老师");
        zhangSan.findTeacher();
        System.out.println("开始学习");
    }
}
public class ZhangSanProxyTest {
    public static void main(String[] args){
        ZhangSanFather zhangSanFather = new ZhangSanFather();
        zhangSanFather.findTeacher();
    }
}

上面场景有个弊端,就是张三的父亲只会帮张三去挑选辅导老师,别人家的孩子是不会管的。于是社会上这样业务发展成了一个产业,出现了答疑、辅导班、培训机构等,还有各种各样的定制套餐。
这样如果还使用静态代理成本就太高了,需要一个更加通用的解决方案,满足任何人想要找老师的需求。这就由静态代理升级到了动态代理。采用动态代理基本要求只要是人就可以提供找老师服务。

接下来使用JDK动态代理实现:辅导班替李四找辅导老师

interface IPerson{
    void findTeacher();
}

class LiSi implements IPerson{
    @Override
    public void findTeacher() {
        System.out.println("找到符合李四要求的老师");
    }
}

class JDKCoach implements InvocationHandler {
    private IPerson target;

    public IPerson getInstance(IPerson target){
        this.target = target;
        Class<?> clazz = target.getClass();
        return (IPerson) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(this.target, args);
        after();
        return result;
    }

    private void before(){ System.out.println("提供挑选老师服务"); }
    private void after(){ System.out.println("开始学习"); }
}
public class CoachProxyTest {
    public static void main(String[] args){
        JDKCoach proxy = new JDKCoach();
        IPerson liSi = proxy.getInstance(new LiSi());
        liSi.findTeacher();
    }
}

2.使用代理模式切换数据源

在分布式业务场景中,通常会对数据库进行分库分表,这样使用Java操作时就可能需要配置多个数据源,可以通过设计数据源路由来动态切换数据源。

@Getter
@Setter
@ToString
class Order{
    private Object orderInfo;
    private Long createTime;
    private String id;
}

class OrderDao{
    public int insert(Order order){
        System.out.println("OrderDao创建Order成功");
        return 1;
    }
}

interface IOrderService{
    int createOrder(Order order);
}

class OrderService implements IOrderService{
    private OrderDao orderDao;

    public OrderService(){
        orderDao = new OrderDao();
    }
    @Override
    public int createOrder(Order order) {
        System.out.println("OrderService调用OrderDao创建订单");
        return orderDao.insert(order);
    }
}

class DynamicDataSourceEntry{
    public final static String DEFAULT_SOURCE = null;
    private final static ThreadLocal<String> local = new ThreadLocal<String>();

    private DynamicDataSourceEntry(){}

    public static void clear(){
        local.remove();
    }

    public static String get(){
        return local.get();
    }

    public static void restore(){
        local.set(DEFAULT_SOURCE);
    }

    public static void set(String source){
        local.set(source);
    }

    public static void set(int year){
        local.set("DB_" + year);
    }
}

public class DataSourceProxy implements InvocationHandler {
    private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
    private Object target;
    private IOrderService orderService;

    public IOrderService getInstance(IOrderService orderService) throws NoSuchMethodException {
        target = orderService;
        Class<?> clazz = target.getClass();
        return (IOrderService) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        before(args[0]);
        Object object = method.invoke(target, args);
        after();
        return object;
    }

    private void before(Object target){
        System.out.println("代理模式之前的方法");
        Long time = null;
        try{
            time = (Long) target
                    .getClass()
                    .getMethod("getCreateTime")
                    .invoke(target);
        }catch (Exception e){
            e.printStackTrace();
        }
        Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
        System.out.println("动态代理类自动分类到【DB_"
                + dbRouter
                + "】数据源处理数据");
        DynamicDataSourceEntry.set(dbRouter);
    }

    private void after(){
        System.out.println("代理模式之后的方法");
    }

    public static void main(String[] args){
        try{
            Order order = new Order();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
            Date date = sdf.parse("2018/01/01");
            order.setCreateTime(date.getTime());
            IOrderService orderService = new DataSourceProxy().getInstance(new OrderService());
            orderService.createOrder(order);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

需要注意的是:这里有个比较重要的约定,即实体类必须实现getCreateTime()方法,因为路由规则是根据时间来运算的,可以通过接口规范达到约束的目的。

3.JDK动态代理核心原理

JDK动态代理采用字节重组,重新生成对象来替代原始对象,以达到动态代理的目的。JDK动态代理生成对象的步骤大致如下:

  1. 获取被代理对象的引用,并且获取它的所有接口。
  2. JDK动态代理类重新生成一个新的类,同时新的类要实现被代理类实现的所有接口。
  3. 动态生成Java代码,新加的业务逻辑方法由一定的逻辑代码调用(在代码中体现),拿到被代理对象的引用。
  4. 编译新生成的Java代码,.class字节码文件。
  5. 重新加载到JVM中运行。
    以上过程就叫字节码重组。

JDK中有一个规范,在ClassPath目录下只要是$开头的.class文件,一般都是自动生成的。
java代码:

interface Animal{
    void eat();
}
class Person implements Animal{

    @Override
    public void eat() {
        System.out.println("人吃饭");
    }
}

class AnimalProxy implements InvocationHandler{
    private Animal animal;

    public Animal getInstance(Animal animal){
        this.animal = animal;
        Class<?> clazz = animal.getClass();
        return (Animal) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理前置处理");
        return method.invoke(animal, args);
    }
}
public class GenerateNewProxyClass {
    public static void main(String[] args){
        try{
            Animal animal = new AnimalProxy().getInstance(new Person());
            animal.eat();
            byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Animal.class});
            FileOutputStream os = new FileOutputStream("D://$Proxy0.class");
            os.write(bytes);
            os.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

运行以上代码,成功后就可以在D盘找到$Proxy0.class文件了。
使用Jad工具反编译:

import com.ding.platform.designpattern.proxy.Animal;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Animal {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void eat() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.ding.platform.designpattern.proxy.Animal").getMethod("eat");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

从反编译的结果可以发现,$Proxy0继承了Proxy类,并且实现了Animal的接口,而且重写了equals、hashCode、toString、eat等方法。其中,在静态代码块中,通过反射获取了代理类的所有方法,而且保留了所有方法的引用,重写的方法用反射调用目标对象的方法。通过invoke执行代理类中的目标方法eat。

4.手动模拟实现动态代理

不依赖JDK,使用代码来动态生成源码、动态完成编译,然后替换目标对象并执行。

public interface MyInvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
class MyProxy{
    public static final String ln = "\r\n";
    public static Object newProxyInstance(MyClassLoader classLoader, Class<?>[] interfaces, MyInvocationHandler h){
        try{
            //动态生成源代码.java文件
            String src = generateSrc(interfaces);

            //将java文件输出到磁盘
            String filePath = MyProxy.class.getResource("").getPath();
            File f = new File(filePath + "$Proxy0.java");
            FileWriter fw = new FileWriter(f);
            fw.write(src);
            fw.flush();
            fw.close();

            //把生成的java文件编译成class文件
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manage = compiler.getStandardFileManager(null, null, null);
            Iterable iterable = manage.getJavaFileObjects(f);
            JavaCompiler.CompilationTask task = compiler.getTask(null, manage, null, null, null, iterable);
            task.call();
            manage.close();

            //将编译生成的.class文件加载到jvm中
            Class proxyClass = classLoader.findClass("$Proxy0");
            Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);
            f.delete();

            //返回字节码重组以后的新的代理对象
            return c.newInstance(h);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    private static String generateSrc(Class<?>[] interfaces){
        StringBuffer sb = new StringBuffer();
        sb.append(MyProxy.class.getPackage() + ";" + ln);
        sb.append("import " + interfaces[0].getName() + ";" + ln);
        sb.append("import java.lang.reflect.*;" + ln);
        sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);
        sb.append("MyInvocationHandler h;" + ln);
        sb.append("public $Proxy0(MyInvocationHandler h) { " + ln);
        sb.append("this.h = h;");
        sb.append("}" + ln);
        for(Method m : interfaces[0].getMethods()){
            Class<?>[] params = m.getParameterTypes();
            StringBuffer paramNames = new StringBuffer();
            StringBuffer paramValues = new StringBuffer();
            StringBuffer paramClasses = new StringBuffer();
            for (int i = 0; i < params.length; i++) {
                Class clazz = params[i];
                String type = clazz.getName();
                String paramName = toLowerFirstCase(clazz.getSimpleName());
                paramNames.append(type + " " + paramName);
                paramValues.append(paramName);
                paramClasses.append(clazz.getName() + ".class");
                if (i > 0 && i < params.length - 1) {
                    paramNames.append(",");
                    paramClasses.append(",");
                    paramValues.append(",");
                }
            }
            sb.append("public " + m.getReturnType().getName() + " " + m.getName() + "(" + paramNames.toString() + ") {" + ln);
            sb.append("try{" + ln);
            sb.append("Method m = " + interfaces[0].getName() + ".class.getMethod(\"" + m.getName() + "\",new Class[]{" + paramClasses.toString() + "});" + ln);
            sb.append((hasReturnValue(m.getReturnType()) ? "return " : "") + getCaseCode("this.h.invoke(this,m,new Object[]{" + paramValues + "})", m.getReturnType()) + ";" + ln);
            sb.append("}catch(Exception ex) { ex.printStackTrace();}");
            sb.append("catch(Throwable e){" + ln);
            sb.append("e.printStackTrace();" + ln);
            sb.append("}");
            sb.append(getReturnEmptyCode(m.getReturnType()));
            sb.append("}");
        }
        sb.append("}" + ln);
        return sb.toString();
    }
    private static Map<Class, Class> mappings = new HashMap<Class, Class>();
    static {
        mappings.put(int.class, Integer.class);
    }
    private static String getReturnEmptyCode(Class<?> returnClass) {
        if (mappings.containsKey(returnClass)) {
            return "return 0;";
        } else if (returnClass == void.class) {
            return "";
        } else {
            return "return null;";
        }
    }
    private static String getCaseCode(String code, Class<?> returnClass) {
        if (mappings.containsKey(returnClass)) {
            return "((" + mappings.get(returnClass).getName() + ")" + code + ")." + returnClass.getSimpleName() + "Value()";
        }
        return code;
    }
    private static boolean hasReturnValue(Class<?> clazz) {
        return clazz != void.class;
    }
    private static String toLowerFirstCase(String src) {
        char[] chars = src.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }
}
class MyClassLoader extends ClassLoader{
    private File classPathFile;
    public MyClassLoader(){
        String classPath = MyClassLoader.class.getResource("").getPath();
        this.classPathFile = new File(classPath);
    }

    @Override
    protected Class<?> findClass(String name){
        String className = MyClassLoader.class.getPackage().getName() + "." + name;
        if(classPathFile != null){
            File classFile = new File(classPathFile, name.replaceAll("\\.", "/") + ".class");
            if(classFile.exists()){
                FileInputStream in = null;
                ByteArrayOutputStream out = null;
                try{
                    in = new FileInputStream(classFile);
                    out = new ByteArrayOutputStream();
                    byte[] buff = new byte[1024];
                    int len;
                    while((len = in.read(buff)) != -1){
                        out.write(buff, 0, len);
                    }
                    return defineClass(className, out.toByteArray(), 0, out.size());
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
        return null;
    }
}
public interface IDao {
    int queryNum();
}
class StudentDao implements IDao{
    @Override
    public int queryNum() {
        System.out.println("查询学生数量");
        return 1;
    }
}
class DaoProxy implements MyInvocationHandler{
    private IDao dao;

    public IDao getInstance(IDao dao){
        this.dao = dao;
        Class<?> clazz = dao.getClass();
        return (IDao) MyProxy.newProxyInstance(new MyClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理模式处理");
        return method.invoke(this.dao, args);
    }
}
public class MyProxyTest {
    public static void main(String[] args){
        IDao dao = new DaoProxy().getInstance(new StudentDao());
        System.out.println(dao.queryNum());
    }
}

5.代理模式在Spring源码中的应用

在使用Spring AOP时,需要先配置好ProxyFactoryBean,然后通过ac.getBean(beanId)来获取ProxyFactoryBean的代理对象。而ProxyFactoryBean类使用ProxyFactoryBean.getObject()方法获取返回的对象,即代理对象。

ProxyFactoryBean类中的核心方法getObject(),源码如下:

/**
* 返回一个代理。 当客户端从这个工厂 bean 获取 bean 时调用。
* 创建一个由该工厂返回的 AOP 代理实例。
* 该实例将被缓存为单例,并在每次调用时创建
* {@code getObject()} 用于代理。
* @return 一个新的 AOP 代理,反映这个工厂的当前状态
*/
@Override
public Object getObject() throws BeansException {
	initializeAdvisorChain();
	if (isSingleton()) {
		return getSingletonInstance();
	}
	else {
		if (this.targetName == null) {
			logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
					"Enable prototype proxies by setting the 'targetName' property.");
		}
		return newPrototypeInstance();
	}
}

在getObject()方法中,主要调用getSingletonInstance()和newPrototypeInstance()方法。
在Spring的配置中,如果不做任何设置,则Spring代理生成的Bean都是单例对象。如果修改scope,则每次都创建一个新的原型对象。

Spring使用动态代理实现AOP时有两个非常重要的类,即JdkDynamicAopProxy类和CglibAopProxy类,其类图如下:
在这里插入图片描述
Spring中的代理选择如下:

  1. 当Bean有实现接口时,Spring会用JDK动态代理方式
  2. 当Bean没有实现接口时,Spring会使用CGLib动态代理方式

Spring可以通过配置强制使用CGLib动态代理,只需在Spring的配置文件中加入如下代码:

<aop:aspectj-autoproxy proxy-targe-class="true"/>

6.jdk和cglib

@ 1.实现原理
  1. Jdk动态代理:利用拦截器(必须实现InvoationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用invokeHandler来处理
  2. Cglib动态代理:利用ASM框架,对代理对象类生成的class文件加载进来,通过修改其字节码生成子类来处理
2.生成字节码方式
  1. Jdk:直接写Class字节码
  2. cglib:使用ASM框架写字节码
3.调用方法方式
  1. Jdk:通过反射机制调用
  2. Cglib:通过FastClass机制直接调用方法
4.使用场景
  1. 目标对象生成了接口,默认用Jdk动态代理
  2. 目标对象使用了接口,可以强制使用cglib
  3. 目标对象没有实现接口,必须采用cglib库,Spring会自动在JDK动态代理和cglib之间转换
5.使用限制
  1. Jdk动态代理只能对实现了接口的类生成代理,而不能针对类
  2. Cglib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中的方法的增强,但是因为使用的是继承,所以该类或方法最好不要生成final,对于final类或方法,是无法继承的
6.使用效率
  1. Cglib底层是ASM字节码生成框架,字节码技术生成代理类比使用java反射的效率要低
  2. Jdk效率比Cglib高
7.代码样例

jdk:

interface UserDao{
    int queryNum();
}
class UserDaoImpl implements UserDao{

    @Override
    public int queryNum() {
        System.out.println("查询学生数量");
        return 1;
    }
}

class JdkProxy implements InvocationHandler{

    private UserDao userDao;

    public UserDao getInstance(UserDao userDao){
        this.userDao = userDao;
        Class<?> clazz = userDao.getClass();
        return (UserDao) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("进入JDK动态代理");
        return method.invoke(this.userDao, args);
    }
}

public class JdkProxyTest {
    public static void main(String[] args){
        System.out.println(new JdkProxy().getInstance(new UserDaoImpl()).queryNum());
    }
}

cglib:

class DogDao{
    public int queryNum(){
        System.out.println("查询单身狗数量");
        return 0;
    }
}
class CglibProxy implements MethodInterceptor{
    private DogDao dogDao;

    public DogDao getInstance(DogDao dogDao){
        this.dogDao = dogDao;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(dogDao.getClass());
        enhancer.setCallback(this);
        return (DogDao) enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("进入Cglib动态代理");
        return methodProxy.invokeSuper(o, objects);
    }
}
public class CglibProxyTest {
    public static void main(String[] args){
        System.out.println(new CglibProxy().getInstance(new DogDao()).queryNum());
    }
}

7.cglib简介

1.jar包

cglib-codep-2.2.jar:使用nodep包不需要关联asm的jar包,jar包内部包含asm的类
cglib-2.2.jar:使用此jar包需要关联asm的jar包,否则运行时报错。

2.类库

net.sf.cglib.core:底层字节码处理类,它们大部分与asm有关系
net.sf.cglib.transform:编译期或运行期类和类文件的转换
net.sf.cglib.proxy:实现创建代理和方法拦截器的类
net.sf.cglib.reflect:实现快速反射和C#风格代理的类
net.sf.cglib.util:集合排序等工具类
net.sf.cglib.beans:JavaBean相关的工具类

3.拦截器

定义一个拦截器。在调用目标方法时,cglib会回调MethodInterceptor接口方法拦截,来实现你自己的代理逻辑,类似于JDK中的InvocationHandler接口。

class CglibProxy implements MethodInterceptor{
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("进入Cglib动态代理");
        return methodProxy.invokeSuper(o, objects);
    }
}

参数:Object是由cglib动态生成的代理类实例,Method为上文中实体类所调用的被代理的方法引用,Object[]为参数值列表,MethodProxy为生成的代理类对方法的代理引用。
返回:从代理实例的方法调用返回的值。
其中,proxy.invokeSuper(obj, arg);调用代理类实例上的proxy方法的父类方法(即实体类中对应的方法)

4.生成动态代理类
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(dogDao.getClass());
enhancer.setCallback(this);
enhancer.create();

这里的Enhancer类是cglib中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展。
首先将被代理类dogDao设置成父类,然后设置拦截器CglibProxy ,最后执行enhancer.create()动态生成一个代理类,并从Object强制转型成父类型dogDao。
最后,在代理类上调用方法。

5.回调过滤器CallbackFilter

在cglib回调时可以设置不同方法执行不同的回调逻辑,或者根本不执行回调。
在JDK动态代理中并没有类似的功能,对InvocationHandler接口方法的调用代理类的所有方法都有效。
定义实现过滤器CallbackFilter接口的类:

class CglibCallbackFilter implements CallbackFilter{

	/**
     * 过滤方法
     * 返回的值为数字,代表了callback数组中的索引位置
     * 对应使用的callback对象
     * @param method 方法对象
     * @return 数组索引
     */
    @Override
    public int accept(Method method) {
        if(method.getName().equals("queryNum")){
            System.out.println("filter queryNum == 0");
            return 0;
        }

        if(method.getName().equals("insert")){
            System.out.println("filter insert == 1");
            return 1;
        }

        if(method.getName().equals("update")){
            System.out.println("filter insert == 2");
            return 2;
        }
        return 1;
    }
}

其中return的值为被代理类的各个方法在回调数组Callback[]中的位置索引

class CglibResultFixed implements FixedValue{

    @Override
    public Object loadObject() throws Exception {
        System.out.println("锁定结果");
        Object obj = 999;
        return obj;
    }
}
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(dogDao.getClass());
CallbackFilter filter = new CglibCallbackFilter();
Callback noopCb = NoOp.INSTANCE;
Callback callback = new CglibProxy();
Callback fixedValue = new CglibResultFixed();
Callback[] cbArray = new Callback[]{callback, noopCb, fixedValue};
enhancer.setCallbacks(cbArray);
enhancer.setCallbackFilter(filter);
enhancer.create();
6.延迟加载对象

LazyLoader接口继承了Callback,因此也算是cglib中的一种Callback类型。
另一种延迟加载接口Dispatcher。Dispatcher接口同样继承于Callback,也是一种回调类型。
但是Dispatcher和LazyLoader的区别在于:LazyLoader只在第一次访问延迟加载属性时触发代理类回调方法,而Dispatcher在每次访问延迟加载属性时都会触发代理类回调方法。
定义一个LoaderBean,该Bean内有一个需要延迟加载的属性PropertyBean

class PersonDao{
    public int queryEyes(){
        System.out.println("人有两只眼睛");
        return 2;
    }
}
@Getter
@Setter
@ToString
class PropertyBean{
    private String key;
    private PersonDao value;
}
@Getter
@Setter
@ToString
class ConcreteClassLazyLoader implements LazyLoader{

    /**
     * 对需要延迟加载的对象添加代理,
     * 在获取该对象属性时先通过代理类回调方法进行对象初始化
     * 让不需要加载该对象时,
     * 只要不去获取该对象内属性,该对象都不会被初始化了
     * 在cglib的实现就会自动触发代理类回调
     */
    @Override
    public Object loadObject() throws Exception {
        System.out.println("before lazyLoader...");
        PropertyBean propertyBean = new PropertyBean();
        propertyBean.setKey("lazyBean");
        propertyBean.setValue(new PersonDao());
        System.out.println("after lazyLoader...");
        return propertyBean;
    }
}
class ConcreteClassDispatcher implements Dispatcher{

    @Override
    public Object loadObject() throws Exception {
        System.out.println("before Dispatcher...");
        PropertyBean propertyBean = new PropertyBean();
        propertyBean.setKey("dispatcherBean");
        propertyBean.setValue(new PersonDao());
        System.out.println("after Dispatcher");
        return propertyBean;
    }
}
@Getter
@Setter
@ToString
public class LazyBean {
    private PropertyBean propertyBean;
    private PropertyBean propertyBeanDispatcher;
    public LazyBean(){
        System.out.println("lazy bean init");
        this.propertyBean = createPropertyBean();
        this.propertyBeanDispatcher = createPropertyBeanDispatcher();

    }

    /**
     * 只有第一次懒加载
     */
    private PropertyBean createPropertyBean(){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(PropertyBean.class);
        enhancer.setCallback(new ConcreteClassLazyLoader());
        return (PropertyBean) enhancer.create();
    }

    /**
     * 每次都懒加载
     */
    private PropertyBean createPropertyBeanDispatcher(){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(PropertyBean.class);
        enhancer.setCallback(new ConcreteClassDispatcher());
        return (PropertyBean) enhancer.create();
    }

    public static void main(String[] args){
        LazyBean bean = new LazyBean();
        System.out.println("第一次调用");
        bean.getPropertyBean().getValue().queryEyes();
        bean.createPropertyBeanDispatcher().getValue().queryEyes();
        System.out.println("第二次调用");
        bean.getPropertyBean().getValue().queryEyes();
        bean.createPropertyBeanDispatcher().getValue().queryEyes();
    }
}

8.代理模式在Mybatis源码中的应用

Mybatis是一个应用非常广泛的优秀持久层框架,它几乎避免了所有JDBC代码、手动设置参数和获取结果集等工作。Mybatis可以使用简单的XML配置文件或注解来映射类、接口和POJO与数据库记录的对应关系。
Mybatis通过动态代理来创建Dao接口的代理对象,并通过这个代理对象实现数据库的操作。
在Mybatis中,MapperProxyFactory、MapperProxy、MapperMethod是三个很重要的类。

1.MapperProxyFactory
public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
}

MapperProxyFactory是一个工厂类,目的就是为了生成MapperProxy。
其构造方法中传入了一个Class,通过参数名mapperInterface可以发现这个类就是Dao接口。
MapperProxyFactory中有2个newInstance()方法,权限修饰符不同,一个是protected,一个是public。

  1. protected的newInstance()方法中首先创建了一个MapperProxy类的对象,然后通过Proxy.newProxyInstance()方法创建了一个对象并返回,也是通过同样的方法返回了mapperInterface接口的代理对象。
  2. public的newInstance()方法中有一个参数SqlSession,SqlSession处理的就是执行一次SQL的过程。

MapperProxy类是InvationHandler接口的实现。因此,可以说MapperProxyFactory类是一个创建代理对象的工厂类,它通过构造函数传入自定义的Dao接口,并通过newInstance方法返回Dao接口的代理对象。

2.MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
}

因为Jdk动态代理,MapperProxy实现了InvocationHandler接口。
在invoke()方法中,首先检查了如果是Object方法就直接调用方法本身,如果不是就把方法Method包装成MapperMethod。MapperMethod主要就是处理方法的注解、参数、返回值,以及参数与SQL语句中参数的对应关系。因次将Method处理为MapperMethod是一个较频繁的操作,所以这里做了缓存处理。

MapperProxy类有三个成员变量。
1.SqlSession,是执行SQL的接口。

public interface SqlSession extends Closeable {
    <T> T selectOne(String statement);

    <T> T selectOne(String statement, Object parameter);
   
    // 下面省略
}

这个接口方法的入参是statement和参数(paramter),返回值是数据对象。
statement可能会被误解为SQL语句,但其实这里的statement是指Dao接口方法的名称,自定义的SQL语句都被缓存在Configuration对象中。
在SqlSession中,可以通过Dao接口的方法名称找到对应的SQL语句。因此可以想到代理对象本质上就是要将执行的方法名称和参数传入SqlSession的对应方法中,根据方法名称找到对应的SQL语句并替换参数,最后得到返回的结果。

2.mapperInteface和methodCache
methodCache是一个Map键值对结构,键是Method,值是MapperMethod。
invoke()方法的最后两行,它首先通过cachedMapperMethod()方法找到将要执行的Dao接口方法对应的MapperMethod,然后调用MapperMethod的excute()方法来实现数据库的操作。这里显然是将SqlSession传入MapperMethod内部,并在MapperMethod内部将要执行的方法名和参数再传入到SqlSession对应的方法中执行。

3.MapperMethod

public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }
}

MapperMethod类中有两个成员变量,分别是SqlCommand和MethodSignature。

  1. SqlCommand主要解析了接口的方法名称和方法类型,定义了诸如Insert、select、delete等数据库操作的枚举类型。
  2. MethodSignature则解析了接口方法的签名,即接口方法的参数名称和参数值的映射关系,即通过MethodSignature类可以将入参的值转换成参数名称和参数值的映射。

MapperMethod类中最重要的execute()方法:

  1. 首先,根据SqlCommand中解析出来的方法类型选择对应SqlSession中的方法,即如果是insert类型,则选择SqlSession.insert()方法来执行数据库操作。
  2. 其次,通过MethodSignature将参数值转换成Map<key,value>的映射,key是方法的参数名称,Value是参数值。
  3. 最后,将方法名称和参数传入对应的SqlSession的方法中执行。至于在配置文件中定义的SQL语句,则被缓存在SqlSession 的成员变量中。
    Configuration中有非常多参数,其中一个是mappedStatements,它保存了我们在配置文件中定义的所有方法。还有一个mappedStatement,它保存了我们在配置文件中定义的各种参数,包括SQL语句。

8.思维导图

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值