【Spring6】| 面向切面编程AOP(文末书籍一本免费奉上)

这篇具有很好参考价值的文章主要介绍了【Spring6】| 面向切面编程AOP(文末书籍一本免费奉上)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一:面向切面编程AOP

1. AOP介绍

2. AOP的七大术语

3. 切点表达式

4. 使用Spring的AOP

4.1 准备工作

4.2 基于AspectJ的AOP注解式开发

4.3 基于XML配置方式的AOP(了解)

5. AOP的实际案例:事务处理

6. AOP的实际案例:安全日志


一:面向切面编程AOP

(1)IoC(控制反转)是使软件组件松耦合;而接下来要学的AOP(面向切面)让你能够捕捉系统中经常使用的功能,把它转化成组件

(2)AOP(Aspect Oriented Programming):面向切面编程(非业务的逻辑代码【或者说交叉业务】提取出来),面向方面编程。(AOP是一种编程技术)

(3)AOP底层使用的就是【动态代理】来实现的;AOP是对OO(面向对象)的补充延伸。

(4)Spring的AOP使用的动态代理是JDK动态代理 + CGLIB动态代理技术;Spring在这两种动态代理中灵活切换,如果是代理接口,会默认使用JDK动态代理,如果要代理某个类,这个类没有实现接口,就会切换使用CGLIB代理。当然,你也可以强制通过一些配置让Spring只使用CGLIB。

1. AOP介绍

(1)一般一个系统当中都会有一些系统服务,例如:日志、事务管理、安全等;这些系统服务被称为:交叉业务!

(2)这些交叉业务几乎是通用的,不管你是做银行账户转账,还是删除用户数据。日志、事务管理、安全,这些都是需要做的!

(3)如果在每一个业务处理过程当中,都掺杂这些交叉业务代码进去的话,存在两方面问题:

①第一:交叉业务代码在多个业务流程中反复出现,显然这个交叉业务代码没有得到复用;并且修改这些交叉业务代码的话,需要修改多处。

②第二:程序员无法专注核心业务代码的编写,在编写核心业务代码的同时还需要处理这些交叉业务。

(4)使用AOP可以很轻松的解决以上问题将与业务逻辑无关的、通用的非业务逻辑代码(交叉业务)抽取出来形成一个横向的切面(独立的组件)核心业务是纵向的把横向的切面以横向交叉的方式应用到业务流程的过程就是面向切面!

(5)请看下图,可以帮助你快速理解AOP的思想:

【Spring6】| 面向切面编程AOP(文末书籍一本免费奉上)

用一句话总结AOP:将与核心业务无关的代码独立的抽取出来(交叉业务),形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程被称为AOP;AOP的优点:

①第一:代码复用性增强。

②第二:代码易维护。

③第三:使开发者更关注业务逻辑。

2. AOP的七大术语

我们先看下面这段代码

public class UserService{
    public void do1(){
        System.out.println("do 1");
    }
    public void do2(){
        System.out.println("do 2");
    }
    public void do3(){
        System.out.println("do 3");
    }
    public void do4(){
        System.out.println("do 4");
    }
    public void do5(){
        System.out.println("do 5");
    }
    // 核心业务方法
    public void service(){
       try{
            // Joinpoint连接点
            do1(); // Pointcut切点
            // Joinpoint连接点
            do2(); // Pointcut切点
            // Joinpoint连接点
            do3(); // Pointcut切点
            // Joinpoint连接点
            do5(); // Pointcut切点
            // Joinpoint连接点
        }catch(Exception e){
            // Joinpoint连接点
        }finally{
            // Joinpoint连接点
        }
    }
}

(1)连接点JoinPoint

在程序的整个执行流程中,可以织入切面的位置;方法的执行前后,异常抛出之后等位置

(2)切点Pointcut

在程序执行流程中,真正织入切面的方法。(一个切点对应多个连接点)

(3)通知点Advice

通知又叫增强,就是具体你要织入的代码;通知包括:

①前置通知(在方法的前面)【Before注解】

②后置通知(在方法的后面)【AfterReturing注解】

③环绕通知(方法的前后面都有)【Around注解】

④异常通知(catch语句块当中)【AfterThrowing注解】

⑤最终通知(finally语句块中)【After注解】

(4)切面Aspect

切点+通知就是切面!

