17|动态代理:如何在运行时插入逻辑?

你好,我是郭屹。今天我们继续手写MiniSpring。 从这节课开始,我们就要进入AOP环节了。在学习之前,我们先来了解一下是AOP怎么回事。 AOP,就是面向切面编程(Aspect Orient Programming),这是一种思想,也是对OOP面向对象编程的一种补充。你可能会想:既然已经存在OOP面向对象编程了,为什么还需要AOP面向切面编程呢? 这是因为在许多场景下,一个类的方法中,除了业务逻辑,通常还会包括其他比较重要但又不算主业务逻辑的例行性逻辑代码,比如常见的日志功能,它不影响我们的主业务逻辑,但又能在必要时定位问题,几乎每一个业务方法中都需要。又比如权限检查、事务处理,还有性能监控等等,都是这种情况。 显而易见,日志这类例行性逻辑,在任何一个业务方法实现中都是需要的。如果简单地将这些代码写在业务方法中,会出现两个后果,第一,我们就会将日志之类的代码重复地编写多次;第二,一个业务方法中会包含很多行例行代码,去看源代码会发现方法中多数语句不是在做业务处理。 有专业进取心的程序员就会思考一个问题, 有没有办法将这些例行性逻辑单独抽取出来,然后在程序运行的时候动态插入到业务逻辑中呢? 正是因为这个疑问,AOP应运而生了。这个问题听起来似乎无解,程序在运行时改变程序本身,似乎有点不可思议。我们研究一下Java,就会惊奇地发现,Java里面早就给我们提供了一个手段: 动态代理。我们可以利用它来开展我们的工作。

代理模式

我们一步步来,先从代理讲起。
图片

看图,我们知道真正干活儿的类是RealSubject,具体则是由DoAction()执行任务。Proxy作为代理提供一个同样的DoAction(),然后调用RealSubject的DoAction()。它们都实现Subject接口,而Client应用程序操作的是Subject 接口。

简单说来,就是在Client应用程序与真正的服务程序RealSubject之间增加了一个Proxy。

我们举例说明,先定义一个服务类接口。

1
2
3
public interface Subject {
	String doAction(String name);
}

再定义具体的服务类。

1
2
3
4
5
6
public class RealSubject implements Subject {
	public String doAction(String name) {
		System.out.println("real subject do action "+name);
		return "SUCCESS";
	}
}

最后再定义一个代理类。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class ProxySubject implements Subject {
	Subject realsubject;
	public ProxySubject() {
		this.realsubject = new RealSubject();
	}
	public String doAction(String name) {
		System.out.println("proxy control");
		String rtnValue = realsubject.doAction(name);
		return "SUCCESS";
	}
}

在软件开发中,代理模式是一种常用的设计模式。该模式下,一个代理类充当了真实服务类的中介,为外部程序提供了与真实服务类相同的接口。当外部应用程序调用代理类的方法时,代理类会将请求转发给实际的服务类执行,并将结果返回给调用者。这样设计的好处在于能够隐藏服务类的具体实现细节。

代理模式的工作流程

  1. 定义公共接口:首先定义一个所有参与者都必须遵循的公共接口。这个接口可以被真正的服务类和服务代理类所实现。

  2. 创建真实的服务类:按照公共接口来实现具体的服务逻辑。

  3. 创建代理类:同样地,代理类也需要实现上述公共接口。它持有一个对真实服务对象的引用,并且在接收到请求时,代理类可以选择性地增加一些额外处理(比如权限验证、日志记录等),之后再将请求转发给真实的对象进行处理。

  4. 客户端通过代理访问服务:最后,客户端不再直接使用服务类,而是通过代理类来进行交互。对于客户端而言,整个过程是透明的。

