SpringBoot整合AOP+模板

SpringBoot整和AOP+模板

参考网址:

https://www.cnblogs.com/pecool/p/8275400.html

https://mp.weixin.qq.com/s/1R07OKP_-CEPwiwT1rkCkw

http://www.itsoku.com/article/299

参考图片

1.execution表达式

说明:

spring在ssm正常我们都是使用的execution表单式,他比较通用

切入点表达式写法:
  参数包括:execution("修饰符 返回值类型 包.类.方法名(参数..) throws异常")

  修饰符(举例):一般省略

     *       任意

    public     公共访问

  返回值(举例):

    void       无返回值

    String      返回值是字符串类型

    *       返回值任意

  包(举例):      

    com.xx.user.dao    固定包

    com.xx.*.dao          com.xx下的任意包中的dao包

    com.xx.user.dao..  包括dao下所有子包中

  类(举例):

    UserDaoImpl   具体类

    User*         以User开头类

    *User         以User结尾类

    *           任意类

  方法(举例):

    addUser      具体方法

    *        任意方法

    *User      以add结尾方法

    add*       以add开头方法

  参数(无参):

    ()         无参

    (..)       任意参数

    (String,int)     1个String和1int类型的参数

    (int)        1int类型参数

  throws,可省略一般不写

aop的excutiont表达式

2.springboot中advice的执行顺序

因为版本的不同,执行顺序也不同

aop正常执行流程image-20201229165107994

3.常用的几种切入点表达式

指示符作用
bean用于匹配指定bean对象的方法
within用于匹配指定包下所有类内的方法
execution用于按指定语法规则匹配到具体方法
@annotation用于匹配指定注解修饰的方法
  • bean表达式(重点)

bean****表达式一般应用于类级别,实现粗粒度的切入点定义,案例分析:

bean(“userServiceImpl”)指定一个userServiceImpl类中所有方法。

bean("*ServiceImpl")指定所有的后缀为ServiceImpl的类。

说明:bean表达式内部的对象是由spring容器管理的一个bean对象,表达式内部的内部的名字应该是spring容器中某个bean的key。

  • within表达式(了解)

within****表达式应用于类级别,实现粗粒度的切入点表达式定义,案例分析:

within(“aop.service.UserServiceImpl”)指定当前包中这个类内部的所有方法。

within(“aop.service.*”) 指定当前目录下的所有类的所有方法。

within(“aop.service…*”) 指定当前目录以及子目录中类的所有方法。

  • execution表达式(了解)

execution****表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析:

语法:execution(返回值类型 包名.类名.方法名(参数列表))。

execution(void aop.service.UserServiceImpl.addUser())匹配addUser方法。

execution(void aop.service.PersonServiceImpl.addUser(String)) 方法参数必须为String的addUser方法。

execution(* aop.service….(…)) 万能配置。

  • @annotation表达式(重点)

@annotaion****表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析

@annotation(anno.RequiredLog) 匹配有此注解描述的方法。

@annotation(anno.RequiredCache) 匹配有此注解描述的方法。

其中:RequestLog为我们自己定义的注解,当我们使用@RequiredLog注解修饰业务层方法时,系统底层会在执行此方法时进行扩展操作。

编写入门demo

说明:

因为springmvc那一套写aop过程过于繁琐,需要配置xml,索性我就是用springboot直接来写一个aop的实例

基于注解和execution两种表达式

1.新建一个springboot项目

2.导入相关的依赖

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.shaoming</groupId>
    <artifactId>springboot-test-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-test-demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!-- springboot整合aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

重点说明

springboot整合aop需要下面这个依赖就可以

  <!-- springboot整合aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

3.模拟一个业务成方法

package com.shaoming.service;

import com.shaoming.annotation.AopAnnotation;
import org.springframework.stereotype.Service;

/**
 * @Auther: shaoming
 * @Date: 2020/12/31 16:00
 * @Description:
 */
