Spring AOP面向切面编程(一)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Spring AOP面向切面编程

一.初识AOP


1.介绍AOP


spring提供了一种可插拔的组件技术。听起来很高大上,但在我们日常生活中经常遇到这样的场景,比如说我们现在开发了两个软件模块,A和B,假设软件模块A是系统的用户管理模块,而软件模块B是系统的员工管理模块。这两个模块都拥有自己的业务处理类,他们执行的过程也是以上到下依次执行的。现在我对这两个模块提出一个要求,这两个模块从上到下进行业务处理的过程中,我希望都要进行权限过滤,只有拥有权限的用户才可以访问对应的模块。你可能会在运行实际代码前去增加相应的权限判断的业务代码,A模块加一个,B模块加一个,这样做固然没问题。但是有一天,项目经理说我们现在不需要这两块功能了,那该怎么办呢?此时你又该打开它对应的代码,把所有的权限控制代码全都去掉。那在这时候,有没有更好的办法呢?答案是肯定的。Spring AOP面向切面编程就可以很好地解决这个问题。


所谓面向切面编程就是指在我们的软件运行过程中在执行前,或者执行后,都可以去额外地增加相应的扩展功能。而这个扩展功能我们称之为切面。


b1bb308a75d54a198445d63e81c24c79.png


就拿当前的例子来说,对于软件模块A和软件模块B,我们在实际的代码运行前,先进入到权限切面中,对权限进行判断。所以我们可以称它为权限切面。权限切面对系统用户的权限进行判断,如果某系统用户拥有访问模块A,或者访问模块B的权限,那就依次向下执行。如果这个用户没有访问权限的话,则由这个切面将其挡在外边。这个权限切面就起到了对应用程序执行前进行拦截的作用。那么随着程序的运行,在程序运行完了以后,我们又可以再额外增加一个日志切面。日志切面的作用是对当前软件运行过程中几点几分运行的参数是什么,输出的结果是什么进行记录。方便我们程序的调试和跟踪。在这里,无论是权限切面,还是日志切面,对于这两个软件模块来说都是额外的,这两个软件模块在运行时也不会感知到有这两个切面的存在。那与此同时,如果有一天我们系统的业务逻辑发生了变化,不需要权限切面和日志切面了,那也只需要在配置文件中进行简单的调整就可以迅速地将这两个切面从当前系统中移除。经过我刚才的描述,是不是有点像我们浏览器中安装的各种各样的插件啊。我们无论使用什么浏览器,这些浏览器都支持插件技术。


Spring AOP 即(Aspect Oriented Programming)面向切面编程。AOP的做法是将通用的、与业务无关的功能抽象封装为切面类。切面可以配置在目标方法的执行前或执行后,真正的做到即插即用。其最终目的是在不修改源代码的情况下对程序行为进行扩展。


2.初识Spring AOP


本节通过案例,一步一步地完成一个AOP的项目配置,首先从感性上了解一下AOP到底能给我们带来哪些功能。


首先创建一个Maven项目,然后创建dao包和service包和aop包,然后创建dao类和service类。因为我现在只是学习AOP,还没有到实际开发案例,所以,里面的方法示意性的编写,只为让我们理解AOP。代码如下:


UserDao.java


package com.haiexijun.dao;
/**
 *用户表Dao
 */
public class UserDao {
    public void insert(){
        System.out.println("新增用户数据");
    }
}


EmployeeDao.java


package com.haiexijun.dao;
/**
 * 员工表Dao
 */
public class EmployeeDao {
    public void insert(){
        System.out.println("新增员工数据");
    }
}


EmployeeService.java


package com.haiexijun.service;
import com.haiexijun.dao.EmployeeDao;
/**
 * 员工服务
 */
public class EmployeeService {
    private EmployeeDao employeeDao;
    public void entry(){
        System.out.println("执行员工入职业务逻辑");
        employeeDao.insert();
    }
    public EmployeeDao getEmployeeDao() {
        return employeeDao;
    }
    public void setEmployeeDao(EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }
}


UserService.java


package com.haiexijun.service;
import com.haiexijun.dao.UserDao;
/**
 * 用户服务
 */
public class UserService {
    private UserDao userDao;
    public void createUser(){
        System.out.println("执行创建用户的业务逻辑");
        userDao.insert();
    }
    public String generateRandomPassword(String type,Integer length){
        System.out.println("按"+type+"方式生成"+length+"位随机密码");
        return "abcdeffdasf";
    }
    public UserDao getUserDao() {
        return userDao;
    }
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}