(5)织入Weaving

把通知应用到目标对象上的过程。

(6)代理对象Proxy

一个目标对象被织入通知后产生的新对象。

(7)目标对象Target

被织入通知的对象。

通过下图,可以很好的理解AOP的相关术语:

【Spring6】| 面向切面编程AOP(文末书籍一本免费奉上)

总结:

连接点(Joinpoint),描述的是位置(可以织入的位置)。

切点(Pointcut),本质上就是方法(真正织入切面的那个方法就做切点)。 

通知(Advice),通知又叫做增强,就是具体增强的代码,例如:具体的事务代码、日志代码、安全代码、统计时长的代码等;(通知描述的是代码)。

切面(Aspect),(切点+通知就是切面,逻辑概念)!

3. 切点表达式

切点表达式用来定义通知(Advice)往哪些方法上切入!

切入点表达式语法格式:

execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])

(1)访问控制权限修饰符:

①可选项。

没写,就是4个权限都包括。

③写public就表示只包括公开的方法。

(2)返回值类型:

①必填项。

* 表示返回值类型任意。

(3)全限定类名:

①可选项。

包后面两个点“ .. ”代表当前包以及子包下的所有类。

包后面一个点“ .* ”,代表当前包下的所有的类(不包括子包下的类)

省略时表示所有的类。

(4)方法名:

①必填项。

*表示所有方法。

set*表示所有的set方法。

(5)形式参数列表:

①必填项

() 表示没有参数的方法。

(*) 只有一个参数的方法。

(..) 参数类型和个数随意的方法。

(*, String) 第一个参数类型随意,第二个参数是String的。

(6)异常:

①可选项。

省略时表示任意异常类型。

(1)service包下所有的类中以delete开始的所有public修饰的方法

execution(public * com.powernode.mall.service.*.delete*(..))

(2)mall包下(包括子包)所有的类的所有的方法

execution(* com.powernode.mall..*(..))

(3)所有类的所有方法(全限定类名省略就表示所有的类)

execution(* *(..))

4. 使用Spring的AOP

(1)Spring对AOP的实现包括以下3种方式:

①第一种方式:Spring框架结合AspectJ框架实现的AOP,基于注解方式(重点)。

②第二种方式:Spring框架结合AspectJ框架实现的AOP,基于XML方式(了解)。

③第三种方式:Spring框架自己实现的AOP,基于XML配置方式。

(2)实际开发中,都是Spring+AspectJ来实现AOP,所以重点学习第一种和第二种方式。

(3)什么是AspectJ?(Eclipse组织的一个支持AOP的框架,AspectJ框架是独立于Spring框架之外的一个框架,Spring框架用了AspectJ)AspectJ项目起源于帕洛阿尔托(Palo Alto)研究中心(缩写为PARC)。该中心由Xerox集团资助,Gregor Kiczales领导,从1997年开始致力于AspectJ的开发,1998年第一次发布给外部用户,2001年发布1.0 release。为了推动AspectJ技术和社团的发展,PARC在2003年3月正式将AspectJ项目移交给了Eclipse组织,因为AspectJ的发展和受关注程度大大超出了PARC的预期,他们已经无力继续维持它的发展。

4.1 准备工作

注:使用Spring+AspectJ的AOP主要需要引入的三个依赖:spring-context依赖、spring-aop依赖、spring-aspects依赖

其中context依赖和aspects依赖是需要手动引入的,aop依赖会在引入context依赖自动关联上,不需要手动引入!

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>spring6-009-spring-aspectj-aop-annotation</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <!--配置多个仓库-->
    <repositories>
        <!--spring6里程碑的仓库-->
        <repository>
            <id>repository.spring.milestone</id>
            <name>Spring Milestone Repository</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>

    <dependencies>
        <!--spring context依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.0-M2</version>
        </dependency>
        <!--spring aspects依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>6.0.0-M2</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>


    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

</project>

Spring配置文件中添加context命名空间和aop命名空间

<?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>

4.2 基于AspectJ的AOP注解式开发

注解式开发步骤

第一步:目标类(目标对象)

package com.bjpowernode.spring.service;


import org.springframework.stereotype.Service;

@Service("userService") // 纳入Spring管理
public class UserService { // 目标类

