Spring实现AOP

一、AOP概述

1、简介

AOP(面向切面向切面编程,Aspect-Oriented Programming)是一种编程范式,它关注于将横切关注点(cross-cutting concerns)与业务逻辑分离。横切关注点通常是一些与业务逻辑无关,但又需要应用到多个模块的功能,如日志记录、安全控制、事务管理等。

在传统的 OOP(面向对象编程)中,代码通常通过继承、接口和多态来组织和复用。AOP 通过“切面”(Aspect)来实现功能的模块化,使得这些横切关注点能够单独提取出来,并可以在不修改现有代码的情况下,插入到程序的执行流程中。

用一个例子来更好的理解AOP。

假设你是一家餐厅的主厨(被增强的类),负责完整的做菜流程:

1.接收订单 → 2. 准备食材 → 3. 烹饪 → 4. 摆盘 → 5. 出菜

突然卫生部门要求:

  • 每次处理食材前要记录操作人员
  • 每个步骤完成后要消毒操作台
  • 出菜前要拍照留存
  • 每个环节需要计时统计效率

如果采用传统方式OOP,需要在做菜五步的每一步手动插入对应的操作,这样会导致:

  • 流程被非核心逻辑污染
  • 重复代码遍布各处
  • 修改卫生规则需要改动所有步骤

但是我们把做这些工作交个别的部门(切面类)来处理,餐厅新招了两个后厨卫生监督员来完成卫生部门要求的这些工作,监督员的检查就相当于AOP中的通知,主厨被检查的动作就相当于AOP中的切点。

2、优势

1.解耦

AOP 允许将横切关注点(如日志、安全、事务等)从主业务逻辑中分离出来,避免了在每个业务类中重复写这些功能的代码,从而降低了代码耦合度。

2.提高代码复用性

横切关注点被独立成一个切面,可以在多个类中复用,减少了代码冗余。

3.提高代码的可维护性

将横切关注点从业务逻辑中剥离开,简化了业务逻辑的实现,使代码更加清晰和易于理解。

4.增强可拓展性

可以动态地给现有的类和方法增加额外的功能,而无需修改原有代码

3、底层原理

1. 动态代理:

AOP 通过创建目标对象的代理对象来增强目标方法的行为。Spring AOP 默认使用 JDK 动态代理(针对接口)和 CGLIB 代理(针对类)来生成代理对象。

JDK 动态代理:如果目标类实现了接口,则通过反射机制创建一个代理类来增强目标方法的执行。

CGLIB 代理:如果目标类没有实现接口,则通过继承目标类生成一个子类,并对方法进行增强。

2. 字节码增强:

这是 AOP 的一种更底层的实现方式,通过对目标类的字节码进行修改来增强方法的行为。字节码增强常见的工具有 ASM、Javassist 等。

3. 通知的织入(Weaving):

织入是指将切面(Aspect)应用到目标对象的过程。织入可以发生在编译时、类加载时或运行时(Spring AOP 是运行时织入)。

在运行时,Spring 通过代理机制动态地将增强的功能织入到目标方法中。

4、相关术语

切面(Aspect):切面是一个关注点的模块,包含了通知和切点的逻辑。例如,日志切面会包含日志记录的功能,事务切面包含事务管理的功能。

通知(Advice):通知定义了在切点发生时所执行的具体操作。根据执行时机的不同,通知可分为多种类型:

  • @Before:方法执行前通知
  • @After:方法执行后通知(无论方法是否抛出异常)
  • @AfterReturning:方法执行成功后通知
  • @AfterThrowing:方法抛出异常时通知
  • @Around:环绕通知,可以在方法执行前后加入自定义逻辑

切点(Pointcut):切点定义了哪些方法应该被通知,即哪些方法的执行符合通知的应用条件。切点通常使用表达式来描述目标方法的匹配规则。

切点表达式:

  • execution([修饰符]返回值类型包名.类名.方法名(参数))
  • 修饰符可以省略不写,不是必须要出现的。
  • 返回值类型是不能省略,根据你的方法来编写返回值。可以使用*代替。
  • 包名例如:com.tx.demo3.BookDaoImpl
    • 首先com是不能省略不写的,但是可以使用*代替
    • 中间的包名可以使用*号代替
    • 如果想省略中间的包名可以使用“..”
  • 类名也可以使用*号代替,也有类似的写法:*DaoImpl
  • 方法也可以使用*号代替
  • 参数如果是一个参数可以使用*号代替,如果想代表任意参数使用“..”

连接点(Joinpoint):连接点是程序执行过程中的一个特定位置,比如方法调用、字段修改、构造函数调用等。连接点是 AOP 执行的潜在位置。

目标对象(Target Object):目标对象是被代理的对象,即最终执行业务逻辑的对象。它是 AOP 中功能增强的主要对象。