示例说明

  • 假设我们有ServiceInterface这样一个接口以及实现了它的RealService类。

  • 接着定义ServiceProxy作为代理类,它也实现了ServiceInterface。在这个代理类中,除了转发调用到RealService外,还可以加入其他功能如打印控制信息System.out.println(" ");,这里用来表示额外的日志记录或其他操作。

  • 客户端代码只需要关心如何通过代理来调用方法即可,无需了解背后是如何工作的。 采用这种结构,不仅有助于增强系统灵活性和可维护性,同时也方便于后期添加新的功能或修改现有逻辑而不需要改变原有架构。

1
2
3
4
5
6
public class Client {
	public static void main(String[] args) {
		Subject subject = new ProxySubject();
		subject.doAction("Test");
	}
}

代理模式允许我们在不修改原有业务逻辑的情况下,通过代理类添加额外的处理逻辑。然而,传统的静态代理模式需要事先定义代理类,这限制了其灵活性。为了解决这个问题,我们可以利用Java的动态代理技术,它允许我们在运行时动态地创建代理对象。以下是使用Java动态代理的三个主要步骤:

  1. 实现InvocationHandler接口:创建一个类实现InvocationHandler接口,并重写其invoke方法,以便在代理方法调用时执行自定义逻辑。

  2. 使用Proxy类创建代理对象:通过Proxy类的newProxyInstance方法,我们可以在运行时动态地创建一个实现了指定接口的代理对象。

  3. 增强代理对象:通过代理对象,我们可以对目标对象的方法调用进行增强处理,即在原有业务逻辑前后添加额外的处理逻辑。 下面是一个简单的例子来说明如何定义一个接口IAction,并使用Java动态代理技术来实现代理。

1
2
3
4
package com.test.service;
public interface IAction {
   void doAction();
}

提供一个具体实现类。

1
2
3
4
5
6
7
package com.test.service;
public class Action1 implements IAction {
   @Override
   public void doAction() {
      System.out.println("really do action");
   }
}

我们定义了一个DynamicProxy类,用来充当代理对象的类。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.test.service;
public class DynamicProxy {
   private Object subject = null;

   public DynamicProxy(Object subject) {
         this.subject = subject;
   }

   public Object getProxy() {
      return Proxy.newProxyInstance(DynamicProxy.class
            .getClassLoader(), subject.getClass().getInterfaces(),
            new InvocationHandler() {
         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getName().equals("doAction")) {
                System.out.println("before call real object........");
                return method.invoke(subject, args);
            }
            return null;
         }
      });
   }
}

在本示例中,我们利用了Java的Proxy类来创建一个实现了IAction接口的动态代理对象。通过实现InvocationHandler接口,并重写其invoke方法,我们能够对所有通过代理对象发起的方法调用进行拦截。在这个invoke方法里,我们首先检查被调用的方法名是否与IAction接口中的doAction方法相匹配。如果匹配成功,则会在实际执行doAction方法之前加入一段例行性的逻辑(这里采用打印一条信息)。最终,通过反射机制调用原始IAction接口的doAction方法。

这样,在不改动原有业务代码的情况下,我们就可以在运行时为程序添加额外的功能或行为。

为了更好地理解这一过程及其效果,我们可以编写一个简单的测试程序,通过它来观察代理模式是如何工作的以及它给我们的应用带来了哪些变化。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package com.test.controller;
public class HelloWorldBean {
    @Autowired
    IAction action;

    @RequestMapping("/testaop")
    public void doTestAop(HttpServletRequest request, HttpServletResponse response) {
     DynamicProxy proxy = new DynamicProxy(action);
     IAction p = (IAction)proxy.getProxy();
     p.doAction();

     String str = "test aop, hello world!";
     try {
        response.getWriter().write(str);
     } catch (IOException e) {
        e.printStackTrace();
     }
  }
}

运行这个程序,返回内容是“test aop,hello world!”。这个时候查看命令行里的内容,你就会发现还有两行输出。

1
2
before call real object........
really do action

