Spring AOP

 1.AOP

        AOP即面向切面编程,那么对于为什么要面向切面编程(AOP),SpringAOP又是什么意思?接下来我将详细解释:

        首先我们先来说为什么要面向切面编程,我们想象你在项目中遇到的这样一种情况:我们需要知道调用这个接口需要花费的代价(时间),那么在这种情况下我们需要对于每个接口都去打印耗费时间的日志,那么我们需要再每个接口中都去重复这些代码吗?显然这样做工作量太大了,面向切面编程就很好的解决了这一问题,这种编程方式是对面向对象编程的补充和增强,它会把程序中多个模块都会用到的横切关注点统一的提取出来,进行模块化的实现。

        那么典型的横切关注点,比如:日志,安全检查,权限控制,事务处理等。

     AOP是一种思想,它的实现方式有很多,有SpringAOP,AspectJ,CGLIB等等,  SpringAOP是其中的一种实现方式。 

2.AOP的核心概念

我们首先罗列一下核心概念,之后在进行一个详细的解释。

概念解释
切面(Aspect)横切关注点的模块化,比如日志、事务。是 AOP 的核心。
连接点(JoinPoint)程序中可以插入切面的地方,如方法调用、异常抛出等。
通知(Advice)实际插入到连接点的代码,如前置、后置、环绕等方法。
切点(Pointcut)定义通知应用在哪些连接点的规则(用表达式来描述)。
织入(Weaving)将切面应用到目标对象上,并创建代理对象的过程。
目标对象(Target)被 AOP 增强的业务对象。

在学习AOP的时候我们首先应该先去把它用起来,用起来之后在回过头去慢慢理解。 AOP在Spring框架中的实现方式主要有两种配置方式:注解方式和XML配置方式,我们只介绍注解的实现方式。我们为大家罗列一些常用的注解,方便查找。

注解说明
@Aspect声明一个类为切面
@Before前置通知,方法执行前运行
@After后置通知,方法执行后运行(无论是否异常)
@AfterReturning返回通知,方法成功返回后执行
@AfterThrowing异常通知,方法抛出异常时执行
@Around环绕通知,方法执行前后都可以执行逻辑(最强大)
@Pointcut定义切点表达式

3.AOP的使用

在使用之前我们应该先去pom.xml文件中去导入我们的依赖,不要忘记去刷新我们的maven。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

我们来看一个例子,是用来记录接口的调用时间

@Slf4j
@Component
@Aspect
public class TimeRecordAspect {

    @Around("execution(* com.project.librarymanagementsystem.controller.*.*(..))")
    public Object record(ProceedingJoinPoint pj) throws Throwable {

        long start = System.currentTimeMillis();
        Object result = pj.proceed();
        log.info(pj.getSignature()+"cost time:"+(System.currentTimeMillis()-start)+"ms");
        log.info(pj.getSignature().toString());         //获取签名(方法的声明)
        log.info(pj.toLongString());                    //类名、方法名、参数全路径
        log.info(pj.toShortString());                   //简略格式的描述
        log.info(String.valueOf(pj.getArgs()));     //获取参数

        return result;
    }
}

首先,我们要在类之间添加注解@Aspect,声明这个类是一个切面,同时添加注解@Component交给spring来管理,添加注解@Slf4j是用来方便打印日志。

@Around是环绕通知注解,表示下面的方法将在目标方法执行前后都执行,可以控制目标方法的执行过程。注解里面的内容是切点表达式。

3.1 切点表达式

切点表达式用于定位连接点,即我们要在哪些方法上应用AOP逻辑

基本语法:

execution([修饰符] 返回类型 [包名.]类名.方法名(参数)) 其中:[]表示可以省略

有关于切点表达式的每一部分含义:

部分说明

execution

指定是方法执行这个连接点类型(Spring AOP 只支持这种)

*

通配符,匹配任意类型或任意方法名

修饰符

方法的访问权限,如 public、private,通常省略

返回类型

可以写具体类型,如 voidString,也可以写 * 表示任意返回类型

包名

包路径,可以使用 .. 表示多级包

类名