代理(Proxy):代理是指一个对象,它通过代理方式增强目标对象的行为,通常是在目标对象的基础上创建一个新的代理对象,通过这个代理对象来执行增强后的业务逻辑。

织入(Weaving):织入是指将切面应用到目标对象的过程。在 Spring AOP 中,这通常发生在运行时。

引入(Introduction):引入允许在不修改目标对象的情况下,向目标对象添加新的方法或属性。Spring AOP 通过 @DeclareParents 或接口来实现引入。

二、配置文件方式实现AOP

1、创建maven项目,引入坐标依赖

<dependencies>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.2.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
  </dependency>
  <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.12</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.0.2.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
  </dependency>
  <!--AOP联盟-->
  <dependency>
    <groupId>aopalliance</groupId>
    <artifactId>aopalliance</artifactId>
    <version>1.0</version>
  </dependency>
  <!--SpringAspects-->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.0.2.RELEASE</version>
  </dependency>
  <!--aspectj-->
  <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.3</version>
  </dependency>
</dependencies>

2、配置Spring的配置文件

maven坐标依赖没引入成功的话,新建xml文件时不会出现 Spring Config 的文件类型提示

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    
</beans>

3、创建接口和实现类

UserService接口

package com.goose.demo1;

public interface UserService {
    public void save();
}

UserServiceImpl实现类

package com.goose.demo1;

import org.springframework.stereotype.Component;

/**
 * @Author: Goose
 * @Description: TODO  
 * @Date: 2025/5/13 22:16
 * @Version: 1.0
 */
@Component
public class UserServiceImpl implements UserService{
    @Override
    public void save() {
        System.out.println("业务层:保存用户........");
    }
}

将要增强的目标类配置到Spring.xml文件中

<bean id="userService" class="com.goose.demo1.UserServiceImpl"/>

4、定义切面类

package com.goose.demo1;

/**
 * @Author: Goose
 * @Description: 定义的切面类
 * @Date: 2025/5/13 22:19
 * @Version: 1.0
 */

public class MyXmlAspect {
    /*
    * 通知
    * */
    public void log(){
        /*
        * 发送手机短信
        * 发送邮件/记录日志/事务管理
        * */
        System.out.println("增强的方法执行了......");
    }
}

将切面类配置到Spring.xml文件中

<bean id="MyXmlAspect" class="com.goose.demo1.MyXmlAspect"/>

5、完成AOP的配置

<!--配置AOP的增强-->
<aop:config>
    <!--配置切面=切入点+通知组成-->
    <aop:aspect ref="MyXmlAspect">
        <!--前置通知:UserServiceImpl的save方法执行前,会增强-->
        <aop:after method="log" pointcut="execution(public void com.goose.demo1.UserServiceImpl.save())"/>
    </aop:aspect>
</aop:config>

6、test文件夹中进行测试

package com.goose;

import com.goose.demo1.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @Author: Goose
 * @Description: TODO
 * @Date: 2025/5/13 22:22
 * @Version: 1.0
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class Demo1 {
    @Autowired
    private UserService userService;
    /*
    * 测试方法
    * */
    @Test
    public void run1(){
        userService.save();
    }
}

三、注解方式实现AOP

要在spring配置文件中添加bean

1、配置文件中开启自动扫描

1.编写切面类

package com.goose.demo2;

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

/**
 * @Author: Goose
 * @Description: TODO
 * @Date: 2025/5/13 22:38
 * @Version: 1.0
 */
@Component  // 交给IOC去处理
@Aspect     // 声明是切面类
public class MyAnnoAspect {
    /*
    * 通知的方法
    * */
    @Before(value = "execution(public void com.goose.demo2.UserServiceImpl2.save())")
    public void log(){
        System.out.println("增强的方法执行了......");
    }
}

2.开启自动代理

<!--开启自动代理-->
<aop:aspectj-autoproxy/>

3.测试类中测试

package com.goose;

import com.goose.demo1.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @Author: Goose
 * @Description: TODO
 * @Date: 2025/5/13 22:43
 * @Version: 1.0
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class Demo2 {
    @Autowired
    private UserService userService;
    /*
    * 测试方法
    * */
    @Test
    public void run2(){
        userService.save();
    }
}

2、配置类实现AOP

1.配置类

package com.goose.demo2;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * @Author: Goose
 * @Description: TODO
 * @Date: 2025/5/13 22:49
 * @Version: 1.0
 */
@Configuration
@ComponentScan(value = "com.goose.demo2")
@EnableAspectJAutoProxy     //开启自动代理
public class SpringConfig {}

2.测试类

package com.goose;

import com.goose.demo2.SpringConfig;
import com.goose.demo2.UserServiceImpl2;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @Author: Goose
 * @Description: 使用配置类实现AOP
 * @Date: 2025/5/13 23:07
 * @Version: 1.0
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class Demo3 {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserServiceImpl2 bean = applicationContext.getBean(UserServiceImpl2.class);
        bean.save();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值