Spring Aop
1.主要内容
2.代理模式
代理模式在 Java 开发中是一种比较常见的设计模式。设计目的旨在为服务类与客户类之间插入其他功
能,插入的功能对于调用者是透明的,起到伪装控制的作用。如租房的例子:房客、中介、房东。对应
于代理模式中即:客户类、代理类 、委托类(被代理类)。
为某一个对象(委托类)提供一个代理(代理类),用来控制对这个对象的访问。委托类和代理类有
一个共同的父类或父接口。代理类会对请求做预处理、过滤,将请求分配给指定对象。
生活中常见的代理情况:
租房中介、婚庆公司等
代理模式的两个设计原则:
1.代理类 与 委托类 具有相似的行为(共同)
2. 代理类增强委托类的行为
3.静态代理
某个对象提供一个代理,代理角色固定,以控制对这个对象的访问。 代理类和委托类有共同的父类或
父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。代理类负责请求的预处理、过滤、
将请求分派给委托类处理、以及委托类执行完请求后的后续处理。
3.1.代理的三要素
a、有共同的行为(结婚) - 接口
b、目标角色(新人) - 实现行为
c、代理角色(婚庆公司) - 实现行为 增强目标对象行为
3.2.静态代理的特点
1、目标角色固定
2、在应用程序执行前就得到目标角色
3、代理对象会增强目标对象的行为
4、有可能存在多个代理 引起"类爆炸"(缺点)
3.3.静态代理的实现
3.3.1. 定义行为(共同) 定义接口
/**
* 定义行为
*/
public interface Marry {
public void toMarry();
}
3.3.2. 目标对象(实现行为)
/**
* 静态代理 ——> 目标对象
*/
public class You implements Marry {
// 实现行为
@Override
public void toMarry() {
System.out.println("我要结婚了...");
}
}
3.3.3. 代理对象(实现行为、增强目标对象的行为)
package com.xxx.proxy;
/**
* 静态代理->代理角色
* 1.实现行为
* 2,。增强用户行为
*/
public class MarryCompanyProxy implements Marry {
//目标对象
private Marry target;
//通过带参构造器传递目标对象
public MarryCompanyProxy(Marry target) {
this.target = target;
}
//实现行为
@Override
public void toMarry() {
//用户行为增强
before();
//调用目标对象的方法
target.toMarry();
//用户行为增强
after();
}
/**
* 用户行为增强
*/
private void after() {
System.out.println("新婚快乐,百年好合!!");
}
/**
* 用户行为增强
*/
private void before() {
System.out.println("婚礼现场正在布置中!!");
}
}
3.3.4. 通过代理对象实现目标对象的功能
package com.xxx.proxy;
public class StaticProxy {
public static void main(String[] args) {
//目标对象
You you = new You();
//代理对象
MarryCompanyProxy marryCompanyProxy = new MarryCompanyProxy(you);
//通过代理对象调用目标对象的方法
marryCompanyProxy.toMarry();
}
}
4.动态代理
相比于静态代理,动态代理在创建代理对象上更加的灵活,动态代理类的字节码在程序运行时,由
Java反射机制动态产生。它会根据需要,通过反射机制在程序运行期,动态的为目标对象创建代理对
象,无需程序员手动编写它的源代码。动态代理不仅简化了编程工作,而且提高了软件系统的可扩展
性,因为反射机制可以生成任意类型的动态代理类。代理的行为可以代理多个方法,即满足生产需要的
同时又达到代码通用的目的。
动态代理的两种实现方式:
1.JDK 动态代理
2.CGLIB动态代理
4.1.动态代理的特点
1. 目标对象不固定
2. 在应用程序执行时动态创建目标对象
3. 代理对象会增强目标对象的行为
4.2. JDK动态代理
注:JDK动态代理的目标对象必须有接口实现
4.2.1 idea中jdk动态代理文件生成
1.必须在main方法中执行,直接用junt的test方法调用无法生成
2.在main方法最前面增加配置,这样会输出代理class文件
system.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true")
4.2.2 idea中JDK动态代理文件的查看
1.代理class的生成路径是在idea的工作空间下的com\sun\proxy目录中
2.$Proxy0.class文件和源代码不在同一目录下
4.2.3. newProxyInstance
Proxy类:
Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了
如下操作方法:
/*
返回一个指定接口的代理类的实例方法调用分派到指定的调用处理程序。 (返回代理对象)
loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接
口,如果 我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),
这样我就能调用这 组接口中的方法了
h:一个InvocationHandler接口,表示代理实例的调用处理程序实现的接口。每个代理实例都具
有一个 关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的
调用处理程序 的 invoke 方法(传入InvocationHandler接口的子类)
*/
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
4.2.4 获取代理对象
package com.xxx.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Jdk动态代理类
* 每一个代理类都需要实现InvocationHandler接口
*/
public class JdkHandler implements InvocationHandler {
//目标对象
private Object target; //目标对象的类型不固定,创建时动态生成
//通过带参构造器传递目标对象
public JdkHandler(Object target) {
this.target = target;
}
/**
* 1.调用目标对象的方法(返回object)
* 2.增强目标对象的行为
* @param proxy 调用该方法的代理实例
* @param method 目标对象的方法
* @param args 目标对象的方法所需要的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke方法的proxy参数:"+proxy.getClass().getName());
//用户增强行为
System.out.println("方法执行前============");
//调用目标对象中的方法(返回Object)
Object object =method.invoke(target,args);
System.out.println("=========方法执行后");
return object;
}
/**
* 获取代理对象
*
* public static Object newProxyInstance(ClassLoader loader,
* Class[] interfaces,
* InvocationHandler h)
* loader:类加载器
* interfaces:接口数组
* target.getClass().getInterfaces():目标对象的接口数据
* h:InvocationHandler接口,(传入InvocationHandler接口的实现类)
*
* @return
*/
public Object getProxy(){
Object object = Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
System.out.println("getProxy返回的代理对象:"+object.getClass().getName());
return object;
}
}
4.2.4. 通过代理对象实现目标对象的功能
// 目标对象
You you = new You();
// 获取代理对象
JdkHandler jdkHandler = new JdkHandler(you);
Marry marry = (Marry) jdkHandler.getProxy();
// 通过代理对象调用目标对象中的方法
marry.toMarry();
问:Java动态代理类中的invoke是怎么调用的?
答:在生成的动态代理类$Proxy0.class中,构造方法调用了父类Proxy.class的构造方法,给成员变量
invocationHandler赋值,$Proxy0.class的static模块中创建了被代理类的方法,调用相应方法时方法
体中调用了父类中的成员变量InvocationHandler的invoke()方法。
注:JDK的动态代理依靠接口实现,如果有些类并没有接口实现,则不能使用JDK代理。
4.3. CGLIB动态代理
JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能使用JDK的动态代理,CGLIB
是针对类来实现代理的,它的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为
采用的是继承,所以不能对final修饰的类进行代理。
4.3.1. 添加依赖
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
4.3.2. 定义类
package com.xxx.proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibInterceptor implements MethodInterceptor {
//目标对象
private Object target;
//通过构造器传入目标对象
public CglibInterceptor(Object target) {
this.target = target;
}
/**
* 获取代理对象
* @return
*/
public Object getProxy(){
//通过Enhancer对象中的create()方法生成一个类,用于生成代理对象
Enhancer enhancer = new Enhancer();
//设置父类(将目标类作为代理类的父类)
enhancer.setSuperclass(target.getClass());
//设置拦截器,回调对象为本身对象
enhancer.setCallback(this);
//生成代理类对象,并返回给调用者
return enhancer.create();
}
/**拦截器
* 1.目标对象的方法调用
* 2.行为增强
* @param o Cglib动态生成的代理类的实例
* @param method 实体类中所调用的被代理的方法的引用
* @param objects 参数列表
* @param methodProxy 生成的代理类对方法的引用
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//增强行为
System.out.println("==========方法执行前");
//调用目标类的方法
Object object = methodProxy.invoke(target,objects);
System.out.println("方法执行后======");
return null;
}
}
4.3.3. 调用方法
package com.xxx.proxy;
import javax.jws.soap.SOAPBinding;
public class CglibInterceptorTest {
public static void main(String[] args) {
/*通过Cglib动态代理实现;没有接口实现的类*/
//目标对象
User user = new User();
CglibInterceptor cglibInterceptor1 = new CglibInterceptor(user);
User u = (User) cglibInterceptor1.getProxy();
u.test();
/*通过JDK动态代理实现,没有接口实现的类*/
User user = new User();
JdkHandler jdkHandler =new JdkHandler(user);
User u = (User) jdkHandler.getProxy();
u.test(); //代理对象com.sun.proxy.$Proxy0 cannot be cast to com.xxx.proxy.User
}
}
4.4. JDK代理与CGLIB代理的区别
JDK动态代理实现接口,Cglib动态代理继承思想
JDK动态代理(目标对象存在接口时)执行效率高于Ciglib
如果目标对象有接口实现,选择JDK代理,如果没有接口实现选择Cglib代理
5. Spring AOP
5.1. 日志处理带来的问题
例:需要对以下功能进行日志记录,事务管理
假如没有AOP,在做日志和事务处理的时候,我们会在每个方法中添加相关处理
但是大多数代码是相同的,为了实现代码复用,我们可能把日志和事务处理抽离成一个新的方法。但是这样我们仍然必须手动插入这些方法。
但这样两个方法就是强耦合的,假如此时我们不需要这个功能,或者想换成其他功能,那么就必须一个个修改。
通能代理模式,可以在指定位置执行对应流程,这样就可以将一些横向的功能抽离出来形成一个独立模块,然后在指定位置插入这些功能。这样的思想,被称为面向切面编程,即AOP。
例: 我们有一个Pay(接口) 然后两个实现类DollarPay和RmbPay,都需要重写pay()方法, 这时我们需要对
pay方法进行性能监控,日志的添加等等怎么做?
5.1.1. 最容易想到的方法
对每个字符方法均做日志代码的编写处理,如下面方式
缺点: 代码重复太多 添加的日志代码耦合度太高(如果需要更改日志记录代码功能需求,类中方法需
要全部改动,工程量浩大)
5.1.2. 使用装饰器模式/ 代理模式改进解决方案
装饰器模式:动态地给一个对象添加一些额外的职责。
代理模式:以上刚讲过。于是得出以下结构:
仔细考虑过后发现虽然对原有内部代码没有进行改动,对于每个类做日志处理,并引用目标类,但是
如果待添加日志的业务类的数量很多,此时手动为每个业务类实现一个装饰器或创建对应的代理类,同
时代码的耦合度也加大,需求一旦改变,改动的工程量也是可想而知的。
有没有更好的解决方案,只要写一次代码,对想要添加日志记录的地方能够实现代码的复用,达到松耦
合的同时,又能够完美完成功能?
答案是肯定的,存在这样的技术,aop已经对其提供了完美的实现!
5.2 什么是AOP
Aspect Oriented Programing 面向切面编程,相比较 oop 面向对象编程来说,Aop关注的不再是程
序代码中某个类,某些方法,而aop考虑的更多的是一种面到面的切入,即层与层之间的一种切入,所
以称之为切面。联想大家吃的汉堡(中间夹肉)。那么aop是怎么做到拦截整个面的功能呢?考虑前面
学到的servlet filter /* 的配置 ,实际上也是aop 的实现。
5.3. AOP能做什么?
AOP主要应用于日志记录,性能统计,安全控制,事务处理等方面,实现公共功能性的重复使用。
5.4. AOP的特点
-
降低模块与模块之间的耦合度,提高业务代码的聚合度。(高内聚低耦合)
-
提高了代码的复用性。
-
提高系统的扩展性。(高版本兼容低版本)
-
可以在不影响原有的功能基础上添加新的功能
5.4.1. AOP的底层实现
动态代理(JDK + CGLIB)
5.5. AOP基本概念
5.5.1. Joinpoint(连接点)
被拦截到的每个点,spring中指被拦截到的每一个方法,spring aop一个连接点即代表一个方法的执
行。
5.5.2. Pointcut(切入点)
对连接点进行拦截的定义(匹配规则定义 规定拦截哪些方法,对哪些方法进行处理),spring 有专门
的表达式语言定义。
5.5.3. Advice(通知)
拦截到每一个连接点即(每一个方法)后所要做的操作
1. 前置通知 (前置增强)— before() 执行方法前通知
2. 返回通知(返回增强)— afterReturn 方法正常结束返回后的通知
3. 异常抛出通知(异常抛出增强)— afetrThrow()
4. 最终通知 — after 无论方法是否发生异常,均会执行该通知。
5. 环绕通知 — around 包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类
型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的
返回值或抛出异常来结束执行。
5.5.4. Aspect(切面)
切入点与通知的结合,决定了切面的定义,切入点定义了要拦截哪些类的哪些方法,通知则定义了拦
截过方法后要做什么,切面则是横切关注点的抽象,与类相似,类是对物体特征的抽象,切面则是横切
关注点抽象。
5.5.5. Target(目标对象)
被代理的目标对象
5.5.6. Weave(织入)
将切面应用到目标对象并生成代理对象的这个过程即为织入
5.5.7. Introduction(引入)
在不修改原有应用程序代码的情况下,在程序运行期为类动态添加方法或者字段的过程称为引入
6.Spring AOP的实现
6.1. Spring AOP环境搭建
6.1.1.坐标依赖引入
<!--Spring AOP-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
6.1.2.添加spring.xml的配置
添加命名空间
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
6.2.注解实现
6.2.1. 定义切面
package com.xxx.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 切面
* 切入点和通知的抽象
* 定义 切入点 和 通知
* 切入点:定义要拦截哪些类的哪些方法
* 通知:定义拦截之后方法要做什么
*/
@Component //将对象交给IOC容器进行实例化
@Aspect //声明当前类是一个切面
public class LogCut {
/**
* 切入点
* 定义要拦截哪些方类的哪些方法
* 匹配规则,拦截什么方法
*
* 定义切入点
* @pointcut("匹配规则")
*
* 切入点表达式
* 1.执行所哟偶的公共方法
* execution(public *(..))
*
* 2.执行任意的set方法
* execution(* set*(..))
*
* 3.设置指定包下的任意类的任意方法(指定包 com.xxx.service)
* execution(* com.xxx.service.*.*(..))
*
* 4.设置指定包及子包的任意类下的任意方法
* execution(* com.xxx.service..*.*(..))
* 表达式中第一个*
* 代表的是方法的修饰范围(public,private,protected)
* 如果取值是*,则表示所有范围
*/
@Pointcut("execution(* com.xxx.service..*.*(..))")
public void cut(){
}
/**
*声明前置通知,并将通知应用到指定的切入点上
* 目标类的方法执行前执行该通知
*/
@Before(value = "cut()")
public void before(){
System.out.println("前置通知");
}
/**
*声明返回通知,并将通知应用到指定的切入点上
* 目标类的方法无异常执行后执行该通知
*/
@AfterReturning(value = "cut()")
public void afterReturn(){
System.out.println("返回通知");
}
/**
*声明最终通知 并将通知应用到定义的切入点上
* 目标类方法(无异常或有异常)执行后 执行该通知
*/
@After(value = "cut()")
public void after() {
System.out.println("最终通知.....");
}
/**
* 声明异常通知 并将通知应用到定义的切入点上
* 目标类方法出现异常时 执行该通知
*/
@AfterThrowing(value="cut()",throwing = "e")
public void afterThrow(Exception e) {
System.out.println("异常通知....." + " 异常原因:" + e.getCause());
}
/**
* 声明环绕通知,并将通知应用到指定的切入点上
* 目标类的方法执行前后都可通过环绕通知定义相应的处理
* 需要通过显式调用方法,否则无法访问指定方法pjp.proceed();
* @param pjp
* @return
*/
@Around(value = "cut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕通知--前置通知");
Object object =null;
try{
//显式调用对应的方法
object =pjp.proceed();
System.out.println(pjp.getTarget());
System.out.println("环绕通知--返回通知");
}catch (Throwable throwable){
throwable.printStackTrace();
System.out.println("环绕通知--异常通知");
}
System.out.println("环绕通知--最终通知");
return object;
}
}
6.2.2. 配置文件(spring.xml)
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启自动化扫描-->
<context:component-scan base-package="com.xxx"/>
<!--配置Aop代理-->
<aop:aspectj-autoproxy> </aop:aspectj-autoproxy>
</beans>
6.3. XML实现
6.3.1.定义切面
package com.xxx.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 切面
* 切入点和通知的抽象
* 定义 切入点 和 通知
* 切入点:定义要拦截哪些类的哪些方法
* 通知:定义拦截之后方法要做什么
*/
@Component //将对象交给IOC容器进行实例化
public class LogCut02 {
/**
* 切入点
* 定义要拦截哪些方类的哪些方法
* 匹配规则,拦截什么方法
*
* 定义切入点
* @pointcut("匹配规则")
*
* 切入点表达式
* 1.执行所哟偶的公共方法
* execution(public *(..))
*
* 2.执行任意的set方法
* execution(* set*(..))
*
* 3.设置指定包下的任意类的任意方法(指定包 com.xxx.service)
* execution(* com.xxx.service.*.*(..))
*
* 4.设置指定包及子包的任意类下的任意方法
* execution(* com.xxx.service..*.*(..))
* 表达式中第一个*
* 代表的是方法的修饰范围(public,private,protected)
* 如果取值是*,则表示所有范围
*/
public void cut(){
}
/**
*声明前置通知,并将通知应用到指定的切入点上
* 目标类的方法执行前执行该通知
*/
public void before(){
System.out.println("前置通知");
}
/**
*声明返回通知,并将通知应用到指定的切入点上
* 目标类的方法无异常执行后执行该通知
*/
public void afterReturn(){
System.out.println("返回通知");
}
/**
*声明最终通知 并将通知应用到定义的切入点上
* 目标类方法(无异常或有异常)执行后 执行该通知
*/
public void after() {
System.out.println("最终通知.....");
}
/**
* 声明异常通知 并将通知应用到定义的切入点上
* 目标类方法出现异常时 执行该通知
*/
public void afterThrow(Exception e) {
System.out.println("异常通知....." + " 异常原因:" + e.getCause());
}
/**
* 声明环绕通知,并将通知应用到指定的切入点上
* 目标类的方法执行前后都可通过环绕通知定义相应的处理
* 需要通过显式调用方法,否则无法访问指定方法pjp.proceed();
* @param pjp
* @return
*/
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕通知--前置通知");
Object object =null;
try{
//显式调用对应的方法
object =pjp.proceed();
System.out.println(pjp.getTarget());
System.out.println("环绕通知--返回通知");
}catch (Throwable throwable){
throwable.printStackTrace();
System.out.println("环绕通知--异常通知");
}
System.out.println("环绕通知--最终通知");
return object;
}
}
6.3.2.配置文件(spring.xml)
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启自动化扫描-->
<context:component-scan base-package="com.xxx"/>
<!--Aop相关配置-->
<aop:config>
<!--aop切面-->
<aop:aspect ref="logCut02">
<!-- Aop切入点-->
<aop:pointcut id="cut" expression="execution(* com.xxx.service..*.*(..))"/>
<!--配置前置通知,设置前置通知对应的方法名及切入点-->
<aop:before method="before" pointcut-ref="cut"></aop:before>
<!--配置返回通知,设置返回通知对应的方法名及切入点-->
<aop:after-returning method="afterReturn" pointcut-ref="cut"></aop:after-returning>
<!--配置最终通知,设置最终通知对应的方法名及切入点-->
<aop:after method="after" pointcut-ref="cut"></aop:after>
<!--配置异常通知,设置异常通知对应的方法名及切入点-->
<aop:after-throwing method="afterThrow" pointcut-ref="cut" throwing="e"></aop:after-throwing>
<!--配置环绕通知,设置环绕通知对应的方法名及切入点-->
<aop:around method="around" pointcut-ref="cut"></aop:around>
</aop:aspect>
</aop:config>
</beans>
7.Spring AOP总结
7.1. 代理模式实现三要素
-
接口定义
-
目标对象与代理对象必须实现统一接口
-
代理对象持有目标对象的引用 增强目标对象行为
7.2. 代理模式实现分类以及对应区别
-
静态代理:手动为目标对象制作代理对象,即在程序编译阶段完成代理对象的创建
-
动态代理:在程序运行期动态创建目标对象对应代理对象。
-
jdk动态代理:被代理目标对象必须实现某一或某一组接口 实现方式 通过回调创建代理对象。
-
cglib 动态代理:被代理目标对象可以不必实现接口,继承的方式实现。
动态代理相比较静态代理,提高开发效率,可以批量化创建代理,提高代码复用率。
7.3. Aop 理解
-
面向切面,相比oop 关注的是代码中的层 或面
-
解耦,提高系统扩展性
-
提高代码复用
7.4. Aop关键词
-
连接点:每一个方法
-
切入点:匹配的方法集合
-
切面:连接点与切入点的集合决定了切面,横切关注点的抽象
-
通知:几种通知
-
目标对象:被代理对象
-
织入:程序运行期将切面应用到目标对象 并生成代理对象的过程
-
引入:在不修改原始代码情况下,在程序运行期为程序动态引入方法或字段的过程
标签:对象,Spring,代理,目标,通知,Aop,方法,public From: https://www.cnblogs.com/MeltSky/p/17189726.html