@Service
public class MyService {
    @AopAnnotation(operModul = "测试方法模块",operType = "测试方法类型",operDesc = "测试方法的操作说明")
    public String service(String str){
        //模拟出现异常的情况,除数为0的异常(不需要模拟异常,注释即可)
        int i = 1/0;
        System.out.println("Myservice的Service方法执行业务方法开始执行------------,方法的参数为:\t"+ str);
        return "方法的返回值是方法的参数,这个方法的参数是:\t"+str;
    }

}

4.写一个切面(execution)

说明:

1.我可能写的有点乱,但是抓住要领就行

​ @Befor,@After,@AfterReturn,@AfterThrowing,@Round

2.注意execution表达式的书写


package com.shaoming.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;

import java.lang.reflect.Method;
import java.util.Arrays;

import static java.util.Arrays.*;

/**
 * @Auther: shaoming
 * @Date: 2020/12/31 15:58
 * @Description:
 */
@Order(value = 2)//这个注解表示切面的优先级,数字越小,优先级越大
@Aspect//这个注解表示这个类是一个aop切面
@Component//切面必须交给spring容器管理
public class AopExecution {
    @Pointcut("execution(public String com.shaoming.service.MyService.*(String))")
    public void point(){
        System.out.println("切入点方法的内容不会执行");
    }
    @Before(value = "point()")
    public void doBefore(JoinPoint joinPoint){
         System.out.println("前置通知:"+joinPoint);
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        System.out.println("获取请求的类名(去类名:包名+类名)"+joinPoint.getTarget().getClass().getName());
        System.out.println("获取方法名的全路径(包名+类名+方法名):"+joinPoint.getTarget().getClass().getName() + "." + signature.getMethod().getName().toString());
        System.out.println("获取的方法名称:"+signature.getMethod().getName().toString());
        System.out.println("获取参数的名称"+ asList(signature.getParameterNames()));
        System.out.println("获取参数的类型"+ asList(signature.getParameterTypes()));
        System.out.println("获取方法的返回值的类型:"+signature.getReturnType().toString());
    }
    @After(value = "point()")
    public void doAfter(JoinPoint joinPoint){
        System.out.println("后置通知" + joinPoint);
    }
    @AfterReturning(value = "point()",returning = "res")
    public void doAfterReturning(JoinPoint joinPoint,Object res){
        System.out.println("返回通知" + joinPoint);
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        System.out.println("====返回通知===获取方法名称"+signature.getMethod().getName());
        System.out.println("=====返回通知====获取方法返回值的类型"+signature.getMethod().getReturnType().toString());
        System.out.println("=====返回通知中获取方法的返回值:\t" + res);
    }
    @AfterThrowing(value = "point()",throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint,Exception e){
        System.out.println("异常通知:"+joinPoint);
        System.out.println("======异常通知===异常信息为:"+e.getMessage());
    }
    @Around(value = "point()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint){
        System.out.println("=============环绕通知的操作开始获取一些信息================");
        System.out.println("环绕通知:" + proceedingJoinPoint);
        Object proceed = null;
        try {
           proceed = proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("=======环绕通知=====获取异常信息为:"+throwable.getMessage());
        }
         MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();

        System.out.println("======环绕通知====获取请求的类名(去类名:包名+类名)"+proceedingJoinPoint.getTarget().getClass().getName());
        System.out.println("======环绕通知====获取方法名的全路径(包名+类名+方法名):"+proceedingJoinPoint.getTarget().getClass().getName() + "." + signature.getMethod().getName().toString());
        System.out.println("======环绕通知====获取的方法名称:"+signature.getMethod().getName().toString());
        System.out.println("======环绕通知====获取参数的名称"+ asList(signature.getParameterNames()));
        System.out.println("======环绕通知====获取参数的类型"+ asList(signature.getParameterTypes()));
        System.out.println("======环绕通知====获取方法的返回值的类型:"+signature.getReturnType().toString());
        return proceed;
    }

}

