欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

小白的springboot之路(十四)、AOP

程序员文章站 2023-11-02 15:18:10
0、前言 1、什么是AOP AOP(面向切面编程),是一种横切技术,是对OOP的补充和完善; 使用AOP的横切,可以对系统进行无侵入性的日志监听、事务、权限管理等; 思想上跟拦截器其实类似;拦截器是对action进行拦截处理,AOP是对切面进行拦截处理,其实切面也属于一种action集合; AOP可 ......

0、前言

1、什么是aop

  aop(面向切面编程),是一种横切技术,是对oop的补充和完善;

  使用aop的横切,可以对系统进行无侵入性的日志监听、事务、权限管理等;

  思想上跟拦截器其实类似;拦截器是对action进行拦截处理,aop是对切面进行拦截处理,其实切面也属于一种action集合;

  aop可以很好解耦;

2、aop的组成

  aspect:切面;

  join point:连接点;

  advice:通知,在切入点上执行的操作;

  poincut:带有通知的连接点;

  target:被通知的对象;

  aop proxy;aop代理;

其中,advice(通知)分为以下几种:

  • before(前置通知): 在方法开始执行前执行
  • after(后置通知): 在方法执行后执行
  • afterreturning(返回后通知): 在方法返回后执行
  • afterthrowing(异常通知): 在抛出异常时执行
  • around(环绕通知): 在方法执行前和执行后都会执行
通知的执行顺序:
around > before > around > after > afterreturning

 

一、实现示例

  光看理论和定义,很多人可能都觉得很难理解,其实用法比较简单,不难的,

  我们先来个简单的例子,看完例子你可能就豁然开朗了,

  所谓程序员,好看书不如多动手:

  实现:

1、添加依赖

        <!-- 8、集成aop  -->
        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-aop</artifactid>
        </dependency>

2、添加切面类logaspect

package com.anson.common.aspect;

import org.aspectj.lang.joinpoint;
import org.aspectj.lang.proceedingjoinpoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.logger;
import org.slf4j.loggerfactory;
import org.springframework.stereotype.component;
import org.springframework.web.context.request.requestcontextholder;
import org.springframework.web.context.request.servletrequestattributes;

import javax.servlet.http.httpservletrequest;
import java.util.arrays;

/**
 * @description: aop切面
 * @author: anson
 * @date: 2019/12/20 10:11
 */

@aspect //1、添加aop相关注解
@component
public class logaspect
{
    private static final logger logger = loggerfactory.getlogger(logaspect.class);

    //2、定义切入点(可以匹配、注解的方式,可混用)
//    @pointcut("execution(public * com.anson.controller.*.*(..))")
@pointcut("execution(public * com.anson.controller.*.*(..)) && @annotation(com.anson.common.annotation.logannotation)")
  // @pointcut("execution(public * com.anson.controller.testcontroller.get*()) && @annotation(com.anson.common.annotation.logannotation)")
    public void pointcut(){}

    //===========通知 多中通知可根据需要灵活选用,一般before 、afterreturning即可=======================
    /**
     * 前置通知:在连接点之前执行的通知
     * @param joinpoint
     * @throws throwable
     */
    @before("pointcut()")
    public void dobefore(joinpoint joinpoint) throws throwable {
        // 接收到请求,记录请求内容
        servletrequestattributes attributes = (servletrequestattributes) requestcontextholder.getrequestattributes();
        httpservletrequest request = attributes.getrequest();

        // 记录下请求内容
        logger.info("url : " + request.getrequesturl().tostring());
        logger.info("http_method : " + request.getmethod());
        logger.info("ip : " + request.getremoteaddr());
        logger.info("class_method : " + joinpoint.getsignature().getdeclaringtypename() + "." + joinpoint.getsignature().getname());
        logger.info("args : " + arrays.tostring(joinpoint.getargs()));
    }

    @afterreturning(returning = "ret",pointcut = "pointcut()")
    public void doafterreturning(object ret) throws throwable {
        // 处理完请求,返回内容
        logger.info("response : " + ret);
    }
//==============================================
    @after("pointcut()")
    public void commit() {
        logger.info("after commit");
    }

