观看视频:Spring_AOP(上)_哔哩哔哩_bilibili

Spring Aop入门

✅ 1. 什么是 AOP?

  • 全称:Aspect Oriented Programming → 面向切面编程
  • 定位:Spring 框架两大核心特性之一(另一个是 IOC/DI)
  • 关系:不是取代 OOP,而是补充 OOP。
    • OOP 处理“业务主体逻辑”(如用户注册、下单)
    • AOP 处理“横切关注点”(如日志、事务、安全校验)

🎭 “星爷+达叔”比喻非常形象:

  • 星爷 = OOP(主角,负责主线剧情)
  • 达叔 = AOP(配角,关键时刻出场帮忙,不抢戏)

❓ 2. 为什么需要 AOP?—— OOP 的局限性

传统 OOP 通过封装、继承、多态解决代码复用和模块化,但遇到以下场景会显得力不从心:

  • 重复代码泛滥:每个方法都要写日志、开事务、判权限…
  • 耦合度高:业务逻辑和非业务逻辑混在一起,难以维护
  • 违反单一职责原则:一个方法既做业务又做日志还做事务

👉 AOP 就是为了解决这些“横切关注点”的分散问题而生!


🛠️ 3. AOP 要解决的问题

3.1 分工明确
  • OOP → 主线任务(业务逻辑)
  • AOP → 打辅助(通用功能)
3.2 配角的要求(AOP 的设计哲学)
  • 不能抢戏 → 不影响主业务流程
  • 招之即来,挥之即去 → 灵活配置,按需启用/关闭
  • 实现机制:文本
    weaving (织入) 
       ↓
    动态代理 
       ↓
    JDK 动态代理 / CGLIB

⚙️ Weaving(织入):将切面代码“插入”到目标对象的过程。

  • 编译期织入(AspectJ)
  • 运行期织入(Spring AOP 默认方式,基于动态代理)
3.3 典型应用场景
  • ✅ 日志记录:自动记录方法调用时间、参数、返回值
  • ✅ 事务管理:@Transactional 注解背后就是 AOP
  • ✅ 权限控制:拦截请求,检查用户是否有访问权限
  • ✅ 性能监控、缓存、异常统一处理等

🧩 4. AOP 的核心概念(必须掌握!)

术语英文说明
关注点Concern程序中某个特定的功能模块,比如“日志”、“事务”。
横切关注点Cross-cutting Concern跨越多个模块的关注点(如所有 Service 层方法都需要日志)。
连接点Join Point程序执行过程中的任意一点(如方法调用、异常抛出),Spring 只支持方法级别的连接点。
切入点Pointcut【重点】对连接点的定义,即“哪些方法需要被增强”。用表达式匹配。
通知/增强Advice在切入点处执行的额外逻辑(如打印日志)。
通知类型Advice Type- @Before:前置通知
- @After:后置通知
- @Around:环绕通知(最强大)
- @AfterReturning:返回后通知
- @AfterThrowing:异常后通知
切面Aspect【核心】= 切入点 + 通知。是一个类,包含“在哪里增强”和“增强什么”。

📌 图中公式:切面(类) = 切入点 + 通知 —— 这是理解 AOP 的关键!

关于切入点和连接点

1. 核心区别一句话总结

  • 连接点 (JoinPoint,候选切入点):是“所有可能”被拦截的地方(客观存在的集合)。
    • 比如:餐厅里每一道菜的制作过程(炒青菜、炖肉、煮汤...)都是连接点。
  • 切入点 (Pointcut):是“实际选中”要拦截的地方(主观定义的规则/子集)。
    • 比如:老板规定“只检查所有肉类菜肴的制作过程”。这个规则选中的那些菜,就是切入点。

公式:切入点 = 连接点的一个子集


2. 详细对比表

特性连接点 (JoinPoint)切入点 (Pointcut)
定义程序执行过程中具体的某个点(在 Spring 中特指方法执行)。匹配连接点的表达式或规则。
性质客观存在的。只要方法被调用,它就是一个连接点。主观定义的。是你写代码时指定要去拦截哪些连接点。
数量程序中可能有成千上万个连接点(所有方法)。通常只有几个切入点(你关心的那几个业务)。
代码体现在通知方法(Advice)中作为参数 JoinPoint joinPoint 出现。在注解 @Pointcut 或 @Before("expression") 的字符串中体现。
能否获取信息能。可以通过它获取方法名、参数、返回值等。不能。它只是一个匹配规则(字符串表达式),本身不包含运行时数据。