    public void login(){ // 目标方法,在这个方法之前加入通知
        System.out.println("系统正在进行身份认证....");
    }

}

第二步:写一个切面类(通知+切点)

切面类是需要引入@Aspect注解进行标注的。

Before注解标注的方法就是一个前置通知,里面写切点表达式

package com.bjpowernode.spring.service;

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

@Component("logAspect") // 纳入Spring管理
@Aspect // 切面类需要这个注解
public class LogAspect { // 切面 = 通知(增强代码)+切点(切点表达式)
    // 这里通知Advice以方法的形式出现(方法中写增强的代码)
    @Before("execution(* com.bjpowernode.spring.service.UserService.*(..))")
    public void beforeAdvice(){
        System.out.println("我是一个通知,一段增强的代码");
    }

}

第三步:在spring配置文件中添加组建扫描和启用自动代理

开启aspectj的自动代理时,spring容器在扫描类的时候,查看该类上是否有@Aspect注解,如果有,则给这个类生成代理对象:

proxy-target-class="true"  表示强制使用CGLIB动态代理
proxy-target-class="false" 这是默认值,表示接口使用JDK动态代理
,反之使用CGLIB动态代理。

<?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">
    <!--组件扫描-->
    <context:component-scan base-package="com.bjpowernode.spring.service"/>
    <!--开启aspectj的自动代理,会查看是否有Aspect注解-->
    <aop:aspectj-autoproxy proxy-target-class="false"/>
</beans>

第四步:测试程序

package com.bjpowernode.spring.test;

import com.bjpowernode.spring.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringAOPTest {

    @Test
    public void testBefore(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.login();
    }
}

执行结果:

【Spring6】| 面向切面编程AOP(文末书籍一本免费奉上)

测试全部的通知类型

通知类型包括:

  • 前置通知:@Before 目标方法执行之前的通知。
  • 后置通知:@AfterReturning 目标方法执行之后的通知。
  • 环绕通知:@Around 目标方法之前添加通知,同时目标方法执行之后添加通知。
  • 异常通知:@AfterThrowing 发生异常之后执行的通知。
  • 最终通知:@After 放在finally语句块中的通知。

第一种情况:没有发生异常的执行顺序

目标类:没有异常

package com.bjpowernode.spring.service;

import org.springframework.stereotype.Component;

// 目标类
@Component
public class OrderService {
    // 目标方法
    public void generate(){
        System.out.println("订单已生成!");
    }
}

切面类:没有异常通知

package com.bjpowernode.spring.service;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component("orderAspect")
@Aspect
public class OrderAspect {
    // 前置通知
    @Before("execution(* com.bjpowernode.spring.service.OrderService.*(..))")
    public void beforeAdvice(){
        System.out.println("前置通知");
    }
    // 后置通知
    @AfterReturning("execution(* com.bjpowernode.spring.service.OrderService.*(..))")
    public void afterReturningAdvice(){
        System.out.println("后置通知");
    }

    // 环绕通知
    @Around("execution(* com.bjpowernode.spring.service.OrderService.*(..))")
    public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 环绕通知前
        System.out.println("环绕通知开始");
        // 执行目标方法
        proceedingJoinPoint.proceed();
        // 环绕通知后
        System.out.println("环绕通知结束");
    }

    // 异常通知(暂时没有异常)
    
    // 最终通知
    @After("execution(* com.bjpowernode.spring.service.OrderService.*(..))")
    public void afterAdvice(){
        System.out.println("最终通知");
    }
}

执行结果:可以得出环绕通知的范围是最大的

注:当没有异常通知;前环绕代码是出现在前置通知前面的,后环绕代码是出现在最终通知最后面的!

【Spring6】| 面向切面编程AOP(文末书籍一本免费奉上)

第二种情况:发生异常的执行顺序

目标类:有异常抛出

package com.bjpowernode.spring.service;

import org.springframework.stereotype.Component;

// 目标类
@Component
public class OrderService {
    // 目标方法
    public void generate(){
        System.out.println("订单已生成!");
        if(1==1){ // 抛出异常
            throw new RuntimeException("恒成立异常");
        }
    }
}

切面类:有异常通知