第一行是代理对象中的输出,第二行是Action1中doAction方法的实现。根据这个输出顺序我们发现,这个代理对象达到了代理的效果,在调用IAction的具体实现类之前进行了额外的操作,从而增强了代理类。而这个代理是我们动态增加的,而不是事先静态地手工编写一个代理类。但是读代码,这种方式显然是不美观的,需要在业务逻辑程序中写上,对代码的侵入性太强了。

1
2
DynamicProxy proxy = new DynamicProxy(action);
IAction p = (IAction)proxy.getProxy();

在软件开发中,我们追求的是一种非侵入式编程方式。这种方式意味着在应用程序编程时,不需要手动创建代理类,而是直接使用业务接口。真正的实现类和代理类的配置都放在外部,以减少代码的侵入性。

非侵入式编程的优势

非侵入式编程的主要优点在于它减少了代码的耦合度,使得代码更加灵活和易于维护。

FactoryBean的作用

为了实现非侵入式编程,我们可以引入FactoryBean。FactoryBean是一个特殊的Bean,它允许我们在Spring容器中创建复杂的对象,而不需要在代码中直接实例化它们。这样,我们可以将对象的创建逻辑从业务逻辑中分离出来,进一步降低代码的侵入性。

实现非侵入式编程的步骤

  1. 定义业务接口:首先,我们需要定义一个业务接口,这个接口将被应用程序直接使用。
  2. 配置实现类:然后,在外部配置文件中配置实现这个接口的具体类。
  3. 配置代理类:同样在外部配置文件中,配置代理类,这个代理类将负责拦截接口调用并执行额外的逻辑。
  4. 使用FactoryBean:通过FactoryBean,我们可以在Spring容器中注册业务接口和代理类,而不需要在代码中直接创建它们。这样,应用程序就可以通过业务接口与Spring容器交互,而不需要关心具体的实现细节。
1
2
3
4
5
6
7
@Autowired
IAction action;

@RequestMapping("/testaop")
public void doTestAop(HttpServletRequest request, HttpServletResponse response) {
    action.doAction();
}

配置如下:

1
2
3
4
<bean id="realaction" class="com.test.service.Action1" />
<bean id="action" class="com.minis.aop.ProxyFactoryBean" >
    <property type="java.lang.Object" name="target" ref="realaction"/>
</bean>

在Spring框架中,我们有时会遇到一种特殊的需求:注册的bean是一个ProxyFactoryBean类,而实际业务逻辑需要的是这个ProxyFactoryBean内部所包含的目标对象(即realAction)。这意味着当我们通过getBean("action")来获取bean时,并非直接得到ProxyFactoryBean实例本身,而是希望获得它内部封装的那个目标对象。这样的设计允许我们在调用实际业务方法之前或之后插入额外的行为,比如日志记录、性能监控等。

实现思路

为了满足上述需求,ProxyFactoryBean需要扮演双重角色:一方面作为容器中的一个bean存在;另一方面,在被请求时能够返回其内部的target对象或基于target动态生成的一个代理对象。这样做的好处是,可以实现对target对象的方法调用进行增强而不影响原始代码结构。

关键点解释

  • ProxyFactoryBean 是Spring提供的用于创建AOP代理的工厂bean。

  • Target Object ProxyFactoryBean内部持有的真正执行业务逻辑的对象。

  • Dynamic Proxy 根据target对象及指定的拦截器配置动态生成的代理对象,该对象会在调用target方法前后执行额外操作。

应用场景

这种机制非常适合于那些需要为已有服务添加横切关注点的应用场合,例如安全检查、事务管理等。通过这种方式,我们可以有效地分离业务逻辑与系统级别的行为,保持代码的清晰度和可维护性。

1
2
DynamicProxy proxy = new DynamicProxy(action);
IAction p = (IAction)proxy.getProxy();

上面的方案,看起来奇怪,但是确实能解决动态代理的问题。

好,现在我们就按照这个思路动手去实现。首先我们参考Spring框架,定义FactoryBean接口。

相关代码参考:

1
2
3
4
5
6
7
8
package com.minis.beans.factory;
public interface FactoryBean<T> {
    T getObject() throws Exception;
    Class<?> getObjectType();
    default boolean isSingleton() {
        return true;
    }
}