Spring Aop经典实现

@Aspect
@Component
public class MyAspect {

    //切入点
    @Pointcut("execution(* com.fast.springaop.Service.*.*(..) )")
    public void myPointCut1() {
        //切入点表达式: execution(* com.fast.springaop.Service.*.*(..) )
        //含义:com.fast.springaop.Service包下的所有方法(不区分方法参数的)
        //execution(* com.fast.springaop.Service.*.*(String,int) )
        //含义:com.fast.springaop.Service包下的所有方法(参数为String,int)

    }
  

    //通知(环绕)
    @Around("myPointCut1()")
    public Object recordLogs(ProceedingJoinPoint joinPoint) throws Throwable {
        //oop辅助任务
        System.out.println("(前)oop辅助任务");
        //oop执行主线任务
        Object obj = joinPoint.proceed();
        //oop辅助任务
        System.out.println("(后)oop辅助任务");
        //返回oop执行主线任务后的结果
        return obj;
    }

}

如果想排除某个方法

@Aspect
@Component
public class MyAspect {

    //切入点
    @Pointcut("execution(* com.fast.springaop.Service.*.*(..) )")
    public void myPointCut1() {
        //切入点表达式: execution(* com.fast.springaop.Service.*.*(..) )
        //含义:com.fast.springaop.Service包下的所有方法(不区分方法参数的)
        //execution(* com.fast.springaop.Service.*.*(String,int) )
        //含义:com.fast.springaop.Service包下的所有方法(参数为String,int)

    }
    //切入点2
    @Pointcut("execution(* com.fast.springaop.Service.*.getUserList(..) )")//
    public void myPointCut2() {
        //切入点表达式: execution(* com.fast.springaop.Service.*.*(..) )
        //含义:com.fast.springaop.Service包下的所有方法(不区分方法参数的))


    }

    //通知(环绕)
    @Around("myPointCut1() && !myPointCut2()")//切入点 符合myPointCut1()的方法,除了getUserList
    public Object recordLogs(ProceedingJoinPoint joinPoint) throws Throwable {
        //oop辅助任务
        System.out.println("(前)oop辅助任务");
        //oop执行主线任务
        Object obj = joinPoint.proceed();
        //oop辅助任务
        System.out.println("(后)oop辅助任务");
        //返回oop执行主线任务后的结果
        return obj;
    }

}

可以自定义注解进行切入

它允许你通过简单的标记来触发切面逻辑,而不需要写复杂的正则表达式(Pointcut Expression)去匹配方法名。

第一步:定义自定义注解

首先,我们需要创建一个注解。这个注解本身没有任何逻辑,它只是一个“标记”。

文件路径: src/main/java/com/fast/springaop/annotation/MyAnnotaion.java

package com.fast.springaop.annotation;

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

// 1. @Target: 指定注解可以用在哪里
// METHOD 表示只能加在方法上
@Target(ElementType.METHOD)

// 2. @Retention: 指定注解的生命周期
// RUNTIME 表示注解在运行时依然存在,这样 AOP 才能通过反射读取到它
// 如果选 CLASS,编译后就没用了,AOP 就失效了
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotaion {
    // 可以定义一些属性,比如备注信息
    String value() default "";
}
//切入点3
@Pointcut("@annotation(com.fast.springaop.annotaion.MyAnnotaion)")
public void myPointCut3() {
    //切入点表达式: execution(* com.fast.springaop.Service.*.*(..) )
    //含义:com.fast.springaop.Service包下的所有方法(不区分方法参数的))


}

//通知(环绕)
@Around("myPointCut3()")
public Object recordLogs(ProceedingJoinPoint joinPoint) throws Throwable {
    //oop辅助任务
    System.out.println("(前)oop辅助任务");
    //oop执行主线任务
    Object obj = joinPoint.proceed();
    //oop辅助任务
    System.out.println("(后)oop辅助任务");
    //返回oop执行主线任务后的结果
    return obj;
}

在想要切入的地方加上注解

package com.fast.springaop.Service.impl;

import com.fast.springaop.Service.UserService;
import com.fast.springaop.annotaion.MyAnnotaion;
import com.fast.springaop.dao.UserDao;
import com.fast.springaop.dao.impl.UserDaoImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;
    @Override
    public void getUserList() {
        userDao.selectUser();
    }

    @Override
    @MyAnnotaion("保存用户")
    public void saveUser() {
        userDao.insertUser();
    }

    @Override
    public void updateUser() {
        userDao.updateUser();
    }

    @Override
    @MyAnnotaion("删除用户")
    public void removeUser() {
        userDao.deleteUser();
    }

    @Override
    @MyAnnotaion("修改用户")
    public void modifyUser() {
        userDao.updateUser();
    }
}