package com.bjpowernode.spring.service;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component("orderAspect")
@Aspect
public class OrderAspect {
    // 前置通知
    @Before("execution(* com.bjpowernode.spring.service.OrderService.*(..))")
    public void beforeAdvice(){
        System.out.println("前置通知");
    }
    // 后置通知
    @AfterReturning("execution(* com.bjpowernode.spring.service.OrderService.*(..))")
    public void afterReturningAdvice(){
        System.out.println("后置通知");
    }

    // 环绕通知
    @Around("execution(* com.bjpowernode.spring.service.OrderService.*(..))")
    public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 环绕通知前
        System.out.println("环绕通知开始");
        // 执行目标方法
        proceedingJoinPoint.proceed();
        // 环绕通知后
        System.out.println("环绕通知结束");
    }

    // 异常通知
    @AfterThrowing("execution(* com.bjpowernode.spring.service.OrderService.*(..))")
    public void afterThrowingAdvice(){
        System.out.println("异常通知");
    }


    // 最终通知
    @After("execution(* com.bjpowernode.spring.service.OrderService.*(..))")
    public void afterAdvice(){
        System.out.println("最终通知");
    }
}

执行结果:有异常抛出,后置通知和环绕通知结束并没有执行

注:当有异常通知;后置通知和环绕通知结束是没有执行的,异常通知在最终通知前面!

【Spring6】| 面向切面编程AOP(文末书籍一本免费奉上)

切面的先后顺序

我们知道,业务流程当中不一定只有一个切面,可能有的切面控制事务,有的记录日志,有的进行安全控制,如果多个切面的话,顺序如何控制:可以使用@Order注解来标识切面类,为@Order注解的value指定一个整数型的数字,数字越小,优先级越高!

安全类的切面SecurityAspect类,顺序设置为1

package com.bjpowernode.spring.service;

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

@Component("securityAspect")
@Aspect
@Order(1)
public class SecurityAspect {
    @Before("execution(* com.bjpowernode.spring.service.OrderService.*(..))")
    public void beforeAspect(){
        System.out.println("安全的前置通知......");
    }
    
}

日志类的切面OrderAspect类,顺序设置为2

package com.bjpowernode.spring.service;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component("orderAspect")
@Aspect
@Order(2)
public class OrderAspect {
    // 前置通知
    @Before("execution(* com.bjpowernode.spring.service.OrderService.*(..))")
    public void beforeAdvice(){
        System.out.println("前置通知");
    }
    // 后置通知
    @AfterReturning("execution(* com.bjpowernode.spring.service.OrderService.*(..))")
    public void afterReturningAdvice(){
        System.out.println("后置通知");
    }

    // 环绕通知
    @Around("execution(* com.bjpowernode.spring.service.OrderService.*(..))")
    public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 环绕通知前
        System.out.println("环绕通知开始");
        // 执行目标方法
        proceedingJoinPoint.proceed();
        // 环绕通知后
        System.out.println("环绕通知结束");
    }

   
    // 最终通知
    @After("execution(* com.bjpowernode.spring.service.OrderService.*(..))")
    public void afterAdvice(){
        System.out.println("最终通知");
    }
}

执行结果:

【Spring6】| 面向切面编程AOP(文末书籍一本免费奉上)

把安全类的切面SecurityAspect类,顺序修改为3,执行结果:

【Spring6】| 面向切面编程AOP(文末书籍一本免费奉上)

优化使用切点表达式

前面的每个通知都要写上相同的切点表达式,缺点是:

①第一:切点表达式重复写了多次,没有得到复用。

②第二:如果要修改切点表达式,需要修改多处,难维护。

可以这样做:将切点表达式单独的定义出来,在需要的位置引入即可。

①为了醒目方法就定义为中文,方法里面什么都不用写。

②使用@Pointcut注解,注解里面定义上通用的切点表达式

注:下面通知想要引用切点表达式,直接把【方法名()】写进去即可(注意小括号也要带上)!

【Spring6】| 面向切面编程AOP(文末书籍一本免费奉上)

在其他通知中,调用这个切点表达式的方法就可以使用

【Spring6】| 面向切面编程AOP(文末书籍一本免费奉上)

测试结果:

【Spring6】| 面向切面编程AOP(文末书籍一本免费奉上)

注:对于跨类的也可以使用,但是需要写上全限定类名

【Spring6】| 面向切面编程AOP(文末书籍一本免费奉上)

连接点