类名,可以使用 * 表示任意类

方法名

方法名,可以使用 * 表示任意方法

参数列表

精确类型、通配符 *(一个参数)、..(任意个参数,含0个)都可以使用

上述代码里面的通配符execution(* com.project.librarymanagementsystem.controller.*.*(..))

这里面就省略了修饰符,最前面的*是表示任意的返回值,并且其表示的含义是,拦截com.project.librarymanagementsystem.controller.*这个包下面的所有类,因为*是通配符可以用来匹配任意类型,所以可以匹配这个包下面的所有类,下一个*的含义也是如此,匹配所有的方法名,最后*(..)表示的是匹配带有任意参数的方法。

我们为大家举一些例子:

  1. @Around("execution(* com.example.service.UserService.*(..))")
  2. @Around("execution(String com.example..*.*(..))")
  3. @Around("execution(* com.example.service.*.get*(..))")
  4. @Around("execution(* com.example.service.UserService.updateUser(String, int))")
  5. @Around("execution(public * *(..))")

答案:

  1. 拦截 UserService 类中所有方法,不管返回什么、不管参数是什么。
  2. 拦截 com.example 及其子包中,所有返回类型是 String 的方法。
  3. 匹配 com.example.service 包下所有类中,所有以 get 开头的方法。
  4. 拦截 UserService 中参数为 (String, int) 的方法。
  5. 匹配所有 public 的方法,不管类名和包名。


3.2 连接点

上面代码中的参数ProceedingJoinPoint就代表着连接点,它是AOP提供的一个接口,是 JoinPoint 的子接口,专门用于 @Around 环绕通知。它代表连接点(Join Point),即程序中被 AOP 拦截的方法调用。

简单来说,JoinPoint表示的是方法执行前,执行中,执行后的连接点;ProceedingJoinPoint是环绕通知中的JoinPoint,多了一个功能-可以执行目标方法
那么pj又是什么?pj 是 ProceedingJoinPoint 类型的参数,是 Spring AOP 自动传进来的,它表示你拦截到的那个方法的信息和控制器,你可以把它当作: 一个“遥控器 + 方法描述器”

举个例子来解释:
假设你写了一个控制器方法:
@GetMapping("/hello")
public String hello(String name) {
     return "Hello, " + name;
}
现在你定义了这个 AOP 切面来拦截:
@Around("execution(* com.project.librarymanagementsystem.controller.*.*(..))")
 public Object record(ProceedingJoinPoint pj) throws Throwable {
 ...
}
那么当你访问 /hello?name=Tom 时:
Spring 会调用你的切面 record() 方法,并把**“hello 方法”对应的信息**封装到 pj 里面传给你!

方法作用
Object proceed()执行原始目标方法
Object[] getArgs()获取方法参数
Signature getSignature()获取方法签名对象(方法名、返回类型等)
String toShortString()获取简短方法描述(类名.方法名)
String toLongString()获取完整方法描述(包含参数类型全名)
Object getTarget()获取被代理的目标对象
Object getThis()获取AOP代理对象

3.3 通知

通知就是在切面中编写的“增强逻辑”,比如日志,权限,事务,异常处理等,它描述了你要在目标方法的哪个阶段做什么事情。上面我们为大家罗列的各种注解,那么我们现在要关注的是各种注解的执行顺序是怎样的。

我们首先先写一个Testcontroller类,用来测试我们的注解的执行顺序是怎样的。

package com.study.aopstudy.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RequestMapping("/test")
@RestController
public class TestController {
    @RequestMapping("/t1")
    public String t1(){
        log.info("执行t1方法");
        return "t1";
    }
    @RequestMapping("/t2")
    public String t2(){
        log.info("执行t2方法");
        return "t2";
    }
}

之后我们在AspectDemo类中写下如下的方法

package com.study.aopstudy.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component
public class AspectDemo {
    @Before("execution(* com.study.aopstudy.controller.*.*(..))")
    public void doBefore(){
        log.info("执行AspectDemo.before方法");
    }

    @After("execution(* com.study.aopstudy.controller.*.*(..))")
    public void doAfter(){
        log.info("执行AspectDemo.after方法");
    }