主要的方法就是getObject(),从Factory Bean中获取内部包含的对象。

接着定义FactoryBeanRegistrySupport,提供一部分通用的方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.minis.beans.factory.support;
public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanRegistry{
    protected Class<?> getTypeForFactoryBean(final FactoryBean<?> factoryBean) {
        return factoryBean.getObjectType();
    }
    protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName) {
        Object object = doGetObjectFromFactoryBean(factory, beanName);
        try {
            object = postProcessObjectFromFactoryBean(object, beanName);
        } catch (BeansException e) {
            e.printStackTrace();
        }
        return object;
    }
    //从factory bean中获取内部包含的对象
    private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName) {
        Object object = null;
        try {
            object = factory.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return object;
    }
}

在Spring框架中,FactoryBean是一个特殊的Bean,它用于生成其他Bean的实例。为了从FactoryBean中获取内部包含的目标对象,我们通常需要调用doGetObjectFromFactoryBean()方法。由于FactoryBeanRegistrySupport继承了DefaultSingletonBeanRegistry,我们可以改写AbstractBeanFactory,使其继承FactoryBeanRegistrySupport而不是DefaultSingletonBeanRegistry。这样,我们在保留原有功能的同时,增加了对FactoryBean的支持。接下来,我们需要重点修改核心的getBean()方法,以确保能够正确处理FactoryBean并从中获取目标对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.minis.beans.factory.support;
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory,BeanDefinitionRegistry{
   public Object getBean(String beanName) throws BeansException{
      Object singleton = this.getSingleton(beanName);
      if (singleton == null) {
         singleton = this.earlySingletonObjects.get(beanName);
         if (singleton == null) {
            System.out.println("get bean null -------------- " + beanName);
            BeanDefinition bd = beanDefinitionMap.get(beanName);
            if (bd != null) {
               singleton=createBean(bd);
               this.registerBean(beanName, singleton);
               //beanpostprocessor
               //step 1 : postProcessBeforeInitialization
               applyBeanPostProcessorsBeforeInitialization(singleton, beanName);
               //step 2 : init-method
               if (bd.getInitMethodName() != null && !bd.getInitMethodName().equals("")) {
                  invokeInitMethod(bd, singleton);
               }
               //step 3 : postProcessAfterInitialization
               applyBeanPostProcessorsAfterInitialization(singleton, beanName);
            }
            else {
               return null;
            }
         }
      }
      else {
      }
      //处理factorybean
      if (singleton instanceof FactoryBean) {
         return this.getObjectForBeanInstance(singleton, beanName);
      }
      else {
      }
      return singleton;
   }

我们看到在getBean()这一核心方法中,原有的逻辑处理完毕后,我们新增下面这一段。

1
2
3
4
//process Factory Bean
if (singleton instanceof FactoryBean) {
   return this.getObjectForBeanInstance(singleton, beanName);
}

根据代码实现可以看出,这里增加了一个判断,如果Bean对象是FactoryBean类型时,则调用getObjectForBeanInstance方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
protected Object getObjectForBeanInstance(Object beanInstance, String beanName) {
    // Now we have the bean instance, which may be a normal bean or a FactoryBean.
    if (!(beanInstance instanceof FactoryBean)) {
         return beanInstance;
    }
    Object object = null;
    FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
    object = getObjectFromFactoryBean(factory, beanName);
    return object;
}

代码显示,getObjectForBeanInstance又会调用doGetObjectFromFactoryBean方法。

1
2
3
4
5
6
7
8
9
private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName) {
    Object object = null;
    try {
        object = factory.getObject();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return object;
}

在Spring框架中,Bean的获取过程是一个复杂而精细的操作。以下是对这一过程的详细解析:

  1. Bean获取过程
  • 当我们通过AbstractBeanFactory获取Bean时,如果这个Bean是一个FactoryBean,那么Spring框架会对其进行特殊处理。

  • 特殊处理的结果是,我们最终得到的并不是FactoryBean的实例本身,而是它所包含的内部对象。

  1. FactoryBean的内部对象
  • 这个内部对象也不是最终的底层Bean,而是它的一个代理对象。这意味着我们需要进一步探索以获取真正的Bean。
  1. FactoryBean的实现
  • FactoryBean是一个接口,它定义了Bean工厂的行为。

  • 在实际应用中,我们需要提供一个FactoryBean的实现,这里提到的是ProxyFactoryBean

  • ProxyFactoryBeanFactoryBean接口的一个具体实现,用于创建代理对象。

  1. 代理对象的作用
  • 代理对象的主要作用是为Bean提供额外的功能,比如懒加载、事务管理等,而不改变Bean本身的代码。

  • 通过代理对象,我们可以在不侵入Bean代码的情况下,为其增加额外的行为。

  1. 总结
  • 通过AbstractBeanFactory获取Bean时,如果Bean是FactoryBean类型,我们得到的是它的内部对象,而不是FactoryBean本身。

  • 这个内部对象实际上是一个代理对象,它为我们提供了进一步获取底层Bean的途径。

  • ProxyFactoryBean作为FactoryBean的实现,负责创建这些代理对象,增强Bean的功能。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.minis.aop;
public class ProxyFactoryBean implements FactoryBean<Object> {
    private AopProxyFactory aopProxyFactory;
    private String[] interceptorNames;
    private String targetName;
    private Object target;
    private ClassLoader proxyClassLoader = ClassUtils.getDefaultClassLoader();
    private Object singletonInstance;
    public ProxyFactoryBean() {
        this.aopProxyFactory = new DefaultAopProxyFactory();
    }
    public void setAopProxyFactory(AopProxyFactory aopProxyFactory) {
        this.aopProxyFactory = aopProxyFactory;
    }
    public AopProxyFactory getAopProxyFactory() {
        return this.aopProxyFactory;
    }
    protected AopProxy createAopProxy() {
        return getAopProxyFactory().createAopProxy(target);
    }
    public void setInterceptorNames(String... interceptorNames) {
        this.interceptorNames = interceptorNames;
    }
    public void setTargetName(String targetName) {
        this.targetName = targetName;
    }
    public Object getTarget() {
        return target;
    }
    public void setTarget(Object target) {
        this.target = target;
    }
    @Override
    public Object getObject() throws Exception {//获取内部对象
        return getSingletonInstance();
    }
    private synchronized Object getSingletonInstance() {//获取代理
        if (this.singletonInstance == null) {
            this.singletonInstance = getProxy(createAopProxy());
        }
        return this.singletonInstance;
    }
    protected Object getProxy(AopProxy aopProxy) {//生成代理对象
        return aopProxy.getProxy();
    }
    @Override
    public Class<?> getObjectType() {
        return null;
    }
}

这段代码的核心在于,ProxyFactoryBean在getObject()方法中生成了一个代理getProxy(createAopProxy()),同样也是通过这种方式,拿到了要代理的目标对象。这里的工作就是 创建动态代理

基于JDK的实现

Spring作为一个雄心勃勃的框架,自然不会把自己局限于JDK提供的动态代理一个技术上,所以,它再次进行了包装,提供了AopProxy的概念,JDK只是其中的一种实现。

1
2
3
4
package com.minis.aop;
public interface AopProxy {
    Object getProxy();
}

还定义了factory。

1
2
3
4
package com.minis.aop;
public interface AopProxyFactory {
    AopProxy createAopProxy(Object target);
}

然后给出了基于JDK的实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package com.minis.aop;
public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
    Object target;
    public JdkDynamicAopProxy(Object target) {
        this.target = target;
    }
    @Override
    public Object getProxy() {
        Object obj = Proxy.newProxyInstance(JdkDynamicAopProxy.class.getClassLoader(), target.getClass().getInterfaces(), this);
        return obj;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("doAction")) {
            System.out.println("-----before call real object, dynamic proxy........");
            return method.invoke(target, args);
        }
        return null;
    }
}
1
2
3
4
5
6
7
package com.minis.aop;
public class DefaultAopProxyFactory implements AopProxyFactory{
    @Override
    public AopProxy createAopProxy(Object target) {
		return new JdkDynamicAopProxy(target);
    }
}

