🔥个人主页: 中草药
🔥专栏:【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代理:基于子类的代理,可以对没有接口的类进行代理。
三、使用
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
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
@Component
public 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")
@RestController
public 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
@Component
public 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
@Component
public 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.*.*(..))
//匹配所有包下⾯的TestController
execution(* 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
@Component
public 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")
@RestController
public class TestController {
@TimeRecord
@RequestMapping("/t1")
public String t1(){
//int a = 10/0;
log.info("执行T1方法");
return "t1";
}
}
3.2 基于XML配置的方式
如果你更倾向于XML配置,可以按照以下步骤进行配置:
- 在
applicationContext.xml
中配置切面和通知。 - 配置AOP代理的方式。
示例代码:
<bean id="logAspect" class="com.example.aop.LogAspect" />
<aop:config>
<aop:aspect ref="logAspect">
<aop:before method="logBefore" pointcut="execution(* com.example.service.*.*(..))" />
<aop:after method="logAfter" pointcut="execution(* com.example.service.*.*(..))" />
</aop:aspect>
</aop:config>
四、代理模式
代理模式(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 动态代理
特点静态代理动态代理代理类生成方式代理类在编译时就已经确定代理类在运行时动态生成代理对象的灵活性每个真实对象需要一个代理类可以为多个不同的真实对象动态生成代理类是否要求目标类实现接口需要(JDK动态代理)JDK动态代理需要目标类实现接口,CGLIB不需要代码量代理类代码冗余,手动编写通过反射或字节码生成,无需手动编写代理类性能开销较低(没有反射开销)反射和字节码生成带来一定的性能开销
4. 总结
静态代理:代理类在编译时就已经确定,适用于目标类较少的简单场景,存在代码冗余的问题。
动态代理:代理类在运行时动态生成,适用于复杂或变化较大的场景,可以大大减少代码冗余。JDK动态代理需要目标类实现接口,而CGLIB动态代理通过继承目标类生成代理对象。
Spring AOP是Spring框架中的一个强大特性,它为开发者提供了一种优雅的方式来处理横切关注点。通过Spring AOP,可以轻松地实现日志记录、事务管理、安全控制等功能,并且能够保持业务逻辑代码的简洁性和清晰度。无论是使用注解还是XML配置,Spring AOP都能让你的代码更加模块化、可维护和易于扩展。
“无谓的争斗让我们浪费了太多的时间与精力,专注于自己能做好的事,才是最明智的选择。” — 乔布斯
🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀
以上,就是本期的全部内容啦,若有错误疏忽希望各位大佬及时指出💐
制作不易,希望能对各位提供微小的帮助,可否留下你免费的赞呢🌸
版权归原作者 中草药z 所有, 如有侵权,请联系我们删除。