这些类再普通不过了,如果我在此时提出一个全新的要求,无论是service的方法还是dao的方法。我希望在执行之前,在控制台中打印出他们各自执行的时间。通过这些时间信息,可以让我了解到在具体的什么什么时段是一天,我们应用程序负载最高的时刻。此时,你可能会想,这还不简单吗?直接在我们的每一个方法上面增加一个sout,然后将当前系统的时间打印出来不就可以了吗?这么做当然没有问题,但你发现了没有,我们系统中的类呢还是很多的,而每个类中又有着大量的方法。即便是你通过这一句话复制粘贴很多次也是一个很麻烦的事情啊?那与此同时,有一天你的项目经理告诉你,我们也不在需要这些时间的输出了。那该怎么办呢?又要打开每一行的代码将其删除吗?这样的工作显然是十分低级的,而且容易出错。那如果,你是一个架构师,又对Spring非常了解的话,完全可以使用Spring AOP这个技术对这些方法运行前,进行进行拦截,打印时间,再去执行方法内部的代码。像这种不用修改源代码,而去对原有程序行为进行扩展的技术就是Spring AOP了。


具体的做法我下面先进行逐步的演示,然后在后面的小节中再针对于每一个细节进行详细的讲解。


先在pom.xml中引入需要的依赖。虽然spring-context这个依赖里面会又aop模块,但是还是要引入另一个依赖aspectjweaer。关于aspectjweaer这个模块的作用,在后面详细讲解。先把它引入进来就行了。这样pom.xml我们就书写好了。


    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.13</version>
        </dependency>
        <!--aspectjweaer是Spring AOP的底层依赖-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.8.RC2</version>
        </dependency>
    </dependencies>


然后在resources目录下面创建applicationContext.xml,先进行如下配置,这个配置和之前配置有些出入,多了xmlns:aop等:

下面的一些约束在https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#xsd-schemas-aop的10.1就可以找到。


<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        https://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        https://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>


然后在当前容器中进行bean的配置


<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       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">
    <bean id="userDao" class="com.haiexijun.dao.UserDao"/>
    <bean id="employeeDao" class="com.haiexijun.dao.EmployeeDao"/>
    <bean id="userService" class="com.haiexijun.service.UserService">
        <property name="userDao" ref="userDao"/>
    </bean>
    <bean id="employeeService" class="com.haiexijun.service.EmployeeService">
        <property name="employeeDao" ref="employeeDao"/>
    </bean>
</beans>


接下来,创建入口类测试一下:


package com.haiexijun;
import com.haiexijun.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringApplication {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        UserService userService= context.getBean("userService", UserService.class);
        userService.createUser();
    }
}


运行后如下图:


49f4004798ca4a0e83b186001ee91755.png


在aop包下面新增加一个新的包aspect,里面创建一个MethodAspect的类,这是一个针对于方法的切面。我们在里面定义一个切面方法,打印执行时间。切面方法必须要额外加入一个JoinPoint类型的参数(连接点), 通过连接点可以获取目标类/方法的信息。那什么是目标类、目标方法呢?其实就是我们真正要执行的这个方法就称之为目标方法,而这个目方法隶属于的类就是目标类。在我们程序在输出的时候,肯定是要打印什么时间,哪个类,什么方法在运行。


package com.haiexijun.aspect;
import org.aspectj.lang.JoinPoint;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
 * 切面类
 */
public class MethodAspect {
    /**
     * 切面方法,打印执行时间
     * @param joinPoint 切面方法必须要额外加入一个JoinPoint类型的参数(连接点)
     *                  通过连接点可以获取目标类/方法的信息
     */
    public void printExecutionTime(JoinPoint joinPoint){
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String now= sdf.format(new Date());
        String ClassName= joinPoint.getTarget().getClass().getName();//获取目标类的名称
        String MethodName=joinPoint.getSignature().getName();//获取目标方法的名称
        System.out.println("---->"+now+":"+ClassName+"."+MethodName);
    }
}


写好这个类后,spring可不知道printExecutionTime方法是在哪些的哪些方法运行前去执行啊。所以,我们还是要在啊applicationContext里面进行aop进行配置,说明我这个切面类作用的范围是什么。

具体的配置方法:


    <!--AOP配置-->
    <bean id="methodAspect" class="com.haiexijun.aspect.MethodAspect"/>
    <aop:config>
        <!--pointcut是切点的意思,使用execution表达式描述切面的作用范围-->
        <!--下面的execution表达式说明切面作用于com.haiexijun包下的所有类的所有方法上-->
        <aop:pointcut id="pointcut" expression="execution(public * com.haiexijun..*.*(..))"/>
        <!--定义切面类-->
        <aop:aspect ref="methodAspect">
            <!--before通知Advice,代表在目标方法运行前先执行切面类里面的方法-->
            <aop:before method="printExecutionTime" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>


后面具体讲解里面的配置。

我们回到主程序,直接运行:


21ed167b86a74985b25faa86c6b650ac.png


我们之前关于时间打印的需求就完成了。如果有一天,不需要这个功能了,就在applicationContext里面把这段aop配置信息给注释掉就行了。到这里,相信你对Spring AOP就有一定的了解了,接下来的小节就来深入学习里面的配置了。

