zoukankan      html  css  js  c++  java
  • SpringBoot切面Aop的demo简单讲解

    前言

    本篇文章主要介绍的是SpringBoot切面Aop的demo简单讲解。

    SpringBoot Aop

    说明:如果想直接获取工程那么可以直接跳到底部,通过链接下载工程代码。

    切面(Aop)

    一、概念

    AOP(Aspect OrientedProgramming):面向切面编程,面向切面编程(也叫面向方面编程),是目前软件开发中的一个热点,也是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

    二、用途

    日志记录,性能统计,安全控制,权限管理,事务处理,异常处理,资源池管理。

    三、详解

    1.切面(Aspect):
    官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”,在本例中,“切面”就是类TestAspect所关注的具体行为,例如:AServiceImpl.barA()的调用就是切面TestAspect所关注的行为之一。“切面”在ApplicationContext中aop:aspect来配置。

    2.连接点(Joinpoint):
    程序执行过程中的某一行为,例如,AServiceImpl.barA()的调用或者BServiceImpl.barB(String _msg, int _type)抛出异常等行为。

    3.通知(Advice):
    “切面”对于某个“连接点”所产生的动作,例如,TestAspect中对com.spring.service包下所有类的方法进行日志记录的动作就是一个Advice。其中,一个“切面”可以包含多个“Advice”,例如TestAspect。Advice共有如下5种类型:

    • A 前置通知(Before advice) :在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。xml中在aop:aspect里面使用aop:before元素进行声明;例如,TestAspect中的doBefore方法。注解中使用@Before声明;例如,TestAnnotationAspect中的doBefore方法。
    • B 后通知(After advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。xml中在aop:aspect里面使用aop:after元素进行声明。例如,TestAspect中的doAfter方法,所以AOPTest中调用BServiceImpl.barB抛出异常时,doAfter方法仍然执行。注解中使用@After声明。
    • C 返回后通知(After return advice):在某连接点正常完成后执行的通知,不包括抛出异常的情况。xml中在aop:aspect里面使用元素进行声明。注解中使用@AfterReturning声明;
    • D 环绕通知(Around advice) :包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。xml中在aop:aspect里面使用aop:around元素进行声明。例如,TestAspect中的doAround方法。注解中使用@Around声明。
    • E 抛出异常后通知(After throwing advice) : 在方法抛出异常退出时执行的通知。xml中在aop:aspect里面使用aop:after-throwing元素进行声明。例如,TestAspect中的doThrowing方法。注解中使用@AfterThrowing声明。
    • 通知执行顺序:前置通知→环绕通知连接点之前→连接点执行→环绕通知连接点之后→返回通知→后通知 →(如果发生异常)异常通知→后通知。

    4.切入点(Pointcut)
    匹配连接点的断言,在AOP中通知和一个切入点表达式关联。例如,TestAspect中的所有通知所关注的连接点,都由切入点表达式execution(* com.spring.service..(..))来决定。

    注:以上的理论知识参考:https://www.cnblogs.com/yepei/p/4735298.html

    开发准备

    环境要求

    JDK:1.8

    SpringBoot:2.2.6.RELEASE

    首先还是Maven的相关依赖,基本和普通springboot项目一样,就是多了spring-boot-starter-aop这jar的依赖。

    pom.xml文件如下:

    <properties>
    	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    	<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    	<java.version>1.8</java.version>
    	<maven.compiler.source>1.8</maven.compiler.source>
    	<maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    
    <parent>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-parent</artifactId>
    	<version>2.2.6.RELEASE</version>
    	<relativePath/>
    </parent>
    
    <dependencies>
    	<dependency>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-web</artifactId>
    	</dependency>
    	<dependency>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-test</artifactId>
    		<scope>test</scope>
    	</dependency>
    	<dependency>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-aop</artifactId>
    	</dependency>
    	<dependency>
    		<groupId>com.alibaba</groupId>
    		<artifactId>fastjson</artifactId>
    		<version>1.2.68</version>
    	</dependency>
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
            <scope>test</scope>
    	</dependency>
    </dependencies>
    

    application.properties的文件的配置:

    banner.charset=UTF-8
    server.tomcat.uri-encoding=UTF-8
    spring.http.encoding.charset=UTF-8
    spring.http.encoding.enabled=true
    spring.http.encoding.force=true
    spring.messages.encoding=UTF-8
    spring.application.name=springboot-aspect
    server.port=8180
    

    代码编写

    SpringBoot在使用切面的时候,只需要在自定义的一个切面类中加上 @Aspect 注解进行声明,然后在自定义切面的类方法中加上对应的注解即可。
    比如这里的示例我们想做一个请求响应的加解密切面处理,业务层只需关心代码逻辑实现,而不用关心请求参数和响应参数的加解密实现。那么首先我们需要自定义一个加解密的切面类,在该类添加@Aspect注解,然后在定义一个公共的切入点(Pointcut),指向需要处理的包,然后在定义一个前置通知(添加@Before注解)和后置通知(添加@AfterReturning)方法实现即可。

    这里我们就将切入点设置为控制层包里所有的请求。

    切入点代码示例:

    
    	 	 @Pointcut("execution(public * com.pancm.web.*.*(..))")
      			  public void doOperation() {
       		 }
    
    

    然后在定义一个前置通知,实现对请求参数的数据解密,这里我们就用User这个实体类的名称,对该数据进行解密。实际在运用是可以根据自身的情况来编写。

    前置通知代码示例:

    	@Before("doOperation()")
        public void before(JoinPoint joinPoint) throws Throwable{
            Object[] objs = joinPoint.getArgs();
            for (Object obj : objs) {
                User user =(User) obj;
                System.out.println("前置通知接受的参数:"+user);
                String name =base64DeStr(user.getName());
                user.setName(name);
            }
        }
    
    

    在编写完前置通知的方法之后,我们在编写后置通知的代码,这块基本和前置通知的一样,就是把返回的数据进行加密而已。

    后置通知代码示例:

    
        @AfterReturning(returning = "object", pointcut = "doOperation()")
        public void doAfterReturning(Object object) {
            ResultBody resultBody = (ResultBody) object;
            String str =null;
            try {
                 str=base64EnStr(resultBody.getResult());
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            resultBody.setResult(str);
            System.out.println("后通知响应的参数:"+resultBody);
        }
    
    

    完整代码示例:

    
    @Aspect
    @Component
    public class ParamAspect {
    
        @Pointcut("execution(public * com.pancm.web.*.*(..))")
        public void doOperation() {
    
        }
    
    
        @Before("doOperation()")
        public void before(JoinPoint joinPoint) throws Throwable{
            Object[] objs = joinPoint.getArgs();
            for (Object obj : objs) {
                User user =(User) obj;
                System.out.println("前置通知接受的参数:"+user);
                String name =base64DeStr(user.getName());
                user.setName(name);
            }
        }
    
    
        @AfterReturning(returning = "object", pointcut = "doOperation()")
        public void doAfterReturning(Object object) {
            ResultBody resultBody = (ResultBody) object;
            String str =null;
            try {
                 str=base64EnStr(resultBody.getResult());
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            resultBody.setResult(str);
            System.out.println("前置通知响应的参数:"+resultBody);
        }
    
    
        public  String base64EnStr(String str) throws UnsupportedEncodingException {
            return Base64.getEncoder().encodeToString(str.getBytes("UTF-8"));
        }
    
    
        public static String base64DeStr(String encodeStr) throws UnsupportedEncodingException {
            byte[] decodeStr = Base64.getDecoder().decode(encodeStr);
            return new String(decodeStr, "UTF-8");
        }
    
    
    

    实体类代码

    
    	public class User {
    		
    		 private Long id;
    	
    		 private String name;
    		 
    		 private Integer age;
    		 
    		//getter 和 setter 略
    		
    	}
    
    
    

    控制层z这块的代码和普通的一样,我们这里只需简单的做下处理即可,这里为了方便理解,没有编写service层和dao的代码。:

    ** 控制层代码**

    
    @RestController
    @RequestMapping(value = "/api")
    public class UserRestController {
    
        @GetMapping("/user")
        public ResultBody findByUser(User  user) {
            System.out.println("用户查询接口请求的参数:"+user);
            ResultBody resultBody = new ResultBody();
            List<User> userList =new ArrayList<>();
            User user2=new User();
            user2.setId(1L);
            user2.setName("xuwujing");
            user2.setAge(18);
            userList.add(user2);
            resultBody.setCode("0");
            resultBody.setResult(userList.toString());
            System.out.println("用户查询接口响应的参数:"+resultBody);
            return resultBody;
        }
    }
    
    
    

    App 入口

    和普通的SpringBoot项目基本一样!

    代码如下:

    
    	@SpringBootApplication
    	public class AspectApp
    	{
    	    public static void main( String[] args )
    	    {
    			SpringApplication.run(AspectApp.class, args);
    			System.out.println("Aspect启动成功!");
    	    }
    	}
    
    
    

    功能测试

    编写完代码之后,我们启动程序,因为是Get请求,在浏览器或者使用Postman输入地址都可以进行测试,需要注意的是这里我们需要对name的值进行base64加密请求。

    输入:

    http://localhost:8180/api/user?name=eHV3dWppbmc=

    控制台打印:

    前置通知接受的参数:{"name":"eHV3dWppbmc="}
    用户查询接口请求的参数:{"name":"xuwujing"}
    用户查询接口响应的参数:{"code":"0","result":"[{"age":18,"id":1,"name":"xuwujing"}]"}
    后通知响应的参数:{"code":"0","result":"W3siYWdlIjoxOCwiaWQiOjEsIm5hbWUiOiJ4dXd1amluZyJ9XQ=="}

    请求响应参数:

    {
    "code": "0",
    "message": null,
    "result": "W3siYWdlIjoxOCwiaWQiOjEsIm5hbWUiOiJ4dXd1amluZyJ9XQ=="
    }

    示例图:
    在这里插入图片描述
    在这里插入图片描述

    其它

    关于SpringBoot切面Aop的demo简单讲解的文章就讲解到这里了,如有不妥,欢迎指正!

    项目地址

    SpringBoot 的aop的项目工程地址:
    https://github.com/xuwujing/springBoot-study/tree/master/springboot-aspect

    SpringBoot整个集合的地址:
    https://github.com/xuwujing/springBoot-study

    SpringBoot整合系列的文章

    音乐推荐

    翩若惊鸿,婉若游龙,荣曜秋菊,华茂春松。仿佛兮若轻云之蔽月,飘飘兮若流风之回雪。远而望之,皎若太阳升朝霞;迫而察之,灼若芙蕖出渌波。--网易云网友评论

    原创不易,如果感觉不错,希望给个推荐!您的支持是我写作的最大动力!
    版权声明:
    作者:虚无境
    博客园出处:http://www.cnblogs.com/xuwujing
    CSDN出处:http://blog.csdn.net/qazwsxpcm    
    个人博客出处:http://www.panchengming.com

  • 相关阅读:
    202103226-1 编程作业
    7 20210516-1 团队作业3—需求分析规格说明书
    原型设计作业
    案例分析作业
    202103226-1 编程作业
    阅读——《构建之法》
    第一周学习任务
    牛客app测评报告
    wordcount编程
    准备工作
  • 原文地址:https://www.cnblogs.com/xuwujing/p/12927081.html
Copyright © 2011-2022 走看看