Java动态代理与Spring AOP实现原理

在Java中,动态代理是一种强大的技术,它允许我们在运行时动态地创建对象的代理。Proxy.newProxyInstance()方法和invoke()方法是我们实现动态代理的关键。通过这种方式,Spring框架中的ProxyFactoryBean能够返回代理对象,而不需要手动构建。

动态代理技术的应用

利用Java的动态代理技术,我们可以代理目标对象,实现AOP(面向切面编程)。这是Spring AOP的核心实现原理之一。

测试Spring AOP

通过Spring AOP,我们不再需要手动构建代理对象。框架会自动处理这些工作,而我们只需要通过配置文件注入属性值即可。

applicationContext.xml配置

在Spring的配置文件applicationContext.xml中,我们可以新增一段内容来配置AOP。

1
2
3
4
<bean id="realaction" class="com.test.service.Action1" />
<bean id="action" class="com.minis.aop.ProxyFactoryBean" >
    <property type="java.lang.Object" name="target" ref="realaction"/>
</bean>

通过配置,我们在HelloWorldBean里注入的IAction对象就纳入了容器管理之中,因此后续测试的时候,直接使用action.doAction(),就能实现手动初始化JDK代理对象的效果。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package com.test.controller;
public class HelloWorldBean {
   @Autowired
   IAction action;

