万金流
以码会友。 吾Q:578751655。 水平有限,轻喷,谢!

问题

日志、验证、事务、权限等逻辑贯穿项目各处。

如果让每个类各自实现这些逻辑:

  • 重复:每个方法都要写一遍,代码大量重复。
  • 耦合:业务代码和通用逻辑混在一起,职责不清。
  • 难维护:改一处日志格式,要改几十个类。

这些逻辑叫横切关注点——它们横跨多个模块,却不属于任何一个模块的核心职责。

解决

把这些横切逻辑从业务代码中抽出来,集中定义,再在需要的地方“织入”。

业务类只写业务,日志/验证等由切面统一处理。

基础概念

概念 含义
Aspect(切面) 横切逻辑的模块化,如日志切面
Advice(通知) 切点处执行的动作,如前置、后置、环绕

通知类型

  • @Before:方法执行前
  • @After:方法执行后
  • @AfterReturning:正常返回后
  • @AfterThrowing:抛异常后
  • @Around:包围方法---唯一可控制执行与返回值的通知

小结

AOP 让业务逻辑和横切逻辑各归其位,是对 OOP 的补充。

例子

逻辑

  1. @PreDoSth 注解:标记在类或方法上,表示“这个地方需要切入”。
  2. MyLog 切面:切点匹配“带 @PreDoSth 的地方”,通知类型 @Before,输出一行日志。
  3. UserService:业务类,createUser 方法标注 @PreDoSth。
  4. CommandLineRunner:启动后调用 userService.createUser("Alice")。

1. 注解文件 PreDoSth.java

package com.example.demo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})  // 可标在类或方法上
@Retention(RetentionPolicy.RUNTIME)              // 运行时可被读取
public @interface PreDoSth {
}

2. 切面文件 MyLog.java

package com.example.demo.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyLog {

    // 匹配所有标注了 @PreDoSth 的方法
    @Before("@annotation(com.example.demo.annotation.PreDoSth)")
    public void Logging() {
        System.out.println("进行业务代码之前,可以在这里记录日志");
    }
}

3. 业务类 UserService.java

package com.example.demo.service;

import com.example.demo.annotation.PreDoSth;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @PreDoSth
    public String createUser(String name) {
        System.out.println("【业务】正在创建用户:" + name);
        return "创建成功: " + name;
    }
}

4. 调用 DemoApplication.java

package com.example.demo;

import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication implements CommandLineRunner {

    @Autowired
    private UserService userService;

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Override
    public void run(String... args) {
        String msg = userService.createUser("Alice");
        System.out.println("【调用者拿到】" + msg);
    }
}

5. pom.xml 依赖

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

运行结果:

进行业务代码之前,可以在这里记录日志
【业务】正在创建用户:Alice
【调用者拿到】创建成功: Alice

说明

  • @PreDoSth 只是标签,本身不执行任何逻辑。
  • MyLog 里的 @Before 才是真正执行日志的地方。
  • 切点 @annotation(...) 表示“匹配标了这个注解的方法”。

如果想改成标在类上,把 UserService 的 @PreDoSth 从方法移到类上,切点表达式从@annotation(com.example.demo.annotation.PreDoSth)换成 @within(com.example.demo.annotation.PreDoSth) 即可。也可以用@Before("@within(com.example.demo.annotation.PreDoSth) || " +"@annotation(com.example.demo.annotation.PreDoSth)")让它在类和方法上都生效。

切片控制业务(@Around)

  • 需求:创建用户的时候,需要对用户名长度进行判断。长度小于5,则不创建该用户。日常判断权限,就是这个道理

  • 代码

package com.example.demo.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyLog {

    // // 匹配所有标注了 @PreDoSth 的方法
    // @Before("@annotation(com.example.demo.annotation.PreDoSth)")
    // public void Logging() {
    //     System.out.println("进行业务代码之前,可以在这里记录日志");
    // }
    @Around("@annotation(com.example.demo.annotation.PreDoSth)")
    public Object aroundCreateUser(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();
        String name = (String) args[0];

        if (name == null || name.length() < 5) {
            return "创建失败:用户名长度必须大于等于5";
        }
        return pjp.proceed();
    }
}

运行效果自行查看。

  • 说明:
  1. @Around可以通过参数获取要执行的业务方法(这里是pjp)。在切片方法内部,调用pjp.proceed()运行业务。业务方法返回值,必须通过切片return才能被调用方拿到。

  2. 正是通过上面的流程,@Around切片可以实现对切入业务的任意控制。

含ai生成、作者整理的内容。

posted on 2026-10-06 16:40  万金流  阅读(29)  评论(0)    收藏  举报