Spring-AOP编程
一、静态代理设计模式
为什么需要代理设计模式?
- 在JavaEE分层开发中,哪个层次对于我们来讲最重要
DAO -> Service -> Controller
JavaEE开发中,最重要的是Service层 - 一个Service层中包含了哪些代码?
Service层=核心功能(几十 上百代码)+ 额外功能(附加功能)
- 额外功能书写在Service层中好不好?
Service层的调用者角度(Controller):需要在Service层书写额外功能
软件设计者的角度:Service层不需要额外功能 - 现实生活中的解决方式
代理设计模式
-
概念
通过代理类,为原始类(目标类=Service层)增加额外的功能
好处:利于原始类(目标)的维护 -
名词解释
- 目标类 原始类
指的是 业务类 (核心功能 ---> 业务运算 DAO调用) - 目标方法 原始方法
目标类(原始类)中的方法 就是目标方法(原始方法) - 额外功能(附加功能)
日志,事务,性能
- 目标类 原始类
-
代理开发的核心要素
代理类 = 目标类(原始类) + 额外功能 + 原始类(目标类)实现相同的接口房东 --> public interface UserService{ m1 m2 } UserServiceImpl implements UserService{ m1 --> 业务运算 DAO调用 m2 } UserServiceProxy implements UserService{ m1 m2 }
-
编码
静态代理:为每一个原始类,手工编写一个代理类(.java .class)public class UserServiceProxy implements UserService{ private UserServiceImpl userService; @Override public void register(User user) { System.out.println("------register--log------"); userService.register(user); } @Override public boolean login(String name, String password) { System.out.println("-------login--log--------"); return userService.login(name,password); } }
-
静态代理存在的问题
- 静态类文件数量过多,不利于项目管理
UserServiceImpl UserServiceProxy
OrderServiceImpl OrderServiceProxy - 额外功能维护性差
代理类中 额外功能修改复杂(麻烦)
- 静态类文件数量过多,不利于项目管理
二、Spring的动态代理开发
Spring动态代理的概念
概念:通过代理类为原始类(目标类)增加额外功能
好处:利于原始类(目标类)的维护
搭建开发环境
<!--开启Spring动态代理-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.22</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.2</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
Spring动态代理的开发步骤
-
创建原始对象(目标对象)
public class UserServiceImpl implements UserService{ @Override public void register(User user) { System.out.println("UserServiceImpl.register"); } @Override public boolean login(String name, String password) { System.out.println("UserServiceImpl.login"); return true; } }
<bean id="userService" class="xxx.UserServiceImpl"/>
- 额外功能
MethodBeforeAdvice 接口
把额外的功能写在接口的实现中,运行在原始方法之前public class Before implements MethodBeforeAdvice { /** * 作用 :需要把运行在原始方法之前的额外功能 ,写在before方法中 * @param method * @param args * @param target * @throws Throwable */ @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("------------MethodBeforeAdvice log-----------"); } }
<bean id="before" class="com.mysite.dynamic.Before"/>
- 定义切入点
切入点: 额外功能加入的位置
目的:由程序员根据自己的需要,决定额外功能加入给哪个原始方法
简单的测试:在Spring配置文件中,将所有的方法都作为切入点,都加入额外的功能<aop:config> <aop:pointcut id="pc" expression="execution(* * (..))"/> </aop:config>
- 组装(2 3 整合)
<!--表达的含义:在Spring配置文件中,将所有的方法 都加入 before 的额外功能--> <aop:advisor advice-ref="before" pointcut-ref="pc"/>
- 调用
目的:获得Spring工厂创建的动态代理对象,并进行调用ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
注意:
- Spring工厂通过原始对象的 id值获得的是代理对象
- 获得代理对象后,可以通过声明接口类型,进行对象的存储
与静态代理一样,原始类和代理类都实现相同的接口UserService userService = (UserService)ctx.getBean("userService"); userService.login(""); userService.register(""); ```
</ol></li>
</ol>
<h3>动态代理细节分析</h3>
<ol>
<li>Spring创建的动态代理类在哪里?
Spring框架在运行时,通过<strong>动态字节码技术</strong>,在JVM中创建的,运行在JVM内部,等程序结束后,会和JVM一起消失。</li>
</ol>
<p>动态字节码技术是通过第三方动态字节码框架,在JVM中创建对应类的字节码,进而创建对象,当虚拟机结束,动态字节码跟着消失</p>
<p>结论:动态代理不需要定义类文件,都是JVM运行过程中动态创建的,所以不会造成静态代理,类文件数量过多,影响项目管理的问题。</p>
<p><img src="https://zhouhuilong.cn/wp-content/uploads/2025/03/b2dd4860-410d-4930-b8f6-5ea2cd5538d0.png" alt="" /></p>
<ol start="2">
<li>动态代理编程简化代理的开发
<strong>在额外功能不改变的前提下</strong>,创建其他目标类(原始类)的代理对象时,只需要指定原始(目标)对象即可。</li>
<li>动态代理额外功能的维护性大大增强
修改额外功能,只需要在定义一个额外功能的类创建新的动态即可,在Spring配置文件中,对切入点和额外功能进行整合。</li>
</ol>
<h2>三、Spring动态代理详解-额外功能</h2>
<ul>
<li>MethodBeforeAdvice 分析
MethodBeforeAdvice接口作用:额外功能运行在原始方法执行之前,进行额外功能操作
```java
public class Before1 implements MethodBeforeAdvice {
/**- 作用:需要把运行在原始方法之前的额外功能 ,写在before方法中
- @param method 额外功能所增加给的那个原始方法
- @param args 额外功能所增加给的那个原始方法的参数
- @param target 额外功能所增加给的那个原始对象 UserServiceImpl OrderServiceImpl
- @throws Throwable
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("");
System.out.println("------------New MethodBeforeAdvice log-----------");
System.out.println("");
}
}
```
实际上before方法中的参数,在实战中几乎不会用</li>
</ul></li>
<li>MethodInterceptor(方法拦截器)
MethodBeforeAdvice ----> 原始方法执行之前
MethodInterceptor 提供的额外功能 可以运行在 原始方法之 前 后 前后
```java
public class Around implements MethodInterceptor {
/
invoke 方法的作用:额外功能书写在invoke
额外功能 原始方法之前
原始方法之后
原始方法执行之前 之后
参数:MethodInvocation (Method):额外功能所增加给的那个原始方法
login
register
invocation.proceed() ---> login 运行
register 运行
返回值:Object:原始方法的返回值
/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
//额外功能运行在原始方法执行之前
System.out.println("----------Before Log----------");
Object ret = invocation.proceed();
//额外功能运行在原始方法执行之后
System.out.println("----------After Log----------");
return ret;
}
}
```
实际上 事务作为额外功能 需要在原始方法运行之前之后 运行
<strong>MethodInterceptor 提供的额外功能 可以运行在原始方法抛出异常的时候</strong>
```java
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object ret = null;
try {
ret = invocation.proceed();
} catch (Throwable e) {
System.out.println("-----原始方法抛出异常,执行的额外功能-----");
throw new RuntimeException(e);
}
return ret;
}
```
MethodInterceptor 可以影响原始方法的返回值
原始方法的返回值,直接作为invoke方法的返回值返回,不会影响原始方法的返回值
MethodInterceptor 影响原始方法的返回值,不返回原始方法的运行结果即可(invocation.proceed();)
```
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("-------log-------");
Object ret = invocation.proceed();
return true;
}
``` - (..) --> 匹配所有方法
第一个 --> 修饰符 返回值
第二个 * --> 方法名
() --> 参数表
.. --> 对于参数没有要求(参数有没有,有几个什么类型的都行)
``` - login(..)
定义register方法作为切入点 - register(..)
``` - login(String,String)
-- 注意:非java.lang包中的类型,必须要写全限定名 - register(com.mysite.proxy.User)
.. 可以和具体的参数类型连用 - login(String, ..) --> login(String),login(String,String),login(String,com.mysite.proxy.User)
```
</ul>
<h2>四、Spring动态代理详解-切入点表达式</h2>
<h3>切入点详解</h3>
<p>切入点决定额外功能加入位置(方法)
```xml
<aop:pointcut id="pc" expression="execution( (..))"/>
```
`execution(<em> </em>(..))` ----> 匹配了所有方法</p>
<ol>
<li>execution() 切入点函数</li>
<li>`<em> </em>(..)` 切入点表达式</li>
</ol>
<h4>切入点表达式</h4>
<ol>
<li>
<p>方法切入点表达式
<img src="https://zhouhuilong.cn/wp-content/uploads/2025/03/1280X1280-17.png" alt="" />
```</ul>
<ul>
<li>
<p>定义login方法作为切入点
```</ul>
</li>
<li>
<p>定义login 方法且login 方法有两个字符串类型的参数 作为 切入点
```</ul>
</li>
<li>
<p>以上的切入点表达式还不够精准
精准切入点限定
<img src="https://zhouhuilong.cn/wp-content/uploads/2025/03/1280X1280-18.png" alt="" /></p>
</li>
</ul>
</li>
<li>
<p>类切入点表达式
指定特定类作为切入点(额外功能加入的位置),自然这个类中的所有方法,都会加上对应的额外功能</p>
<ul>
<li>语法1
<pre><code>
# 类中的所有方法加入了额外功能</code></pre></li>
<li>com.mysite.proxy.UserServiceImpl.*(..)
<pre><code></code></pre></li>
<li>语法2
```忽略包
- 类只存在一级包 com.UserServiceImpl
- .UserServiceImpl.(..)
- 类存在多级包 com.mysite.proxy.UserServiceImpl
- ..UserServiceImpl.(..)
``` -
包切入点表达式 实战中常用
- 语法1
# 切入点包中的所有类,必须在proxy包中,不能在其子包中 com.mysite.proxy.*.*(..)
- 语法2
# 切入点当前包及其子包都生效 com.mysite.proxy..*.*(..)
- 语法1
切入点函数
切入点函数:用于执行切入点表达式
- execution
最重要的切入点函数,功能最全。
执行 方法切入点表达式 类切入点表达式 包切入点表达式
弊端: execution执行切入点表达式,书写麻烦
execution(* com.mysite.proxy..*.*(..))
注意:其他的切入点函数 简化是execution书写复杂度,功能上完全一致 - args
作用:主要用于函数(方法)参数的匹配切入点:方法参数必须得是2个字符串类型的参数 execution(* *(String,String)) args(String,String)
- within
作用:主要用于进行类、包切入点表达式的匹配# 切入点:UserServiceImpl这个类 execution(* *..UserServiceImpl.*(..)) within(*..UserServiceImpl) execution(* com.mysite..*.*(..)) within(com.mysite..*)
- @annotation
作用:为具有特殊功能的方法加入额外功能<aop:pointcut id="pc" expression="@annotation(com.mysite.Log)"/>
- 切入点函数的逻辑运算
指的是 整合多个切入点函数一起配合工作,进而完成更为复杂的需求 - and与 操作
案例:login 同时 参数 2个字符串 方式1. execution(* login(String,String)) 方式2. execution(* login(..)) and args(String,String) 注意:与操作 不能用于同种类型的切⼊点函数 案例:register⽅法 和 login⽅法作为切⼊点 execution(* login(..)) or execution(* register(..))
- or或 操作
案例:register⽅法 和 login⽅法作为切⼊点 execution(* login(..)) or execution(* register(..))
- 切入点函数的逻辑运算
五、AOP编程
AOP概念
AOP(Aspect Oriented Programing) 面向切面编程 = Spring动态代理开发
以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建
切面 = 切入点 + 额外功能
OOP (Object Oritened Programing) 面向对象编程 Java
以对象为基本单位的程序开发,通过对象间的彼此协同,相互调用,完成程序的构建
POP (Producer Oriented Programing) 面向过程(方法、函数)编程 C
以过程为基本单位的程序开发,通过过程间的彼此协同,相互调用,完成程序的构建
AOP的本质就是Spring的动态代理开发,通过代理类为原始类增加额外功能。
好处:利于原始类的维护
注意:AOP编程不可能取代OOP,是对OOP编程的补充
AOP的开发步骤
与Spring动态代理的开发步骤完全一样
- 原始对象
- 额外功能(MethodInterceptor)
- 切入点
- 组装切面(额外功能+切入点)
切面的名词解释
切面 = 切入点 + 额外功能
六、AOP的底层实现原理
核心问题
- AOP如何创建动态代理类(动态字节码技术)
- Spring工厂如何加工创建代理对象
为什么通过原始对象的id 值,获得的是代理对象
动态代理类的创建
JDK的动态代理
-
Proxy.newProxyInstance();方法参数详解
-
编码
public class TestJDKProxy { public static void main(String[] args) { //1.创建原始对象 UserService userService = new UserServiceImpl(); //2. JDK创建动态代理 InvocationHandler invocationHandler = new InvocationHandler() { //这里实现的invoke方法 // 方法名很像之前实现的 MethodInterceptor接口 // 参数列表和名字很像之前实现的 MethodBefore接口 //参数: // proxy:忽略掉,代表的是 代理对象 // method:额外功能 所增加给的那个原始方法 // args[]:原始方法的参数 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //额外功能 System.out.println("-------JDKProxy Before Log-------"); Object ret = method.invoke(userService, args); //原始方法运行 System.out.println("-------JDKProxy After Log--------"); return ret; } }; //newProxyInstance(classloader,interfaces,invocationHandler); // classloader:借用一个类加载器,创建代理类的Class对象,进而可以创建代理对象 // interfaces: 原始对象 所实现的接口 // userService.getClass().getInterfaces() ==> 代理创建的第三要素:代理对象和原始对象实现相同的接口 // invocationHandler: 创建匿名内部类,实现 invoke 方法,返回 invoke的值 // classloader:通过类加载器把类的字节码文件加载到JVM, // 通过类加载器创建类的Class对象,进而创建这个类的对象 ClassLoader classLoader = UserService.class.getClassLoader(); // 类加载器的作用 ClassLoader // 1. 通过类加载器把对应类的字节码文件加载JVM // 2. 通过类加载器创建类的Class对象,进而创建这个类的对象 // 如何获得类加载器:每一个类的.class文件 自动分配与之对应的ClassLoader /* 在当前动态代理的创建中,需要ClassLoader创建动态代理类的Class对象, 可是动态代理类没有对应的.class 文件,JVM也就不会为其分配ClassLoader, 那怎么办? 我们可以借用一个ClassLoader 借谁的都行 */ UserService userServiceProxy = (UserService) Proxy.newProxyInstance(classLoader, userService.getClass().getInterfaces(), invocationHandler); //测试 userServiceProxy.register(new User()); userServiceProxy.login("zhl","123"); } }
CGlib的动态代理
-
CGlib创建动态代理的原理:父子继承关系创建代理对象,原始类作为父类,代理类作为子类,这样既可以保证2者方法一致,同时在代理类中提供新的实现(额外功能+原始方法)
-
编码
public class TestCglib { public static void main(String[] args) { //1.创建原始对象 UserService userService = new UserService(); //2.通过CGlib的方式创建动态代理对象 /* Proxy.newProxyInstance(classloader,interfaces,invocationHandler); Enhancer.setClassLoader() Enhancer.setSuperClass() Enhancer.setCallback() ----> MethodInterceptor(cglib) 在 cglib包下的 Enhancer.create() ---> 代理 */ Enhancer enhancer = new Enhancer(); enhancer.setClassLoader(TestCglib.class.getClassLoader()); enhancer.setSuperclass(userService.getClass()); MethodInterceptor interceptor= new MethodInterceptor() { //等同于 在JDK中创建动态代理类 InvocationHandler 中的 invoke方法 @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("---------Cglib Before Log----------"); //调用原始方法 Object ret = method.invoke(userService, args); System.out.println("---------Cglib After Log----------"); return ret; } }; //MethodInterceptor extends Callback enhancer.setCallback(interceptor); UserService userServiceProxy = (UserService) enhancer.create(); //测试 userServiceProxy.login("zhl","123"); userServiceProxy.register(new User()); } }
-
总结
- JDK动态代理
Proxy.newProxyInstance()
通过接口创建代理的实现类 - Cglib动态代理
Enhancer
通过继承父类创建的代理类
- JDK动态代理
Spring工厂如何加工原始对象
-
思路分析
public class ProxyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } /* Proxy.newProxyInstance(classloader,interfaces,invocationHandler) */ @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { InvocationHandler invocationHandler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("----------new Before Log---------"); Object ret = method.invoke(bean, args); System.out.println("----------new after Log---------"); return ret; } }; return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(),bean.getClass().getInterfaces(),invocationHandler); } }
底层是通过实现BeanPostProcessor接口完成的,实现接口后,方法返回的是代理对象
七、基于注解的AOP编程
基于注解的AOP编程的开发步骤
- 原始对象
- 额外功能
- 切入点
- 组装切面
通过切面类 定义了 额外功能 @Around
定义了 切入点 @Around("execution(* login(..))")
定义了 切面类 @Aspect
/*
1.额外功能
public class MyAround implements MethodInterceptor{
public Object invoke(MethodInvocation invocation){
Object ret = invocation.proceed();
return ret;
}
}
2.切入点
<aop:config>
<aop:pointcut id="" expression="execution(* login(..))"/>
*/
@Aspect
public class MyAspect {
@Around("execution(* login(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("-----------aspect Before Log---------");
Object ret = joinPoint.proceed();
System.out.println("-----------aspect After Log---------");
return ret;
}
}
<bean id="userService" class="com.mysite.aspect.UserServiceImpl"/>
<!--
额外功能
切入点
组装切面
-->
<bean id="aspect" class="com.mysite.aspect.MyAspect"/>
<!--告诉Spring现在基于注解进行aop编程-->
<aop:aspectj-autoproxy/>
细节
-
切入点复用:在切面类中定义一个函数,添加@Pointcut注解 通过这种方式,定义切入点表达式,后续更加利于切入点复用
@Aspect public class MyAspect { /** * 实现切入点表达式的复用,利于维护和复用 */ @Pointcut("execution(* login(..))") public void myPointCut(){} @Around(value = "myPointCut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("-----------aspect Log---------"); Object ret = joinPoint.proceed(); return ret; } @Around(value = "myPointCut()") public Object around1(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("-----------aspect tx---------"); Object ret = joinPoint.proceed(); return ret; } }
- 动态代理的创建方式
AOP底层实现 2种代理创建方式- JDK 通过实现接口 做新的实现类方式 创建代理对象
- Cglib 通过继承父类 做新的子类 创建代理对象
默认情况AOP编程 底层应用JDK动态代理创建方式
如果切换 Cglib
- 基于注解的AOP开发
<aop:aspectj-autoproxy proxy-target-class="true" />
- 传统的AOP开发
<aop:config proxy-target-class="true" />
八、AOP开发的易错点
当业务比较复杂时,我们可能会在同一个业务操作中,调用另一个业务操作,这时我们只有在外层被调用的方法,才会调用额外功能,内层方法实际上的调用都是对原始方法的调用,没有对额外功能的调用。
我们可以创建一个Spring工厂,调用代理对象。但是Spring是一个重量级资源,一个应用中,应该只创建一个工厂。
所以可以在业务类中声明 ApplicationContext的Spring工厂作为成员变量,然后实现ApplicationContextAware接口,实现 setApplicationContext方法,为成员变量工厂赋值就拿到了工厂对象,然后获取代理对象即可。
public class UserServiceImpl implements UserService, ApplicationContextAware {
//将工厂作为成员变量,方便调用代理对象
private ApplicationContext ctx;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//为成员变量工厂赋值 ,方便获取代理对象
ctx = applicationContext;
}
@Log
@Override
public void register(User user) {
System.out.println("UserServiceImpl.register");
/*
实际上
调用的是 代理对象的register方法(额外功能+核心功能) 和 原始对象的login 方法 ----》核心功能
设计目的: 调用代理对象的 login方法 ---》额外功能 + 代理功能
*/
//this是调用 原始对象的 login方法
//this.login("zhl","222");
//通过工厂获取 代理对象
UserService userService = (UserService) ctx.getBean("userService");
userService.login("zhl","1111");
}
@Override
public boolean login(String name, String password) {
System.out.println("UserServiceImpl.login");
return true;
}
}