    @Around("execution(* com.study.aopstudy.controller.*.*(..))")
    public Object doAround(ProceedingJoinPoint pj){
        log.info("执行AspectDemo.doAround 目标方法前...");
        Object result = null;
        try {
            result = pj.proceed();
        } catch (Throwable throwable) {
            log.error(pj.toShortString()+"发生异常, e:",throwable);
        }
        log.info("执行AspectDemo.doAround 目标方法后...");
        return result;
    }
}

我们在浏览器的输入127.0.0.1:8080/test/t1 观察我们控制台打印的结果。

 

通过图片我们可以清晰的看到其注解执行的先后顺序。那么,我们思考在程序运行结果不出错的前提下我们的注解都能正常执行,如果程序出现了bug我们的执行结果又会有什么不同呢。我们对我们的AspectDemo类增加一些新的注解。

    @AfterReturning("execution(* com.study.aopstudy.controller.*.*(..))")
    public void doAfterReturn(){
        log.info("执行AspectDemo.doAfterReturn方法...");
    }

    @AfterThrowing("execution(* com.study.aopstudy.controller.*.*(..))")
    public void doAfterThrow(){
        log.info("执行AspectDemo.doAfterThrow方法...");
    }

同时我们将t2方法增加一行代码int a = 10/0,这样就会让程序报错,导致结果发生变化,以便我们对两种结果控制台打印的日志进行对比,我们先来看正常情况下打印的日志:

在对t2添加语句后的日志进行查看:

 

 我们可以看到@AfterThrowing在目标方法抛出异常时才执行,@AfterReturning在目标方法正常返回时才会执行。而@Around在方法执行前后都可以增加逻辑,同时还能捕获异常。

3.4切点

        从上述的代码我们可以清楚地看到对于每一个切点我们的代码高度重合。无论是 @Before、@After、@AfterReturning 还是 @Around 等通知,切点表达式几乎都是如下形式:

execution(* com.study.aopstudy.controller.*.*(..))

        这其实就是 AOP 中的切点表达式(Pointcut Expression)。它用来描述:我要拦截哪些类、哪些方法,从而将通知织入到这些方法中。 

        切点(Pointcut)用于定义“横切逻辑”作用的目标方法范围,是 Spring AOP 中通知的“入口过滤器”。你可以把它理解为一个“过滤器”或“条件表达式”,用来告诉 Spring:“我只想增强 controller 包里的所有方法,其他方法不要拦!”

        
        当你像我们这样在每个通知中都写一遍 execution(...),虽然功能上没问题,但却存在几个明显的问题:

  1. 代码重复,不好维护
  2. 不易阅读,逻辑分散
  3. 一旦切点需要修改,所有通知都要改,非常繁琐

解决方法:Spring AOP 提供了 @Pointcut 注解,我们可以将切点表达式单独封装成一个方法,其他通知只引用这个方法即可。我们对之前的代码进行一些改造:

 @Pointcut("execution(* com.study.aopstudy.controller.*.*(..))")
    public void pt(){}

    @Before("pt()")
    public void doBefore(){
        log.info("执行AspectDemo.before方法");
    }

    @After("pt()")
    public void doAfter(){
        log.info("执行AspectDemo.after方法");
    }

这样修改的优点显而易见:

  1. 改一次切点,全局生效
  2. 通知逻辑与切点范围分离,结构更清晰

        在实际开发中,随着项目的逐渐复杂,我们通常会把切点表达式进行统一管理。例如我们可能在一个类中专门定义多个切点方法,然后在多个切面中进行复用,如果我们在另一个类中引用我们需要注意引用的格式,我们必须使用全限定类名 + 静态切点方法。