环绕通知里面的参数就是连接点,除了环绕通知里面必须传一个ProceedingJoinPoint参数的连接点,其它通知也可以传一个JoinPoint参数的连接点

JoinPoint的作用:调用getSignature()方法,得到目标方法的签名,例如以下形式:

void com.zl.spring.service.UserService.login();然后就可以通过方法的签名可以获取到一个方法的具体信息,例如:调用getName方法可以得到目标方法的方法名!

    @Before("通用切点表达式()")
    public void beforeAdvice(JoinPoint joinPoint){
        System.out.println("前置通知");
        // 通过签名,获取到目标方法的方法名
        System.out.println("目标方法的方法名:"+joinPoint.getSignature().getName());
    }

基于全注解开发

我们不在使用xml文件中去配置组件扫描开启aspectj的自动代理,使用一个类声明三个注解去代表xml和xml中的配置,使用全注解的方式开发!

①第一个注解:使用@Configuration注解来表示xml文件

②第二个注解:使用@CompoentSan注解,代表扫描组件,扫描包

③第三个注解:使用@EnableAspectJAutoProxy注解,代表开启自动代理。

注解类

package com.bjpowernode.spring.service;

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

@Configuration // 代替spring.xml文件
@ComponentScan({"com.bjpowernode.spring.service"}) // 组件扫描
@EnableAspectJAutoProxy(proxyTargetClass = true) // 启动aspectj的自动代理机制--使用CGLIB动态代理
public class SpringConfig {
}

测试程序:

注:前面有了@Configuration注解,就表示当前类当做xml配置文件使用,创建AnnotationConfigApplicationContext对象,把带有@Configuration注解的.class类传进去

@Test
public void testNoXML(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
    OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
    orderService.generate();
}

4.3 基于XML配置方式的AOP(了解)

第一步:编写目标类

没有使用@Service注解注入,需要在xml中注入配置进行注入Spring容器管理。

package com.bjpowernode.spring.service;

public class UserService { // 目标对象
    public void logout(){ //目标方法
        System.out.println("系统正在安全退出...");
    }
}

第二步:编写切面类,并且编写通知

①没有使用@Component注解纳入Spring容器管理;也没有使用@Aspect注解来表示这是一个切面类;这些都要在xml文件中进行配置。

②没有在方法上使用@Around注解(里面是切点表达式),表示这是一个环绕通知,也要在xml中进行配置。

package com.bjpowernode.spring.service;

import org.aspectj.lang.ProceedingJoinPoint;

// 负责计时的切面类
public class TimerAspect {// 环绕通知
    public void time(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
       // 通知
        long begin = System.currentTimeMillis();
        //执行目标
        proceedingJoinPoint.proceed();
        // 通知
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }
}

第三步:编写spring.xml配置文件

①先把上面两个类进行注入,纳入Spring容器管理。

②使用aop命名空间,编写切点表达式和切面(在切面中指定那个类、把切点表达式放入那个方法)。

<?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-bcontext.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--纳入spring ioc-->
    <bean id="userService" class="com.bjpowernode.spring.service.UserService"/>
    <bean id="timerAspect" class="com.bjpowernode.spring.service.TimerAspect"/>

    <!--aop的配置-->
    <aop:config>
        <!--切点表达式-->
        <aop:pointcut id="mypointcut" expression="execution(* com.bjpowernode.spring.service..*(..))"/>
        <!--切面:通知+切点-->
        <aop:aspect ref="timerAspect">
            <aop:around method="time" pointcut-ref="mypointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

编写测试程序

package com.bjpowernode.spring.test;

import com.bjpowernode.spring.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringAOPTest {
    @Test
    public void testXML(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.logout();
    }
}

执行结果

【Spring6】| 面向切面编程AOP(文末书籍一本免费奉上)

5. AOP的实际案例:事务处理

需求:项目中的事务控制是在所难免的;在一个业务流程当中,可能需要多条DML语句共同完成,为了保证数据的安全,这多条DML语句要么同时成功,要么同时失败;这就需要添加事务控制的代码。例如以下伪代码:

class 业务类1{
    public void 业务方法1(){
        try{
            // 开启事务
            startTransaction();
            
            // 执行核心业务逻辑
            step1();
            step2();
            step3();
            ....
            
            // 提交事务
            commitTransaction();
        }catch(Exception e){
            // 回滚事务
            rollbackTransaction();
        }
    }
    public void 业务方法2(){
        try{
            // 开启事务
            startTransaction();
            
            // 执行核心业务逻辑
            step1();
            step2();
            step3();
            ....
            
            // 提交事务
            commitTransaction();
        }catch(Exception e){
            // 回滚事务
            rollbackTransaction();
        }
    }
    public void 业务方法3(){
        try{
            // 开启事务
            startTransaction();
            
            // 执行核心业务逻辑
            step1();
            step2();
            step3();
            ....
            
            // 提交事务
            commitTransaction();
        }catch(Exception e){
            // 回滚事务
            rollbackTransaction();
        }
    }
}

class 业务类2{
    public void 业务方法1(){
        try{
            // 开启事务
            startTransaction();
            
            // 执行核心业务逻辑
            step1();
            step2();
            step3();
            ....
            
            // 提交事务
            commitTransaction();
        }catch(Exception e){
            // 回滚事务
            rollbackTransaction();
        }
    }
    public void 业务方法2(){
        try{
            // 开启事务
            startTransaction();
            
            // 执行核心业务逻辑
            step1();
            step2();
            step3();
            ....
            
            // 提交事务
            commitTransaction();
        }catch(Exception e){
            // 回滚事务
            rollbackTransaction();
        }
    }
    public void 业务方法3(){
        try{
            // 开启事务
            startTransaction();
            
            // 执行核心业务逻辑
            step1();
            step2();
            step3();
            ....
            
            // 提交事务
            commitTransaction();
        }catch(Exception e){
            // 回滚事务
            rollbackTransaction();
        }
    }
}
//......

可以看到,这些业务类中的每一个业务方法都是需要控制事务的,而控制事务的代码又是固定的格式,都是:

try{
    // 开启事务
    startTransaction();

    // 执行核心业务逻辑
    //......

    // 提交事务
    commitTransaction();
}catch(Exception e){
    // 回滚事务
    rollbackTransaction();
}

这个控制事务的代码就是和业务逻辑没有关系的“交叉业务”。以上伪代码当中可以看到这些交叉业务的代码没有得到复用,并且如果这些交叉业务代码需要修改,那必然需要修改多处,难维护,怎么解决?可以采用AOP思想解决!可以把以上控制事务的代码作为环绕通知,切入到目标类的方法当中。接下来我们做一下这件事,有两个业务类,如下:

银行账户的业务类AccountService

package com.bjpowernode.spring.service;

import org.springframework.stereotype.Service;

@Service("accountService")
public class AccountService { // 目标对象
    // 转账的业务方法
    public void transfer(){ // 目标方法
        System.out.println("银行账户正在完成转账操作...");
    }
    
    // 取款的业务方法
    public void withdraw(){// 目标方法
        System.out.println("正在取款,请稍后...");
    }

}

订单业务类OrderService

package com.bjpowernode.spring.service;

import org.springframework.stereotype.Service;

@Service("orderService")
public class OrderService { // 目标对象
    
    // 生成订单的业务方法
    public void generate(){// 目标方法
        System.out.println("正在生成订单...");
    }
    
    // 取消订单的业务方法
    public void cancel(){ // 目标方法
        System.out.println("订单已取消...");
    }

}

注意,以上两个业务类已经纳入spring bean的管理,因为都添加了@Service注解。

给以上两个业务类的4个方法添加事务控制代码,使用AOP来完成

编写一个控制事务的切面类,通过环绕通知来控制事务前环绕指定开启事务后环绕指定提交事务catch语句块中指定回滚事物!

注:此时对于异常的处理方式是try...catch,这样才能在try开始事务和提交事务,在catch中回滚事务。

package com.bjpowernode.spring.service;


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

@Component("transactionAspect")
@Aspect
public class TransactionAspect {

    // 编程式事务解决方案
    @Around("execution(* com.bjpowernode.spring.service..*(..))")
    public void aroundAdvice(ProceedingJoinPoint joinPoint){
        try {
            // 前环绕
            System.out.println("开启事务");
            // 执行目标
            joinPoint.proceed();
            // 后环绕
            System.out.println("提交事务");
        } catch (Throwable e) {
            System.out.println("回滚事务");
        }
    }
}

spring.xml配置

