【Spring】AOP_aop实现运用了什么设计模式
🔥个人主页: 中草药
🔥专栏:【Java】登神长阶 史诗般的Java成神之路
一、概述
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,它通过预定义的切面(Aspect)对程序进行动态扩展,从而减少代码重复并提升可维护性。AOP的核心思想是把关注点从核心业务逻辑中分离出来,并通过切面(Aspect)将其实现。
举个例子,在一个电商应用中,我们可能会在多个业务逻辑中用到日志记录、安全验证或事务管理等功能。传统的方式是将这些功能硬编码到各个业务模块中,这样会导致代码的重复和混乱。而AOP允许我们将这些功能提取成独立的“切面”,并在需要的地方动态应用,从而保持代码的整洁和可维护性。
简单来说,AOP是一种思想,是对某一类事务的集中处理
二、概念
Spring AOP主要包括以下几个核心概念:
-
切面(Aspect):切面是指横切关注点的模块化,通常包括功能如日志、安全、事务管理等。Spring中,切面通常是通过“@Aspect”注解来声明的。
-
连接点(JoinPoint):程序执行的某个点,通常是方法调用。Spring AOP的连接点通常是方法执行前、执行后等时机。
-
通知(Advice):通知是切面中的实际操作,它定义了在连接点上执行的动作。例如,我们可以在方法执行前后加入日志打印。通知可以分为几种类型:
- 前置通知(Before):在目标方法执行前调用。
- 后置通知(After):在目标方法执行后调用,无论方法是否异常。
- 返回通知(AfterReturning):目标方法成功执行后调用。
- 异常通知(AfterThrowing):当目标方法抛出异常时调用。
- 环绕通知(Around):在方法执行前后都可以进行处理,它可以控制方法是否执行。
-
切点(Pointcut):切点定义了切面应用的具体位置,指定在哪些连接点上应用通知。切点通常是通过表达式来定义的,如
execution(* com.example.service.*.*(..))
表示所有com.example.service
包下的类的所有方法。
-
目标对象(Target Object):被代理的对象,也就是我们要增强功能的核心业务对象。
-
代理(Proxy):代理是通过AOP生成的一个对象,它包含了增强逻辑。Spring支持两种类型的代理:(后面详细介绍)
- JDK动态代理:基于接口的代理,只能对实现了接口的类进行代理。
- CGLIB代理:基于子类的代理,可以对没有接口的类进行代理。
三、使用
引入依赖
org.springframework.boot spring-boot-starter-aop
3.1 基于注解的方式
Spring提供了@Aspect
和@Before
、@After
等注解来实现AOP。
步骤:
- 创建切面类,并使用
@Aspect
注解标注。 - 在切面类中定义通知方法,并使用相应的通知注解(如
@Before
、@After
)。
示例代码:
package org.example.aspect;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;@Aspect@Slf4j@Componentpublic class AspectDemo { //该注解把公共的切点表达式提取出来 @Pointcut(\"execution(* org.example.controller.*.*(..))\") public void pt(){} @Before(\"pt()\") public void doBefore(){ log.info(\"doBefore执行\"); } @After(\"execution(* org.example.controller.*.*(..))\") public void doAfter(){ log.info(\"doAfter执行\"); } @AfterReturning(\"execution(* org.example.controller.*.*(..))\") public void doAfterReturning(){ log.info(\"doAfterReturning执行\"); } @AfterThrowing(\"execution(* org.example.controller.*.*(..))\") public void doAfterThrowing(){ log.info(\"doAfterThrowing执行\"); } @Around(\"execution(* org.example.controller.*.*(..))\") public Object doAround(ProceedingJoinPoint pjp) throws Throwable{ log.info(\"doAround前执行\"); Object result = pjp.proceed(); log.info(\"doAround前执行\"); return result; }}
测试代码
package org.example.controller;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.annotation.Pointcut;import org.example.aspect.TimeRecord;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@Slf4j@RequestMapping(\"/testA\")@RestControllerpublic class TestController { @TimeRecord @RequestMapping(\"/t1\") public String t1(){ //int a = 10/0; log.info(\"执行T1方法\"); return \"t1\"; } @RequestMapping(\"/t2\") public int t2(){ String aa = \"abc\"; log.info(\"执行T2方法\"); return aa.length(); } @RequestMapping(\"/t3\") public int t3(){ int[] a = {1,2,3}; int val = a[2]; log.info(\"执行T3方法\"); return val; }}
运行结果
通过运行结果,我们可得,通知的先后顺序基本上为这个
@order 切面优先级
@Order
注解是用来设置Bean的加载顺序的,值越小,优先级越高。它的值越小,表示加载的优先级越高(数字越小,优先级越高)。通常我们在需要多个相似的组件或者配置类时,使用@Order
来确保它们的执行顺序。
@Aspect@Component@Order(1) // 日志切面,优先级高,先执行public class LogAspect { @Before(\"execution(* com.example.service.*.*(..))\") public void logBefore(JoinPoint joinPoint) { System.out.println(\"Logging before method: \" + joinPoint.getSignature().getName()); }}@Aspect@Component@Order(2) // 事务切面,优先级低,后执行public class TransactionAspect { @Before(\"execution(* com.example.service.*.*(..))\") public void beginTransaction(JoinPoint joinPoint) { System.out.println(\"Starting transaction for method: \" + joinPoint.getSignature().getName()); }}
@ execution表达式
execution( )
举例
@Aspect@Componentpublic class UserServiceAspect { // 匹配所有返回类型为void的方法 @Before(\"execution(void com.example.service.UserService.*(..))\") public void logBefore(JoinPoint joinPoint) { System.out.println(\"Before executing void method in UserService\"); }}@Aspect@Componentpublic class UserServiceAspect { // 匹配参数为User类型的方法 @Before(\"execution(* com.example.service.UserService.addUser(com.example.model.User))\") public void logBefore(JoinPoint joinPoint) { System.out.println(\"Before executing addUser method with User parameter\"); }}
切点表达式举例
//TestController 下的 public修饰, 返回类型为String ⽅法名为t1, ⽆参⽅法execution(public String com.example.demo.controller.TestController.t1())//省略访问修饰符execution(String com.example.demo.controller.TestController.t1())//匹配所有返回类型execution(* com.example.demo.controller.TestController.t1())//匹配TestController 下的所有⽆参⽅法execution(* com.example.demo.controller.TestController.*())//匹配TestController 下的所有⽅法execution(* com.example.demo.controller.TestController.*(..))//匹配controller包下所有的类的所有⽅法execution(* com.example.demo.controller.*.*(..))//匹配所有包下⾯的TestControllerexecution(* com..TestController.*(..))//匹配com.example.demo包下, ⼦孙包下的所有类的所有⽅法execution(* com.example.demo..*(..))
@annotation
@annotation
是Spring AOP中的一种切点表达式,用于匹配目标方法上带有特定注解的方法。通过@annotation
,我们可以根据方法上是否标注了某个注解来决定是否执行切面逻辑。这在实现特定的功能(如权限控制、日志记录等)时非常有用。
注解
package org.example.aspect;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;//生命周期@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD})public @interface TimeRecord {}
切面
package org.example.aspect;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.springframework.stereotype.Component;@Aspect@Slf4j@Componentpublic class TimeAspect { //@Around(\"org.example.aspect.AspectDemo.pt()\") @Around(\"@annotation(org.example.aspect.TimeRecord)\") public Object timeRecord(ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); Object result = joinPoint.proceed(); long endTime = System.currentTimeMillis(); long elapsedTime = endTime - startTime; log.info(joinPoint.getSignature()+\"耗时\"+elapsedTime + \"ms\"); return result; }}
使用
package org.example.controller;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.annotation.Pointcut;import org.example.aspect.TimeRecord;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@Slf4j@RequestMapping(\"/testA\")@RestControllerpublic class TestController { @TimeRecord @RequestMapping(\"/t1\") public String t1(){ //int a = 10/0; log.info(\"执行T1方法\"); return \"t1\"; }}
3.2 基于XML配置的方式
如果你更倾向于XML配置,可以按照以下步骤进行配置:
- 在
applicationContext.xml
中配置切面和通知。 - 配置AOP代理的方式。
示例代码:
四、代理模式
代理模式(Proxy Pattern) 是一种结构型设计模式,主要通过引入代理对象来控制对其他对象的访问。代理模式可以为真实对象提供一个替代对象,以便在不修改原始对象的情况下对其进行控制或增强功能。
为其他对象提供⼀种代理以控制对这个对象的访问. 它的作用就是通过提供⼀个代理类, 让我们在调用目标方法的时候, 不再是直接对目标方法进行调用, 而是通过代理类间接调用.
代理模式的组成
代理模式包含以下几个角色:
- Subject(抽象主题):定义了真实主题和代理主题的公共接口,客户端通过该接口来与真实主题或代理对象进行交互。
- RealSubject(真实主题):实现了
Subject
接口,执行实际的业务逻辑。 - Proxy(代理):也实现了
Subject
接口,持有RealSubject
的引用,并在其上添加额外的功能或控制访问。
静态代理和动态代理都是代理模式的两种实现方式,它们的区别主要体现在代理对象的创建方式上。下面我们分别介绍静态代理和动态代理的特点、实现方式以及优缺点。
1. 静态代理
静态代理是指在编译时就已经确定了代理对象的类型,代理对象的代码由开发人员手动编写或者由工具自动生成。
1.1 特点
- 编译时生成代理类:代理类在编译时就已经确定,代理类是由开发人员编写的。
- 代理类实现接口:代理类通常实现与真实对象相同的接口,并通过代理类访问真实对象。
- 代码冗余:每一个真实对象都需要对应一个代理类,如果有多个真实对象,代理类的数量就会增加,导致代码冗余。
1.2 实现
假设我们有一个RealService
类,需要通过代理类ProxyService
来访问它。
// 真实对象(目标对象)public class RealService implements Service { @Override public void performAction() { System.out.println(\"Performing the real service operation...\"); }}// 代理对象public class ProxyService implements Service { private RealService realService; public ProxyService() { realService = new RealService(); } @Override public void performAction() { System.out.println(\"Proxy: Before calling real service...\"); realService.performAction(); // 调用真实对象的方法 System.out.println(\"Proxy: After calling real service...\"); }}
1.3 使用
在使用静态代理时,客户端代码会通过ProxyService
来访问RealService
:
public class Client { public static void main(String[] args) { Service service = new ProxyService(); // 使用代理对象 service.performAction(); // 调用代理对象的方法 }}
1.4 优缺点
- 优点:
- 简单,易于理解。
- 可以为目标对象增加附加功能,如日志记录、权限控制等。
- 缺点:
- 代理类需要手动编写,代码冗余较大,无法动态改变代理类。
- 每个目标对象都需要一个代理类,导致类的数量增加,难以维护。
在程序运行前, 代理类的 .class文件就已经存在了. (在出租房子之前, 中介已经做好了相关的 工作,就等租户来租房子了)
2. 动态代理
动态代理是指在运行时,通过反射机制动态生成代理类,不需要在编译时预先定义代理类。Java提供了两种常用的动态代理方式:JDK动态代理和CGLIB代理。
2.1 JDK动态代理
JDK动态代理是通过java.lang.reflect.Proxy
类和InvocationHandler
接口实现的。JDK动态代理要求目标类实现一个接口,代理对象通过反射机制动态生成,并且通过InvocationHandler
来调用目标对象的方法。
示例:JDK动态代理实现
//注意是同一个包import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;// 目标接口public interface Service { void performAction();}// 真实对象(目标对象)public class RealService implements Service { @Override public void performAction() { System.out.println(\"Performing the real service operation...\"); }}// 动态代理处理器class ProxyHandler implements InvocationHandler { private Object target; public ProxyHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(\"Proxy: Before calling real service...\"); Object result = method.invoke(target, args); // 调用真实对象的方法 System.out.println(\"Proxy: After calling real service...\"); return result; }}// 客户端代码public class Client { public static void main(String[] args) { Service realService = new RealService(); Service proxyService = (Service) Proxy.newProxyInstance( realService.getClass().getClassLoader(), realService.getClass().getInterfaces(), new ProxyHandler(realService) ); proxyService.performAction(); // 调用代理对象的方法 }}
2.2 CGLIB动态代理
CGLIB(Code Generation Library)是一个功能强大的字节码生成库,通过继承目标类来创建代理对象,而不是要求目标类实现接口。CGLIB生成的代理类是目标类的子类,可以覆盖目标类的方法。
示例:CGLIB动态代理实现
import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;public class RealService { public void performAction() { System.out.println(\"Performing the real service operation...\"); }}// CGLIB动态代理class ProxyHandler implements MethodInterceptor { private Object target; public ProxyHandler(Object target) { this.target = target; } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println(\"Proxy: Before calling real service...\"); Object result = proxy.invokeSuper(obj, args); // 调用目标对象的方法 System.out.println(\"Proxy: After calling real service...\"); return result; }}// 客户端代码public class Client { public static void main(String[] args) { RealService realService = new RealService(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(RealService.class); // 设置目标类 enhancer.setCallback(new ProxyHandler(realService)); // 设置回调 RealService proxyService = (RealService) enhancer.create(); // 创建代理对象 proxyService.performAction(); // 调用代理对象的方法 }}
2.3 动态代理的优缺点
- 优点:
- 灵活性高,不需要手动编写代理类,代理类是在运行时动态生成的。
- 可以针对不同的对象动态创建代理,避免了静态代理中代理类冗余的问题。
- 缺点:
- JDK动态代理要求目标对象实现接口,不能对没有接口的类进行代理。
- CGLIB代理通过继承目标类生成代理类,可能会带来一些性能开销,且不能对
final
类进行代理。 - 代理类的生成过程涉及反射,可能会导致一定的性能开销。
3. 静态代理 vs 动态代理
4. 总结
- 静态代理:代理类在编译时就已经确定,适用于目标类较少的简单场景,存在代码冗余的问题。
- 动态代理:代理类在运行时动态生成,适用于复杂或变化较大的场景,可以大大减少代码冗余。JDK动态代理需要目标类实现接口,而CGLIB动态代理通过继承目标类生成代理对象。
Spring AOP是Spring框架中的一个强大特性,它为开发者提供了一种优雅的方式来处理横切关注点。通过Spring AOP,可以轻松地实现日志记录、事务管理、安全控制等功能,并且能够保持业务逻辑代码的简洁性和清晰度。无论是使用注解还是XML配置,Spring AOP都能让你的代码更加模块化、可维护和易于扩展。
“无谓的争斗让我们浪费了太多的时间与精力,专注于自己能做好的事,才是最明智的选择。” — 乔布斯
🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀
以上,就是本期的全部内容啦,若有错误疏忽希望各位大佬及时指出💐
制作不易,希望能对各位提供微小的帮助,可否留下你免费的赞呢🌸