//@After("pt()")错误用法
@After("com.springaop.aop.aspect.AspectDemo.pt()") //正确用法

        接下来,我们思考一个问题,当我们定义多个切面类的时候,多个切面类的执行顺序是怎样的,我们应该如何控制他们的执行顺序。假如我们定义了两个切面类它们都有 @Before 通知,都会作用在同一个控制器方法上。但如果你没有加顺序注解,Spring 不保证谁先执行,取决于扫描顺序。但是,如果我们定义的类名称是类似AspectDemo+数字这样的类,那么他会默认按照名称来先后执行的,但是这种方式对于执行顺序来说也是不可控的。

        我们需要通过@Order来明确执行的顺序,我们要注意的是再使用@Order注解时我们只能用在类上,不能加在方法上。@Order适用于所有类型通知,以 @Around 为例,执行顺序体现为嵌套:
Order(1) doAround 前
  Order(2) doAround 前
    controller 方法执行
  Order(2) doAround 后
Order(1) doAround 后

3.5自定义注解

我们在做项目中,随着项目越来越大,我们可能不希望再根据包名,类名,方法名去拦截,而是更加希望通过打了某个注解的方式,才能被增强,此时自定义+AOP切点表达式就能够用上了。

我们首先要自定义一个注解类:TimeRecord(这个名字主要是为了说明这个自定义注解的主要功能是用来记录调用接口所需要的时间)

package com.study.aopstudy.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 {
}

注解解释:

  1. @Target(ElementType.METHOD),只能标注在方法上
  2. @Retention(RetentionPolicy.RUNTIME),在运行时可通过反射读取
  3. 自定义名TimeRecord可随意命名,例如用于标记“需要统计耗时”的方法

接下来我们要创建一个切面类,专门拦截带这个注解的方法

package com.study.aopstudy.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;

@Slf4j
@Component
@Aspect

public class AspectDemo2 {
    @Around("@annotation(com.study.aopstudy.aspect.TimeRecord)")
    public Object timeRecord(ProceedingJoinPoint joinPoint){
        Long start = System.currentTimeMillis();
        Object result = null;
        try {
            result = joinPoint.proceed();
        } catch (Throwable e) {
            log.error(joinPoint.toShortString()+"发生异常,e:",e);
        }
        log.info(joinPoint.toString() + "执行时间:" + (System.currentTimeMillis() - start) + "ms");
        return result;
    }
}

之后就是使用注解了,使用非常简单就需要在在控制器中加上注解就可以。

@Slf4j
@RequestMapping("/test")
@RestController
public class TestController {
    @TimeRecord
    @RequestMapping("/t1")
    public String t1(){
        log.info("执行t1方法");
        return "t1";
    }

我们看一下打印台日志的打印结果,

使用自定义注解 + AOP 是企业项目中最常用的做法之一,能够:实现功能增强与业务逻辑完全解耦,提高可读性和可维护性,避免“全包拦截”,支持精确控制。

4.代理模式

        代理模式是结构型设计模式,它提供了一个代理对象来控制对目标对象的访问。代理对象在客户端和目标对象之间起到中介作用,可以在访问目标对象前后增加一些功能,比如权限控制,缓存,延迟加载等。代理模式主要使用在对一个类不太适合直接访问的情况下使用。

        在代理模式模式中主要由三大核心:Subject(抽象主题),RealSubject(真实主题),Proxy(代理对象),抽象主题主要是定义目标对象与代理对象共同实现的接口(通过这样确保可以被代理);真实对象就是实现实际业务逻辑的类;代理对象就是包裹真实对象,实现相同的接口并且添加增强逻辑。

        我们aop的底层正是代理模式应用的场景,spring使用代理模式为方法调用织入横切逻辑。从而体现出aop的本质是用代理对象去封装真实业务对象,在调用方法前后执行切面逻辑。

aop使用两种代理方式:

类型使用条件
JDK动态代理有接口的类
CGLIB动态代理没有接口或强制代理类

 4.1静态代理

        我们首先通过代码来了解一下静态代理,我这里创建了一个接口HouseSubject,两个类分别是HouseProxy和RealHouseSubject。

package com.study.aopstudy.proxy;

public interface HouseSubject {
    void rentHouse();

    void saleHouse();
}
package com.study.aopstudy.proxy;

public class HouseProxy implements HouseSubject{
    private HouseSubject houseSubject;

    public HouseProxy(HouseSubject houseSubject) {
        this.houseSubject = houseSubject;
    }