5.再写一个切面(注解方式)

  • 定义一个自定义注解
package com.shaoming.annotation;

import org.springframework.stereotype.Component;

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

/**
 * @Auther: shaoming
 * @Date: 2021/1/1 16:13
 * @Description:
 */
@Target({ElementType.METHOD})//这个注解表示改方法作用在方法上
@Retention(RetentionPolicy.RUNTIME)//这个注解表示该方法在运行时有效
public @interface AopAnnotation {
    String operModul() default "";//操作模块
    String operType()  default "";//操作类型
    String operDesc()  default "";//操作说明
}

  • 写一个切面
package com.shaoming.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * @Auther: shaoming
 * @Date: 2021/1/1 16:33
 * @Description:
 */
@Order(value = 1)//这个注解表示切面的优先级,数字越小,优先级越大
@Aspect//这个注解表示这是一个切面类
@Component//切面类需要交给spring容器管理
public class AopAnnotation {
    @Pointcut("@annotation(com.shaoming.annotation.AopAnnotation)")
    public void point(){
        System.out.println("切入点方法的内容不会被执行");
    }
    @Before(value = "point()")
    public void doBefore(JoinPoint joinPoint){
        System.out.println("前置通知:======(注解方式)" + joinPoint);
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取到方法上的注解
        com.shaoming.annotation.AopAnnotation annotation =
                signature.getMethod().getAnnotation(com.shaoming.annotation.AopAnnotation.class);
        System.out.println("获取到的操作模块:   \t"+annotation.operModul());
        System.out.println("获取到的操作类型:   \t"+annotation.operType());
        System.out.println("获取到的操作说明:   \t"+annotation.operDesc());
        System.out.println("=============注解方式==================");
    }
}

6.定义切面的优先级

说明:

有时候一个方法对应几个切面,当然就有优先级

当然,这种情况比较少

切面的优先级需要借助@Order注解进行描述,数字越小优先级越高,默认优先级比较低。例如:

定义日志切面并指定优先级。



@Order(1)

@Aspect

@Component

public class SysLogAspect {}


定义缓存切面并指定优先级:

@Order(2)

@Aspect

@Component

public class SysCacheAspect {}

说明:当多个切面作用于同一个目标对象方法时,这些切面会构建成一个切面链,类似过滤器链、拦截器链,其执行分析如图e6402139fc8c6ea9c828a6d9172286f

7.写测试方法进行测试

package com.shaoming;

import com.shaoming.service.MyService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

/**
 * @Auther: shaoming
 * @Date: 2020/12/31 16:04
 * @Description:
 */
@SpringBootTest
public class AopTest {
    @Autowired
    private MyService myService;

    @Test
    public void testAop1() {
         String strReturn = myService.service("我是参数的值");
        System.out.println(strReturn);
    }
}

分两种情况进行打印

  • 测试又异常的情况进行打印
前置通知:======(注解方式)execution(String com.shaoming.service.MyService.service(String))
获取到的操作模块:   	测试方法模块
获取到的操作类型:   	测试方法类型
获取到的操作说明:   	测试方法的操作说明
=============注解方式==================
=============环绕通知的操作开始获取一些信息================
环绕通知:execution(String com.shaoming.service.MyService.service(String))
前置通知:execution(String com.shaoming.service.MyService.service(String))
获取请求的类名(去类名:包名+类名)com.shaoming.service.MyService
获取方法名的全路径(包名+类名+方法名):com.shaoming.service.MyService.service
获取的方法名称:service
获取参数的名称[str]
获取参数的类型[class java.lang.String]
获取方法的返回值的类型:class java.lang.String
异常通知:execution(String com.shaoming.service.MyService.service(String))
======异常通知===异常信息为:/ by zero
后置通知execution(String com.shaoming.service.MyService.service(String))
java.lang.ArithmeticException: / by zero
	at com.shaoming.service.MyService.service(MyService.java:15)
	at -------一栈异常信息
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
=======环绕通知=====获取异常信息为:/ by zero
======环绕通知====获取请求的类名(去类名:包名+类名)com.shaoming.service.MyService
======环绕通知====获取方法名的全路径(包名+类名+方法名):com.shaoming.service.MyService.service
======环绕通知====获取的方法名称:service
======环绕通知====获取参数的名称[str]
======环绕通知====获取参数的类型[class java.lang.String]
======环绕通知====获取方法的返回值的类型:class java.lang.String
null
  • 测试又异常的情况进行打印