注:aop:aspectj-autoproxy不用指定proxy-target-class参数,默认就是false,表示启用JDK的动态代理!

<?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">
    <!--组件扫描-->
    <context:component-scan base-package="com.bjpowernode.spring.service"/>
    <!--启动自动代理-->
    <aop:aspectj-autoproxy/>
</beans>

编写测试

package com.bjpowernode.spring.test;

import com.bjpowernode.spring.service.AccountService;
import com.bjpowernode.spring.service.OrderService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class AOPRealAppTest {

    @Test
    public void testTransaction(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
        accountService.transfer();
        accountService.withdraw();
        orderService.generate();
        orderService.cancel();
    }

}

执行结果:四个方法都会被添加上事务处理 

【Spring6】| 面向切面编程AOP(文末书籍一本免费奉上)

如果转账的时候出现了异常

【Spring6】| 面向切面编程AOP(文末书籍一本免费奉上)

 执行结果:也能正常回滚

【Spring6】| 面向切面编程AOP(文末书籍一本免费奉上)

6. AOP的实际案例:安全日志

需求:项目开发结束了,已经上线了。运行正常。客户提出了新的需求:凡事在系统中进行修改操作的,删除操作的,新增操作的,都要把这个人记录下来。因为这几个操作是属于危险行为。例如有业务类和业务方法:

用户业务类UserService

package com.bjpowernode.spring.biz;

import org.springframework.stereotype.Service;

@Service
public class UserService {

    public void saveUser(){
        System.out.println("新增用户信息");
    }

    public void deleteUser(){
        System.out.println("删除用户信息");
    }

    public void modifyUser(){
        System.out.println("修改用户信息");
    }

    public void getUser(){
        System.out.println("获取用户信息");
    }
}

编写一个负责记录日志的切面类

难点:这里只对增、删、改进行日志的记录,对于查并不记录;所以怎么操作呢?

①使用@Pointcut定义几个切点,把增、删、改使用注解的方式写出来,然后再通过或(||)的方式调用方法即可!

②记录日志信息时,先调用切点JoinPointgetSignature()方法获取到签名;然后在调用getDeclaringTypeName()方法获取到类名、调用getName()方法获取到方法名

package com.bjpowernode.spring.biz;

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

import java.text.SimpleDateFormat;
import java.util.Date;

@Component
@Aspect
public class SecurityLogAspect {
    // 定义切点
    @Pointcut("execution(* com.bjpowernode.spring.biz..save*(..))")
    public void savePointcut(){}
    // 定义切点
    @Pointcut("execution(* com.bjpowernode.spring.biz..delete*(..))")
    public void deletePointcut(){}
    // 定义切点
    @Pointcut("execution(* com.bjpowernode.spring.biz..modify*(..))")
    public void modifyPointcut(){}

    @Before("savePointcut() || deletePointcut() || modifyPointcut()")
    public void beforeAdvice(JoinPoint joinPoint){
        // 系统时间
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String nowTime = sdf.format(new Date());
        // 输出日志信息
        System.out.println(nowTime + " zhangsan : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
    }

}

spring.xml配置:扫描的是biz包

<?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">
    <!--组件扫描-->
    <context:component-scan base-package="com.bjpowernode.spring.service"/>
    <context:component-scan base-package="com.bjpowernode.spring.biz"/>
    <!--启动自动代理-->
    <aop:aspectj-autoproxy/>
</beans>

编写测试

    @Test
    public void testSecurityLog(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.saveUser();
        userService.deleteUser();
        userService.modifyUser();
        userService.getUser();
    }

 执行结果:符合我们的预想,增、删、改有日志信息,查没有日志信息

【Spring6】| 面向切面编程AOP(文末书籍一本免费奉上)文章来源地址https://www.toymoban.com/news/detail-415372.html

到了这里,关于【Spring6】| 面向切面编程AOP(文末书籍一本免费奉上)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 认识 spring AOP (面向切面编程) - springboot

    本篇介绍什么是spring AOP, AOP的优点,使用场景,spring AOP的组成,简单实现AOP 并 了解它的通知;如有错误,请在评论区指正,让我们一起交流,共同进步! 本文开始 AOP: 面向切面编程,也就是面向某一类编程,对某一类事情进行统一处理; spring AOP: 是实现了AOP这种思想的一