    @Override
    public void rentHouse() {
        System.out.println("我是中介, 我开始代理");
        houseSubject.rentHouse();
        System.out.println("我是中介, 我结束代理");
    }

    @Override
    public void saleHouse() {
        System.out.println("我是中介, 我开始代理");
        houseSubject.saleHouse();
        System.out.println("我是中介, 我结束代理");
    }
}
package com.study.aopstudy.proxy;

public class RealHouseSubject implements HouseSubject{

    @Override
    public void rentHouse() {
        System.out.println("我是房东,出租房子");
    }

    @Override
    public void saleHouse() {
        System.out.println("我是房东, 我出售房子");
    }
}

      我们来对应一下之间说的,

代理模式角色对应类名
Subject(抽象主题)HouseSubject 接口
RealSubject(真实对象)RealHouseSubject 实现类
Proxy(代理对象)HouseProxy 实现类(封装了目标对象)

        首先我们在HouseSubject接口中,定义了房屋服务的通用接口,RealHouseSubject 和 HouseProxy 都要实现它,以便客户端统一调用,AOP 中也是通过接口让代理对象与目标对象具有相同行为。然后实际业务逻辑由RealSubject提供,HouseProxy 去包装RealSubject类并且在方法前后添加增强行为。这三者结构与 AOP 中的代理机制一一对应,只是 AOP 是动态代理 + 通知注解 + 自动织入,不需要自己显式写代理类而已。

4.2JDK动态代理

        我们同样通过上面这个中介代理租房的例子来进行讲解。我们创建一个JDKInvocationHandler类。

package com.study.aopstudy.proxy;


import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

@Slf4j
public class JDKInvocationHandler implements InvocationHandler {
    private Object target;

    public JDKInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("JDK动态代理开始");
        //调用目标函数
        Object result = method.invoke(target, args);
        log.info("JDK动态代理结束");
        return result;
    }
}

我们在Main函数中这样调用

        //JDK动态代理
        //目标类
        HouseSubject target = new RealHouseSubject();
        //生成代理对象
        HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                new Class[]{HouseSubject.class},
                new JDKInvocationHandler(target));

        proxy.rentHouse();
        proxy.saleHouse();

  我们将静态代理升级成了 JDK 动态代理——仍然遵循代理模式的三角色,但把「代理类」的生成交给 JDK 在运行期完成。我们之前说过JDK代理和CGLIB代理有一定的区别,我们在额外创建一个类RealHouseSubject,但是这个类我们并不去实现接口而是写成一个普通类,去观察运行结果的区别。

package com.study.aopstudy.proxy;

public class RealHouseSubject2 {
    public void rentHouse() {
        System.out.println("我是房东, 我出租房子");
    }
    public void saleHouse() {
        System.out.println("我是房东, 我出售房子");
    }

}
        RealHouseSubject2 target = new RealHouseSubject2();
        RealHouseSubject2 proxy = (RealHouseSubject2) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                new Class[]{RealHouseSubject2.class},
                new JDKInvocationHandler(target));

        proxy.rentHouse();
        proxy.saleHouse();

我们发现我们把一个普通类穿进去之后,代码抛出异常,展现出JDK不能代理普通类的问题。但是在CGLIB中我们可以去代理普通类。

 4.3CGLIB动态代理

        我们同样通过上面这个中介代理租房的例子来进行讲解。我们创建一个CGLibMethodInterceptor类。

package com.study.aopstudy.proxy;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

@Slf4j
public class CGLibMethodInterceptor implements MethodInterceptor {
    private Object target;

    public CGLibMethodInterceptor(Object target) {
        this.target = target;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        log.info("CGLib动态代理开始");
        //调用目标方法
        Object result = method.invoke(target, objects);
        log.info("CGLib 动态代理结束");
        return result;

    }
}
        //CGlib动态代理
        HouseSubject target1 = new RealHouseSubject();
        HouseSubject proxy1 = (HouseSubject) Enhancer.create(
                target1.getClass(),
                new CGLibMethodInterceptor(target1));
        proxy1.rentHouse();
        proxy1.saleHouse();
        //CGLib代理非接口类
        RealHouseSubject2 target2 = new RealHouseSubject2();
        RealHouseSubject2 proxy2 = (RealHouseSubject2) Enhancer.create(
                target2.getClass(),
                new CGLibMethodInterceptor(target2));
        proxy2.rentHouse();
        proxy2.saleHouse();
        