SpringAOP——静态代理

【Spring中的代理模式】

1.为什么需要代理
生活中的代理场景
演员 -> 经纪 -> 商演
网民 -> 代购 -> 商家
租客 -> 中介 -> 房东
搜房源 -> 分析对比 -> 联系房东 中介(代理)
看房 -> 签订合同 -> 支付租金 -> 入住 租客
扩展:房源
解除了房源带来的限制:解耦

2.什么是代理模式
使用【代理类对象】去扩展【原始类对象】的功能,而又无需对原始类进行修改的一种设计模式;
使用代理模式,对原始类对象的访问转变为对代理对象的访问,并且由代理对象把返回值给调用者;
代理模式分静态代理、动态代理两种。

3.静态代理的实现
3.1 实现手段
1)代理类、原始类实现相同接口

由中介去搜房源 ->  分析对比 -> 联系房东 ->

自己做看房 -> 签订合同 -> 支付租金 -> 入住

public class HouseAgent implements Rental {
    /**
     * 中介类(代理类)
     */
    //who
    private Tenant tenant;


    public HouseAgent(Tenant tenant) {
        this.tenant = tenant;
    }

    @Override
    public void renting() {
        System.out.println("搜房源 ->  分析对比 -> 联系房东 -> ");
        //what
        tenant.renting();
    }
}
public interface Rental {
    public void renting();
}
/**
 * 租客类(原始类)
 */
public class Tenant implements Rental{
    @Override
    public void renting() {
        System.out.println(" 看房 -> 签订合同 -> 支付租金 -> 入住");
    }
}
@SpringBootTest
public class apptest {
    @Test
    public void testStaticProxyByInterface() {
        //租客对象
        Tenant tenant = new Tenant();
        //中介对象(代理对象)
        HouseAgent agent = new HouseAgent(tenant);
        //调用(测试租房)
        agent.renting();

    }
}


2)代理类 extends 原始类

public class TenantChild extends Tenant{
    //who -> 为谁代理(父类)
    //what -> 方法继写

    @Override
    public void renting() {
        System.out.print("搜房源 ->  分析对比 -> 联系房东 -> ");
        super.renting();//what
    }

}
 @Test
    public void testStaticProxyByExtends() {
        //租客对象
        TenantChild tenantChild = new TenantChild();
        //调用(测试租房)
        tenantChild.renting();

    }

3.2 静态代理的局限

  1. 手动创建代理类 2) 类型单一 3) 代理类分散, 维护成本高(1个原始类 VS 1个代理类)

SpringAOP——动态代理

1.动态代理的实现
1.1 实现手段

  1. 代理类、原始类实现相同接口
  2. 代理类 extends 原始类

1.2 动态代理的优势

  1. 运行时动态生成代理类 2) 支持为所有类型做代理 3) 维护成本低

2.SpringAOP与动态代理
 2.1 Spring和SpringBoot在动态代理实现上面的区别
 Spring: 原始类实现了接口则jdk, 否则cglib
 SpringBoot: 默认jdk -> v2.0 -> 默认cglib

/**
 * 代理工厂类
 */
public class ProxyFactory {
    //who
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }
    //how
    public Object jdkProxy(){
        //类加载器
        ClassLoader loader = this.getClass().getClassLoader();
        //原始对象所实现的所有的接口数组
        Class<?>[] interfaces = target.getClass().getInterfaces();
        InvocationHandler h = (Object proxy, Method method, Object[] args)->{
            System.out.print("搜房源 ->  分析对比 -> 联系房东 -> ");//增强
            Object invoked = method.invoke(target, args);
            return invoked;
        };
        return Proxy.newProxyInstance(loader,interfaces,h);
    }

    public Object cglibProxy(){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.print("搜房源 ->  分析对比 -> 联系房东 -> ");//增强
                Object invoked = method.invoke(target, args);
                return invoked;
            }
        });
        return enhancer.create();
    }
}
@Test
    public void testDynamicProxyByExtends() {
        //租客(原始对象)
        Tenant tenant = new Tenant();
        //代理工厂
        ProxyFactory factory = new ProxyFactory(tenant);
        //代理对象
        //Rental obj = (Rental) factory.jdkProxy();
        Rental rental = (Rental) factory.cglibProxy();
        //测试(租房)
        rental.renting();
    }

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