    2024年02月14日
    浏览(49)
  • Spring AOP(面向切面编程)和方法拦截

    Spring AOP(面向切面编程)和方法拦截 Spring是一款广泛使用的Java开发框架,提供了丰富的功能和工具,用于简化企业级应用程序的开发。其中一个重要的特性是面向切面编程(AOP)和方法拦截。本文将介绍Spring AOP和方法拦截的概念、工作原理以及在实际开发中的应用。 在软

    2024年02月05日
    浏览(44)
  • 切面的魔力:解密Spring AOP 面向切面编程

    目录 一、AOP简介 1.1 什么是AOP ? 1.2 什么是面向切面编程 ? 1.3 AOP 的特点 二、 AOP的基本概念解读 2.1 AOP的基本概念 2.2 AOP 概念趣事解读 三、代码情景演示 3.1 编写目标对象(超级英雄们正常的行动) 3.2 编写通知类 3.2.1 前置通知 3.2.2 后置通知 3.2.3 异常通知 3.2.4 环绕通知

    2024年02月11日
    浏览(54)
  • JAVA:使用 Spring AOP 实现面向切面编程

    1、简述 在现代的软件开发中,面向切面编程(AOP)是一种重要的编程范式,它允许我们将横切关注点(如日志记录、性能监控、事务管理等)从应用程序的核心业务逻辑中分离出来,以提高代码的模块化和可维护性。Spring 框架提供了强大的 AOP 支持,使得我们可以轻松地实

    2024年04月13日
    浏览(45)
  • 【Spring AOP】结合日志面向切面编程 两种写法

            这里需要提前了解什么是Spring的AOP(Aspect Oriented Programming)。是在OOP(面向对象)思想的一种拓展思想。 简单来说就是将某个代码块嵌入到其它的代码块中 。笔者先前学Spring也有学什么IoC啊AOP啊,但实际上没有用过、就那听过学过没啥用的。。没会儿就忘记了。

    2024年02月13日
    浏览(43)
  • 【Java 初级】Spring核心之面向切面编程(AOP)

    tip:作为程序员一定学习编程之道,一定要对代码的编写有追求,不能实现就完事了。我们应该让自己写的代码更加优雅,即使这会费时费力。 💕💕 推荐: 体系化学习Java(Java面试专题) AOP(面向切面编程)是一种编程范式,用于将横切关注点(如日志记录、性能统计等

    2024年02月04日
    浏览(50)
  • Spring AOP (面向切面编程)原理与代理模式—实例演示

    Spring 中文文档 (springdoc.cn) Spring | Home 官网         Java是一个面向对象(OOP)的语言,但它有一些弊端。虽然使用OOP可以通过组合或继承的方式来实现代码的重用。但当我们需要为多个不具有继承关系的对象(一般指的是两个不同的类,它们之间没有继承自同一个父类或接

    2024年02月15日
    浏览(55)
  • Spring AOP使用指南: 强大的面向切面编程技术

    🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页 ——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文并茂🦖生动形象🐅简单易学!欢迎大家来踩踩~🌺 🌊 《IDEA开发秘籍专栏》 🐾 学会IDEA常用操作,工作效率翻倍~💐 🌊 《100天精通Golang(基础

    2024年02月09日
    浏览(51)
  • 【JavaEE】面向切面编程AOP是什么-Spring AOP框架的基本使用

    【JavaEE】 AOP(1) 1.1 AOP 与 Spring AOP AOP ( A spect O riented P rogramming),是一种思想,即 面向切面编程 Spring AOP 则是一个框架,Spring项目中需要引入依赖而使用 AOP和Spring AOP的关系就相当于IoC和DI Spring AOP让开发者能够半自动的开发AOP思想下实现的功能 1.2 没有AOP的世界是怎样的

    2024年02月11日
    浏览(46)
  • 【Spring】一文带你吃透AOP面向切面编程技术(上篇)

    个人主页: 几分醉意的CSDN博客_传送门 什么是AOP? AOP(Aspect Orient Programming):面向切面编程 Aspect:表示切面,给业务方法增加的功能,叫做切面。切面一般都是非业务功能,而且切面功能一般都是可以复用的。例如日志功能,事务功能,权限检查,参数检查,统计信息等等

    2024年01月16日
    浏览(61)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包