   @RequestMapping("/testaop")
   public void doTestAop(HttpServletRequest request, HttpServletResponse response) {
      action.doAction();

      String str = "test aop, hello world!";
      try {
         response.getWriter().write(str);
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
}

在这节课中,我们探讨了如何通过JDK动态代理技术实现AOP(面向切面编程)的概念。aaaaaaaAOP是一种编程范式,它允许开发者将横切关注点(如日志、事务管理等)与业务逻辑分离。 aaaaaaa首先,我们介绍了静态代理的基本概念,这是一种设计模式,其中代理类手动为每个被代理的接口方法提供一个实现。接着,我们转向了JDK动态代理,这是一种更为灵活的方法,能够在运行时创建代理对象。然而,直接使用JDK动态代理是侵入式的,因为它需要具体的代理类代码。为了改善这一点,我们将代理配置移到了XML文件中。 aaaaaaa但是,简单的外部配置不足以创建真正的代理。因此,我们引入了FactoryBean的概念,它是一个特殊的Spring Bean,其目的是创建并返回一个特定的对象,而不是返回它本身。通过FactoryBean中的getObject()方法,我们可以创建一个基于JDK动态代理技术的代理对象。这样,IoC容器中的Bean就可以分为两大类:普通Bean和FactoryBean。 aaaaaaa在getObject()方法的实现中,我们利用JDK动态代理来创建了一个代理对象,从而实现了AOP的功能。此外,Spring框架支持两种代理方式:JDK动态代理和Cglib代理。目前我们的MiniSpring仅支持JDK代理,但我们也提到了可以思考如何扩展以支持Cglib代理。 aaaaaaa最后,虽然Spring AOP简单易懂,但在实际工程实践中,AspectJ因其更高的性能和更全面的功能而更加常用。AspectJ在编译时创建代理,且切入点不仅限于方法级别,因此提供了完整的AOP解决方案。尽管如此,Spring AOP仍然保留下来,部分原因在于它的原理易于理解,以及对原创作者心血的尊重。 aaaaaaa课后题 aaaaaaa如果你考虑为MiniSpring添加Cglib代理的支持,你需要考虑修改哪些地方?请在留言区分享你的想法。也欢迎你将这节课的内容分享给更多的人。下一节课见!