相关实践学习
通过日志服务实现云资源OSS的安全审计
本实验介绍如何通过日志服务实现云资源OSS的安全审计。
相关文章
|
1月前
|
XML 安全 Java
使用 Spring 的 @Aspect 和 @Pointcut 注解简化面向方面的编程 (AOP)
面向方面编程(AOP)通过分离横切关注点,如日志、安全和事务,提升代码模块化与可维护性。Spring 提供了对 AOP 的强大支持,核心注解 `@Aspect` 和 `@Pointcut` 使得定义切面与切入点变得简洁直观。`@Aspect` 标记切面类,集中处理通用逻辑;`@Pointcut` 则通过表达式定义通知的应用位置,提高代码可读性与复用性。二者结合,使开发者能清晰划分业务逻辑与辅助功能,简化维护并提升系统灵活性。Spring AOP 借助代理机制实现运行时织入,与 Spring 容器无缝集成,支持依赖注入与声明式配置,是构建清晰、高内聚应用的理想选择。
301 0
|
2月前
|
Java API 开发者
Spring 控制反转与依赖注入:从玄学编程到科学管理
在传统开发中,手动`new`对象导致紧耦合、难以维护和测试。控制反转(IoC)将对象创建交给框架,实现解耦。Spring通过IOC容器自动管理对象生命周期,开发者只需声明依赖,无需关心创建细节。依赖注入(DI)是IoC的具体实现方式,支持构造器、Setter和字段注入。构造器注入推荐使用,保证依赖不可变且易于测试。对于多个同类型Bean,可用`@Qualifier`或`@Primary`解决冲突。此外,Spring还支持依赖查找(DL),开发者主动从容器获取Bean,适用于动态场景,但侵入性强。掌握IoC与DI,有助于构建灵活、可维护的Spring应用。
|
5月前
|
监控 安全 Java
Spring AOP实现原理
本内容主要介绍了Spring AOP的核心概念、实现机制及代理生成流程。涵盖切面(Aspect)、连接点(Join Point)、通知(Advice)、切点(Pointcut)等关键概念,解析了JDK动态代理与CGLIB代理的原理及对比,并深入探讨了通知执行链路和责任链模式的应用。同时,详细分析了AspectJ注解驱动的AOP解析过程,包括切面识别、切点表达式匹配及通知适配为Advice的机制,帮助理解Spring AOP的工作原理与实现细节。
|
2月前
|
人工智能 监控 安全
Spring AOP切面编程颠覆传统!3大核心注解+5种通知类型,让业务代码纯净如初
本文介绍了AOP(面向切面编程)的基本概念、优势及其在Spring Boot中的使用。AOP作为OOP的补充,通过将横切关注点(如日志、安全、事务等)与业务逻辑分离,实现代码解耦,提升模块化程度、可维护性和灵活性。文章详细讲解了Spring AOP的核心概念,包括切面、切点、通知等,并提供了在Spring Boot中实现AOP的具体步骤和代码示例。此外,还列举了AOP在日志记录、性能监控、事务管理和安全控制等场景中的实际应用。通过本文,开发者可以快速掌握AOP编程思想及其实践技巧。
|
2月前
|
人工智能 监控 安全
如何快速上手【Spring AOP】?核心应用实战(上篇)
哈喽大家好吖~欢迎来到Spring AOP系列教程的上篇 - 应用篇。在本篇,我们将专注于Spring AOP的实际应用,通过具体的代码示例和场景分析,帮助大家掌握AOP的使用方法和技巧。而在后续的下篇中,我们将深入探讨Spring AOP的实现原理和底层机制。 AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架中的核心特性之一,它能够帮助我们解决横切关注点(如日志记录、性能统计、安全控制、事务管理等)的问题,提高代码的模块化程度和复用性。
|
2月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
6月前
|
人工智能 监控 Java
面向切面编程(AOP)介绍--这是我见过最易理解的文章
这是我见过的最容易理解的文章,由浅入深介绍AOP面向切面编程,用科普版和专家版分别解说,有概念,有代码,有总结。
|
7月前
|
Java API 微服务
微服务——SpringBoot使用归纳——Spring Boot中的切面AOP处理——Spring Boot 中的 AOP 处理
本文详细讲解了Spring Boot中的AOP(面向切面编程)处理方法。首先介绍如何引入AOP依赖,通过添加`spring-boot-starter-aop`实现。接着阐述了如何定义和实现AOP切面,包括常用注解如`@Aspect`、`@Pointcut`、`@Before`、`@After`、`@AfterReturning`和`@AfterThrowing`的使用场景与示例代码。通过这些注解,可以分别在方法执行前、后、返回时或抛出异常时插入自定义逻辑,从而实现功能增强或日志记录等操作。最后总结了AOP在实际项目中的重要作用,并提供了课程源码下载链接供进一步学习。
780 0
|
7月前
|
Java 开发者 微服务
微服务——SpringBoot使用归纳——Spring Boot中的切面AOP处理——什么是AOP
本文介绍了Spring Boot中的切面AOP处理。AOP(Aspect Oriented Programming)即面向切面编程,其核心思想是分离关注点。通过AOP,程序可以将与业务逻辑无关的代码(如日志记录、事务管理等)从主要逻辑中抽离,交由专门的“仆人”处理,从而让开发者专注于核心任务。这种机制实现了模块间的灵活组合,使程序结构更加可配置、可扩展。文中以生活化比喻生动阐释了AOP的工作原理及其优势。
392 0