前置通知:======(注解方式)execution(String com.shaoming.service.MyService.service(String))
获取到的操作模块:   	测试方法模块
获取到的操作类型:   	测试方法类型
获取到的操作说明:   	测试方法的操作说明
=============注解方式==================
=============环绕通知的操作开始获取一些信息================
环绕通知:execution(String com.shaoming.service.MyService.service(String))
前置通知:execution(String com.shaoming.service.MyService.service(String))
获取请求的类名(去类名:包名+类名)com.shaoming.service.MyService
获取方法名的全路径(包名+类名+方法名):com.shaoming.service.MyService.service
获取的方法名称:service
获取参数的名称[str]
获取参数的类型[class java.lang.String]
获取方法的返回值的类型:class java.lang.String
Myservice的Service方法执行业务方法开始执行------------,方法的参数为:	我是参数的值
返回通知execution(String com.shaoming.service.MyService.service(String))
====返回通知===获取方法名称service
=====返回通知====获取方法返回值的类型class java.lang.String
=====返回通知中获取方法的返回值:	方法的返回值是方法的参数,这个方法的参数是:	我是参数的值
后置通知execution(String com.shaoming.service.MyService.service(String))
======环绕通知====获取请求的类名(去类名:包名+类名)com.shaoming.service.MyService
======环绕通知====获取方法名的全路径(包名+类名+方法名):com.shaoming.service.MyService.service
======环绕通知====获取的方法名称:service
======环绕通知====获取参数的名称[str]
======环绕通知====获取参数的类型[class java.lang.String]
======环绕通知====获取方法的返回值的类型:class java.lang.String
方法的返回值是方法的参数,这个方法的参数是:	我是参数的值

8.小总结(踩坑记录)

其他方法和环绕通知混用时候,环绕方法的返回值一定要写

还有声明的执行目标方法(proceed = proceedingJoinPoint.proceed()😉

反正参照我以上的方法就可以,否者会出现问题

否则别的通知将会不生效,只有环绕通知(@Aroud)生效

总结

spring的两大核心(IOC和AOP)一定要灵活运用

AOP要重视起来,因为springboot不少实现都是基于aop的

个人csdn博客网址:https://blog.csdn.net/shaoming314

jam

个人博客网址:www.shaoming.club

oming.service.MyService.service
==环绕通知获取的方法名称:service
==环绕通知获取参数的名称[str]
==环绕通知获取参数的类型[class java.lang.String]
==环绕通知获取方法的返回值的类型:class java.lang.String
方法的返回值是方法的参数,这个方法的参数是: 我是参数的值


### 8.小总结(踩坑记录)

> 其他方法和环绕通知混用时候,环绕方法的返回值一定要写
>
> 还有声明的执行目标方法(proceed = proceedingJoinPoint.proceed();)
>
> 反正参照我以上的方法就可以,否者会出现问题
>
> 否则别的通知将会不生效,只有环绕通知(@Aroud)生效



## 总结

`spring的两大核心(IOC和AOP)一定要灵活运用`

`AOP要重视起来,因为springboot不少实现都是基于aop的`



个人csdn博客网址:https://blog.csdn.net/shaoming314

[外链图片转存中...(img-h7YdQPgJ-1609493237876)]

个人博客网址:www.shaoming.club



![halo](https://img-blog.csdnimg.cn/img_convert/c2c1dd5b4b71e544d16229a48274dce9.png)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值