无论真实对象是不是一个接口,CGLIB都可以代理。

4.4代理选择

Spring Framework与Spring Boot在AOP代理的的底层实现都是JDK和CGLIb。

        但是他俩也有不同的地方,对于Spring Framework来说,如果代理的是接口,那么就使用JDK,如果代理的是没有实现接口的类,就是用CGLIb;对与Spring Boot来说在SpringBoot 2.x版本之后,默认配置使用CGLIb代理,代理的无论是否实现了接口,都是用CGLIb代理,如果需要使用JDK代理,需要手动设置(即使手动动配置了,经过一些判断之后,最后也可能使用CGLIb代理)。在SpringBoot 2.x版本之前他与Spring Framework一样默认使用的JDK代理。

5.总结

        AOP(面向切面编程)是一种编程思想,用于将横切关注点(如日志、事务、权限控制等)从业务逻辑中分离出来,实现模块化管理。Spring AOP是其一种实现方式,通过动态代理技术在运行时将切面逻辑织入目标方法中。核心概念包括切面(Aspect)、连接点(JoinPoint)、通知(Advice)、切点(Pointcut)等,开发者可以通过注解(如@Aspect、@Around等)快速实现AOP功能。Spring AOP支持JDK动态代理(基于接口)和CGLIB动态代理(基于类),默认情况下Spring Boot 2.x以上版本优先使用CGLIB。AOP的应用显著提升了代码的可维护性和复用性,是Spring框架中不可或缺的重要特性。

###Spring AOP 的概念 AOP(Aspect-Oriented Programming)即面向切面编程,是一种编程范式,旨在通过分离横切关注点来提高模块化程度。在 Spring 框架中,AOP 被广泛用于实现诸如日志记录、事务管理、安全性等通用功能,这些功能通常与业务逻辑无关但又需要在多个地方重复使用。 Spring AOP 主要是基于 AspectJ 实现的,尽管 AspectJ 是一个独立的 AOP 框架,并不是 Spring 的组成部分,但它通常与 Spring 一起使用以提供更强大的 AOP 功能[^1]。Spring AOP 支持两种方式来定义切面:基于 XML 配置文件的方式和基于注解的方式。 ###Spring AOP 的原理 Spring AOP 使用运行时代理来实现 AOP 功能,这意味着它会在运行时动态生成代理对象。对于实现了接口的类,Spring AOP 默认使用 JDK 动态代理;而对于没有实现接口的类,则会使用 CGLIB 代理[^4]。这种方式允许在不修改原始代码的情况下向程序中添加新的行为。 织入(Weaving)是将增强(advice)应用到目标对象的过程,Spring AOP 在运行时进行织入操作[^3]。当创建了代理对象后,所有对目标对象方法的调用都会被拦截,并且可以插入额外的操作,比如在方法执行前后做一些处理。 ###Spring AOP 的使用教程 要开始使用 Spring AOP,首先需要确保项目中包含了必要的依赖。如果使用 Maven 构建工具,可以在 `pom.xml` 文件中加入如下依赖: ```xml <!-- 引入aop依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> ``` 一旦添加了依赖并刷新了 Maven 项目,就可以开始编写切面了。下面是一个简单的例子,展示如何使用注解来定义一个切面: ```java import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class LoggingAspect { @Before("execution(* com.example.service.*.*(..))") public void logBefore(JoinPoint joinPoint) { System.out.println("Method " + joinPoint.getSignature().getName() + " is called."); } } ``` 在这个示例中,`LoggingAspect` 类被标记为 `@Aspect` 和 `@Component` 注解,这样 Spring 就能识别这是一个切面组件。`@Before` 注解指定了在哪些方法上应用前置通知(before advice),这里的表达式表示匹配 `com.example.service` 包下所有的方法。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值