静态代理设计模式
为什么需要代理设计模式?
问题
-
在JavaEE分层开发中,哪个层次对于我们来讲最重要?
DAO --> Service --> Controller
JavaEE分层开发中,最为重要的是Service层。 -
Service层中包含了哪些代码?
Service层 = 核心功能(几十上百行代码) + 额外功能(附加功能)
核心功能:业务运算、DAO调用
额外功能:不属于业务、可有可无、代码量很小(如事务、日志、性能、…)
-
额外功能书写在Service层中好不好?
Service层的调用者的角度(Controller):需要在Service层书写额外功能(事务)。
软件设计者的角度:Service层不需要额外功能,因为不好维护。 -
现实中的问题和解决办法:
租房场景
租房场景改造
代理设计模式
概念
通过代理类,为原始类(目标)增加额外的功能。
好处:利于原始类(目标)的维护。
名词解释
- 目标类(原始类):指的是业务类(核心功能 --> 业务运算、DAO调用)
- 目标方法(原始方法):目标类(原始类)中的方法就是目标方法(原始方法)。
- 额外功能(附加功能):日志、事务、性能、…
代理开发的核心要素
代理类 = 目标类(原始类) + 额外功能 + 原始类(目标类)实现相同的接口(为了与目标类(原始类)的方法保持一致)
编码
静态代理:为每一个原始类,手动创建一个代理类(.java .class)
//代理类实现与原始类相同的接口,保证方法一致
public class UserServiceProxy implements UserService {
//原始类
private UserServiceImpl userService = new UserServiceImpl();
@Override
public void register(User user) {
//额外功能
System.out.println("-----log-----");
//调用原始类中的方法
userService.register(user);
}
@Override
public boolean login(String name, String password) {
//额外功能
System.out.println("-----log-----");
//调用原始类中的方法
return userService.login(name, password);
}
}
对于每个Service原始类都需要一个对应的代理类,代码冗余。
静态代理存在的问题
- 代理类文件数量过多,不利于项目的管理。
UserServiceImpl --> UserServiceProxy
OrderServiceImpl --> OrderServiceProxy
… - 额外功能维护性差
代理类中,额外功能修改复杂(麻烦)。一百个就需要修改一百次。
Sring的动态代理开发
Spring动态代理的概念
概念:通过代理类,为原始类(目标)增加额外的功能。
好处:利于原始类(目标)的维护。
搭建开发环境
引入jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
<scope>runtime</scope>
</dependency>
Spring动态代理的开发步骤
-
创建原始对象(目标对象)
public class UserServiceImpl implements UserService { @Override public void register(User user) { //核心功能 System.out.println("UserServiceImpl.register (业务运算+DAO调用)"); } @Override public boolean login(String name, String password) { //核心功能 System.out.println("UserServiceImpl.login"); return true; } }
<bean id="userService" class="com.angenin.proxy.UserServiceImpl"/>
-
额外功能
Spring提供了MethodBeforeAdvice接口,我们需要把额外功能书写在此接口的实现类的before方法中,这个实现类的before方法会在原始方法执行前先执行。public class Before implements MethodBeforeAdvice { //书写运行在原始方法之前的额外功能 @Override public void before(Method method, Object[] objects, Object o) throws Throwable { //额外功能 System.out.println("Before...before..."); } }
<bean id="before" class="com.angenin.dynamic.Before"/>
-
定义切入点
切入点:额外功能加入的位置。
目的:由程序员根据自己的需要,决定额外功能加入给哪个原始方法。
简单的测试:所有方法都作为切入点,都加入额外功能。<!--配置aop--> <aop:config> <!--配置切入点,id可任意起,expression:切入点表达式,这里代表所有的方法--> <aop:pointcut id="pc" expression="execution(* *(..))"/> </aop:config>
-
组装(2和3的整合)
<!--配置aop--> <aop:config> <!--配置切入点,id可任意起,expression:切入点表达式,这里代表所有的方法--> <aop:pointcut id="pc" expression="execution(* *(..))"/> <!--组装:把额外功能和切入点进行整合,advice-ref:额外功能,pointcut-ref:切入点--> <!--切入点包含的所有方法执行前都会先执行before的额外功能--> <aop:advisor advice-ref="before" pointcut-ref="pc"/> </aop:config>
-
调用
获得Spring工厂创建的动态代理对象,并进行调用。此时,通过原始对象的id值获得的就是代理对象,生成的对象用原始类的接口类型进行接收。
打断点debug后可以看到获得的userService对象为代理对象。
动态代理细节分析
-
Spring创建的动态代理类在哪里?
Spring框架在运行时,通过动态字节码技术,在JVM创建的,运行在JVM内部,等程序结束后,会和JVM一起消失。什么叫动态字节码技术?
通过第三方动态字节码框架,在JVM中创建对应类的字节码,进而创建对象,当虛拟机结束,动态字节码跟着消失。
结论:动态代理不需要定义类文件,都是JⅥM运行过程中动态创建的,所以不会造成静态代理,类文件数量过多,影晌项目管理的问题。 -
动态代理编程简化代理的开发。
在额外功能不改变的前提下,创建其他目标类(原始类)的代理对象时,只需要指定原始(目标)对象即可。 -
动态代理额外功能的维护性大大增强。
Spring动态代理详解
额外功能的详解
MethodBeforeAdvice分析
- MethodBeforeAdvice接口的作用:额外功能运行在原始方法执行之前,进行额外功能操作。
public class Before implements MethodBeforeAdvice { /* 作用:书写运行在原始方法之前的额外功能 参数: Method:额外功能所增加给的那个原始方法(如:login方法、register方法) Object[]:额外功能所增加给的那个原始方法的参数(如login方法的String name, String password) Object:额外功能所增加给的那个原始对象(如UserServiceImpl) */ @Override public void before(Method method, Object[] objects, Object o) throws Throwable { //额外功能 System.out.println("Before...before..."); } }
- before方法的3个参数在实战中,该如何使用?
before方法的参数,在实战中,会根据需要进行使用,不一定都会用到,也有可能都不用,实战开发中before的三个参数用得比较少。
MethodInterceptor(方法拦截器)
MethodBeforeAdvice --> 运行在原始方法之前
MethodInterceptor接口:额外功能可以根据需要运行在原始方法执行前、后、前后以及原始方法执行出现异常时执行,MethodInterceptor接口功能比MethodBeforeAdvice接口更加强大,所以更推荐使用MethodInterceptor接口。
//这里的MethodInterceptor是aop包下的那个
public class Around implements MethodInterceptor {
/*
invoke方法的作用:额外功能书写在invoke
额外功能执行的时机 1. 原始方法之前
2. 原始方法之后
3. 原始方法之前 和 之后
4. 原始方法执行异常时
调用参数methodInvocation的proceed()方法,可以让原始方法执行,所以我们可以在执行前写额外功能,也可以在之后写,或者都写
参数:
methodInvocation:额外功能所增加给的那个原始方法
返回值:
Object:原始方法执行后的返回值
*/
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
//额外功能1
System.out.println("额外功能在原始方法执行前执行-------");
//原始方法
Object ret = methodInvocation.proceed();
//额外功能2
//System.out.println("额外功能在原始方法执行后执行-------");
//返回的是原始方法执行后的返回值
return ret;
}
}
<bean id="around" class="com.angenin.dynamic.Around"/>
<!--配置aop-->
<aop:config>
<!--配置切入点,id可任意起,expression:切入点表达式,这里代表所有的方法-->
<aop:pointcut id="pc" expression="execution(* *(..))"/>
<!--组装:把额外功能和切入点进行整合,advice-ref:额外功能,pointcut-ref:切入点-->
<aop:advisor advice-ref="around" pointcut-ref="pc"/>
</aop:config>
什么的额外功能需要在原始方法运行的前后都运行呢?事务等。
-
额外功能运行在原始方法抛出异常时
public Object invoke(MethodInvocation methodInvocation) throws Throwable { //额外功能1 //System.out.println("额外功能在原始方法执行前执行-------"); Object ret = null; try { //原始方法 ret = methodInvocation.proceed(); } catch (Throwable throwable) { System.out.println("额外功能在原始方法抛出异常时执行-------"); throwable.printStackTrace(); } //额外功能2 //System.out.println("额外功能在原始方法执行后执行-------"); return ret; }
具体额外功能执行的时机,需要按照需求进行合理的安排。
-
MethodInterceptor影响原始方法的返回值
原始方法的返回值,直接作为invoke方法的返回值返回,MethodInterceptor不会影响原始方法的返回值。
不返回原始方法的返回值,就会影响到结果。@Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { Object ret = methodInvocation.proceed(); //return 1; return false; }
切入点详解
切入点决定额外功能加入的位置(这里的位置指的是方法,切入哪些方法)。
<aop:pointcut id="pc" expression="execution(* *(..))"/>
execution(* *(..)):匹配了所有方法
execution():切入点函数
* *(..)
切入点表达式
1. 方法切入点表达式
* *(..) --> 所有方法
* --> 修饰符 返回值
* --> 方法名
() --> 参数表
.. --> 对参数没有要求(参数有没有、有几个、什么类型都行)
- 定义名为login的方法作为切入点:
* login(..)
- 定义名为login并且有两个字符串类型参数的方法作为切入点:
* login(String,String) 注意: 非java.lang包中的类型,必须写全限定类名 如:* register(com.angenin.proxy.User) ..可以和具体的参数类型连用 如:* login(String,..) 第一个参数为String类型的login方法
- 精准方法切入点限定
上面所讲解的方法切入点表达式不精准。
如下图,不能精准到a包下的UserServiceImpl的第一个login方法。
精准切入
修饰符/返回值 包.类.方法(参数) * com.baizhiedu.a.UserServiceImpl.login(String,String) 精准到a包下的UserServiceImpl的第一个login方法
2. 类切入点表达式
指定特定类作为切入点(额外功能加入的位置),自然这个类中的所有方法,都会加上对应的额外功能。
- 语法一
a包下UserServiceImpl类中的所有方法都加入额外功能 * com.baizhiedu.a.UserServiceImpl.*(..)
- 语法二
忽略包 1. 类只存在一级包 com.UserServiceImpl * *.UserServiceImpl.*(..) 2. 类存在多级包 com.baizhiedu.a.UserServiceImpl * *..UserServiceImpl.*(..)
3. 包切入点表达式
指定包作为额外功能加入的位置,自然包中的所有类及其方法都会加入额外的功能。
- 语法一
切入点包中的所有类,下面这条表达式包括的类必须在proxy包中,即使在proxy的子包中也无法生效。 * com.baizhiedu.proxy.*.*(..)
- 语法二
包切入点在实战中使用得更多,把相加额外功能的类放到同个包下即可。切入点当前包及其子包都生效(多加一个点) * com.baizhiedu.proxy..*.*(..)
切入点函数
切入点函数:用于执行切入点表达式。
-
execution
最为重要的切入点函数,功能最全。
可以执行方法切入点表达式、类切入点表达式、包切入点表达式。弊端:execution执行切入点表达式,书写麻烦。
注意:其他的切入点函数只是简化了execution书写的复杂度,在功能上完全一致。 -
args
作用:主要用于函数(方法)参数的匹配,关注点在于参数,忽略包、类和方法名。如:切入点为方法参数必须得是2个字符串类型的参数。 execution(* *(String,String)) 替换后 args(String,String)
-
within
作用:主要用于进行类、包切入点表达式的匹配,关注点在于包名、类名,忽略方法名和参数。如:切入点为UserServiceImpl类。 execution(* *..UserServiceImpl.*(..)) 替换后 within(*..UserServiceImpl) 如:切入点为proxy包下及其子包的所有类 execution(* com.baizhiedu.proxy..*.*(..)) 替换后 within(com.baizhiedu.proxy..*)
-
@annotation
作用:为具有特殊注解的方法加入额外功能。自定义一个Log注解。
@Target(ElementType.METHOD) //决定在什么位置起作用(这里是在方法上起作用) @Retention(RetentionPolicy.RUNTIME) //决定在什么时候起作用(这里在运行时起作用) public @interface Log {}
为UserServiceImpl类的register方法加上@Log注解。
<aop:config> <!--@annotation括号里为注解的全限定类名--> <aop:pointcut id="pc" expression="@annotation(com.angenin.proxy.Log)"/> <aop:advisor advice-ref="around" pointcut-ref="pc"/> </aop:config>
切入点函数的逻辑运算
指的是 整合多个切入点函数一起配合工作,进而完成更为复杂的需求。
-
and 与操作
案例:方法名为login,并且参数为2个字符串。 execution(* login(String,String)) 替换后 execution(* login(..)) and args(String,String) 注意:与操作不能用于同种类型的切入点函数,因为and需要同时满足。 如:login方法和register方法都作为切入点(这样写是错误的,可以换成or) execution(* login(..)) and execution(* register(..))
-
or 或操作
案例:login方法和register方法都作为切入点 execution(* login(..)) or execution(* register(..))
AOP开发
AOP的概念
AOP(Aspect Oriented Programing)面向切面编程 = Spring动态代理开发
以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建。
切面 = 切入点 + 额外功能OOP(Object Oritened Programing)面向对象编程 Java
以对象为基本单位的程序开发,通过对象间的彼此协同,相互调用,完成程序的构建。POP(Procedure Oriented Programing)面向过程(方法、函数)编程 C语言
以过程为基本单位的程序开发,通过过程间的彼此协同,相互调用,完成程序的构建。
AOP的概念:
- 本质就是Spring的动态代理开发,通过代理类为原始类增加额外功能。
- 好处:利于原始类的维护。
- 注意:AOP是基于OOP的,所以AOP不可能取代OOP,AOP是OOP为了解耦而做的补充。
AOP的开发步骤
- 原始对象
- 额外功能(MethodInterceptor)
- 切入点
- 组装切面(额外功能 + 切入点)
切面的名词解释
切面 = 切入点 + 额外功能
几何学
面 = 点 + 形同的性质
AOP的底层实现原理
核心问题
- AOP如何创建动态代理类?(动态字节码技术)
- Spring工厂如何加工创建代理对象?为什么通过原始对象的id值,获得的是代理对象?
动态代理类的创建
1. JDK的动态代理
Proxy.newProxyInstance方法参数详解
关于类加载器:
首先得了解类加载器的作用,及其对象生成的过程。
根据老师讲的内容画的图,如果画得不对不好,麻烦各位请指出来。
>
因为代理类没有字节码文件,所以JVM没有给它生成它的类加载器,需要借用其他类的类加载器,所以Proxy.newProxyInstance的第一个参数为其他类的类加载器(原始类或者其他类都可以)。
- 原始对象直接创建。
- 额外功能由InvocationHandler接口的invoke方法解决。
- 相同接口由原始对象获取其Class,再获取其实现的接口。
InvocationHandler的invoke与MethodInterceptor的invoke对比:
代码实现
public class TestJdkProxy {
public static void main(String[] args) {
//代理创建3要素:1.原始对象;2.额外功能;3.代理对象和原始对象实现相同的接口。
// 1.创建原始对象
UserServiceImpl userService = new UserServiceImpl();
// 2.JDK创建动态代理
//这里为了下面来起来整洁一点,单独拿开,平时用匿名内部类即可
InvocationHandler handler = new InvocationHandler() {
/*
InvocationHandler的invoke方法:
作用:用于书写额外功能,额外功能运行在原始方法执行前、后、前后、抛出异常
参数:
proxy:忽略即可,代表的是代理对象
method:原始方法(增加额外功能的那个原始方法)
args:原始方法的参数
method.invoke方法:执行原始方法,返回原始方法执行后的结果,
第一个参数为原始对象
第二个参数为原始方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("----proxy log-----");
//传入原始对象和原始方法的参数
//这里需要注意,内部类使用外部类的变量时,外部类的变量需要用final来修饰
Object ret = method.invoke(userService, args);
//返回原始方法执行后的结果
return ret;
}
};
/*
第一个参数为借用的类加载器(任意类都可以)
第二个参数为原始类实现的接口,通过获取原始对象的class再获取接口(userService.getClass().getInterfaces())
第三个参数为InvocationHandler接口,在此接口的invoke方法中书写额外功能
*/
UserService userServiceProxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(), handler);
//因为InvocationHandler接口是一个函数式接口,所以可以使用Lambda表达式,代码变得更加简洁(因为main方法的参数同样为args,所以需要改一下)
// UserService userServiceProxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
// userService.getClass().getInterfaces(), (proxy, method, argss) -> {
// System.out.println("----log-----");
// return method.invoke(userService, argss);
// });
userServiceProxy.login("angenin", "123456");
userServiceProxy.register(new User());
}
}
2. CGlib的动态代理
CGlib创建动态代理的原理:父子继承创建代理对象,原始类作为父类,代理类作为子类,这样既可以保证二者方法一致,同时在代理类中提供新的实现(额外功能 + 原始方法)。
CGlib编码实现
public class TestCglib {
public static void main(String[] args) {
// 1.创建原始对象
UserService userService = new UserService();
/*
2. 通过Cglib方式创建动态代理对象
JDK动态代理:Proxy.newProxyInstance(classloader,interface,invocationhandler)
Cglib动态代理:
设置类加载器: Enhancer.setClassloader() --> 同样,借用任意一个类的类加载器
设置父类: Enhancer.setSuperClass()
设置额外功能: Enhancer.setCallback() --> 实现接口MethodInterceptor
Enhancer.create() ---> 创建代理
*/
Enhancer enhancer = new Enhancer();
//设置类加载器
enhancer.setClassLoader(userService.getClass().getClassLoader());
//设置父类
enhancer.setSuperclass(userService.getClass());
//这里的MethodInterceptor接口是spring的cglib包下的那个
//可以用匿名内部类,这里只是为了看起来简洁
MethodInterceptor interceptor = new MethodInterceptor() {
//这里的intercept方法 等同于 InvocationHandler的invoke方法
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("------cglib log------");
//这里调用method的invoke和前面的JDK动态代理一样
Object ret = method.invoke(userService, args);
return ret;
}
};
//设置额外功能
enhancer.setCallback(interceptor);
//创建代理
UserService userServiceProxy = (UserService) enhancer.create();
userServiceProxy.login("angenin", "123456");
userServiceProxy.register(new User());
}
}
总结
1. JDK动态代理 Proxy.newProxyInstance() 通过接口创建代理的实现类
2. Cglib动态代理 Enhancer 通过继承父类创建的代理类
代理都是为了面向切面服务的。
Spring工厂如何加工原始对象
思路分析
BeanPostProcessor在上一篇文章里讲到。
编码实现:
- 实现 BeanPostProcessor 进行加工
public class ProxyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { //返回生成的理对象 return Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(), (proxy, method, args) -> { System.out.println("----new log-----"); //返回原始类的执行结果 return method.invoke(bean, args); }); } }
- 配置文件中对 BeanPostProcessor 进行配置
<bean id="userService" class="com.angenin.factory.UserServiceImpl"/> <bean id="proxyBeanPostProcessor" class="com.angenin.factory.ProxyBeanPostProcessor"/>
- 测试
public class Test { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext4.xml"); UserService userService = (UserService) ctx.getBean("userService"); userService.login("angenin", "123456"); userService.register(new User()); } }
基于注解的AOP开发
基于注解的AOP开发步骤
- 原始对象
public class UserServiceImpl implements UserService { @Override public void register(User user) { System.out.println("UserServiceImpl.register (业务运算+DAO调用)"); } @Override public boolean login(String name, String password) { System.out.println("UserServiceImpl.login"); return true; } }
- 切面类(额外功能 + 切入点 + 组装)
/* 1. 原版的额外功能 public class MyAround implements MethodInterceptor { public Object invoke(MethodInvocation invocation) { return invocation.proceed(); } } 2. 切入点 <aop:config <aop:pointcut id="" expression="execution(* login(..))"/> */ @Aspect //加上@Aspect注解代表这个类是切面类 public class MyAspect { //加上@Around注解后,此时的 around方法 相当于 MethodInterceptor接口的invoke方法 //方法名和任意起,这里的参数 ProceedingJoinPoint 对应 原先的参数 MethodInvocation @Around("execution(* login(..))") //对应aop:pointcut标签 public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("----log-----"); Object ret = joinPoint.proceed(); return ret; } }
<bean id="userService" class="com.angenin.aspect.UserServiceImpl"/> <!-- 切面类:额外功能 + 切入点 --> <bean id="around" class="com.angenin.aspect.MyAspect"/> <!-- 告知Spring基于注解进行AOP开发 --> <aop:aspectj-autoproxy/>
- 测试
public class TestAspectProxy { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext4.xml"); UserService userService = (UserService) ctx.getBean("userService"); userService.login("angenin", "123456"); userService.register(new User()); } }
细节
1. 切入点复用
切入点复用:在切面类中定义一个空方法,在其上面加上@Pointcut
注解,通过这种方式定义切入点表达式,后续更加有利于切入点的复用。
@Aspect
public class MyAspect {
//创建空方法,提取出切入点表达式
@Pointcut("execution(* login(..))")
public void myPointcut(){}
@Around(value = "myPointcut()") //引用空方法的切入点函数
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("----log-----");
Object ret = joinPoint.proceed();
return ret;
}
@Around("myPointcut()") //引用空方法的切入点表达式
public Object around2(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("----tx-----");
Object ret = joinPoint.proceed();
return ret;
}
}
2. 动态代理的创建方式
AOP底层实现的2种代理创建方式:
- JDK:通过实现接口,做新的实现类方式,创建代理对象。
- Cglib:通过继承父类,做新的子类,创建代理对象。
默认情况下,AOP底层应用JDK动态代理创建的方式。
切换为Cglib:
- 基于注解AOP开发
只需要在aop:aspectj-autoproxy
标签中加入proxy-target-class="true"
即可。<aop:aspectj-autoproxy proxy-target-class="true"/>
- 传统的AOP开发
在aop:config
标签上加上proxy-target-class="true"
即可,只是加的位置不同而已,属性都是同一个。<aop:config proxy-target-class="true"> ... </aop:config>
AOP开发中的一个坑
坑:在同一个业务类中,进行业务方法间的相互调用,只有最外层的方法,才是加入了额外功能(内部的方法,通过普通的方式调用,都调用的是原始方法)。如果想让内层的方法也调用代理对象的方法,需要实现AppicationContextAware接口,通过其setApplicationContext方法获得工厂对象,进而获得代理对象,调用方法,加入额外功能。
原始类:
public class UserServiceImpl implements UserService, ApplicationContextAware {
private ApplicationContext ctx;
//通过实现 ApplicationContextAware 接口,通过 setApplicationContext方法 获取项目中的IOC工厂对象
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.ctx = applicationContext;
}
@Override
public void register(User user) {
System.out.println("UserServiceImpl.register (业务运算+DAO调用)");
// 模拟同一个业务类不同的业务方法间相互调用的情况
// 此时调用的是原始对象的login,不是代理对象,所以只有核心功能,没有额外功能
// 但是我们在这里调用的目的是要有额外功能+核心功能的
// 由于IOC工厂是重量级资源,一个应用最好只创建一个,所以不能在这里再创建一个工厂
// 实现 ApplicationContextAware 接口,可以获取到项目中的IOC工厂对象
//this.login("angenin", "11");
//通过工厂对象创建代理对象,再调用login方法,实现额外功能+核心功能
UserService userService = (UserService) ctx.getBean("userService");
userService.login("angenin", "11");
}
@Override
public boolean login(String name, String password) {
System.out.println("UserServiceImpl.login");
return true;
}
}
AOP阶段知识总结
学习视频(p63-p107):https://www.bilibili.com/video/BV185411477k?p=63