    @afterthrowing("pointcut()")
    public void afterthrowing() {
        logger.info("afterthrowing afterthrowing  rollback");
    }

    @around("pointcut()")
    public object around(proceedingjoinpoint joinpoint) throws throwable {
        try {
            system.out.println("around");
            return joinpoint.proceed();
        } catch (throwable e) {
            e.printstacktrace();
            throw e;
        } finally {
            logger.info("around");
        }
    }

}

 

需要注意的是:上面代码注释2的地方【2、定义切入点(可以匹配、注解的方式,可混用)】;

简单点说就是通过匹配或者注解(也可两种同时使用)匹配哪些类的哪些方法;

再直白点说,就是我们要对哪些类的哪些方法执行处理,要处理的范围是哪些;

类用*作为通配符,方法用(..)表示,括号中的两点表示匹配任何参数,包括没有参数;

 

上面这样其实就可以完成一个切面了,

 

比如将切面定义那里改为:@pointcut("execution(public * com.anson.controller.*.*(..))")
那么运行程序后,所有com.anson.controller包下的所有类的所有方法,都会执行这个切面定义的方法进行相关写日志处理了,结果如下:
around
[2019-12-20 11:19:02,205][info ][http-nio-8095-exec-1] url : http://localhost:8095/user/userall (logaspect.java:45)
[2019-12-20 11:19:02,205][info ][http-nio-8095-exec-1] http_method : get (logaspect.java:46)
[2019-12-20 11:19:02,206][info ][http-nio-8095-exec-1] ip : 0:0:0:0:0:0:0:1 (logaspect.java:47)
[2019-12-20 11:19:02,207][info ][http-nio-8095-exec-1] class_method : com.anson.controller.usercontroller.getuserall (logaspect.java:48)
[2019-12-20 11:19:02,208][info ][http-nio-8095-exec-1] args : [] (logaspect.java:49)
[2019-12-20 11:19:02,510][debug][http-nio-8095-exec-1] ==>  preparing: select id, username, password, realname from user  (basejdbclogger.java:143)
[2019-12-20 11:19:02,606][debug][http-nio-8095-exec-1] ==> parameters:  (basejdbclogger.java:143)
[2019-12-20 11:19:02,631][debug][http-nio-8095-exec-1] <==      total: 4 (basejdbclogger.java:143)
[2019-12-20 11:19:02,634][info ][http-nio-8095-exec-1] around (logaspect.java:77)
[2019-12-20 11:19:02,635][info ][http-nio-8095-exec-1] after commit (logaspect.java:60)
[2019-12-20 11:19:02,635][info ][http-nio-8095-exec-1] response : com.anson.common.result.resultbody@6d9947d (logaspect.java:55)
如果不用注解的话,上面就已经完成一个切面了,如果用注解来定义切面范围呢,好,也简单,我们来定义一个注解

--------------华丽丽的分割线-------------------------------
--------------增加自定义注解的方式----------------------------

3、添加一个logannotation注解
package com.anson.common.annotation;

import java.lang.annotation.*;

/**
 * @description: 自定义注解
 * @author: anson
 * @date: 2019/12/20 10:32
 */

@documented
@retention(retentionpolicy.runtime)
@target(elementtype.method)
public @interface logannotation 
{ }

这样就可以了,然后,在需要的地方,加入这个自定义注解:

    //2、获取所有用户
    @apioperation(value = "获取所有用户", notes = "获取所有用户")
    @requestmapping(value="/userall",method= requestmethod.get)
    @logannotation //自定义注解
    public resultbody getuserall()
    {
        list<user> users = userservice.getall();
        return  resultbody.success(users,"获取所有用户信息成功");
    }

 

同时,修改切面范围的定义即可:

//2、定义切入点(可以匹配、注解的方式,可混用)--以下表示范围为:controller包下所有包含@logannotation注解的方法
 @pointcut("execution(public * com.anson.controller.*.*(..)) && @annotation(com.anson.common.annotation.logannotation)") 

public void pointcut(){}

完了,就这么简单;

至于什么地方该使用aop,以及aop和拦截器用哪个比较好,这个就要根据业务场景灵活取舍了,掌握了思想,具体使用那就可以灵活发挥了