其他分享
首页 > 其他分享> > 论 Spring+SpringBoot+Aop 以及应用场景

论 Spring+SpringBoot+Aop 以及应用场景

作者:互联网

众所周知,spring最核心的两个功能是aop和ioc,即面向切面,控制反转。这里我们探讨一下如何使用spring aop。

1.何为aop

  aop全称Aspect Oriented Programming,面向切面,AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。其与设计模式完成的任务差不多,是提供另一种角度来思考程序的结构,来弥补面向对象编程的不足。

  通俗点讲就是提供一个为一个业务实现提供切面注入的机制,通过这种方式,在业务运行中将定义好的切面通过切入点绑定到业务中,以实现将一些特殊的逻辑绑定到此业务中。

  比如,若是需要一个记录日志的功能,首先想到的是在方法中通过log4j或其他框架来进行记录日志,但写下来发现一个问题,在整个业务中其实核心的业务代码并没有多少,都是一些记录日志或其他辅助性的一些代码。而且很多业务有需要相同的功能,比如都需要记录日志,这时候又需要将这些记录日志的功能复制一遍,即使是封装成框架,也是需要调用之类的。在此处使用复杂的设计模式又得不偿失。

  所以就需要面向切面出场了。

3.搭建aop

  本来spring就自带一套aop实现,我们直接使用此实现即可,本来使用aop还需要定义一些xml文件,但由于我们使用的是spring-boot框架,这一步就省略掉了。也就是说,在spring-boot中,我们可以直接使用aop而不需要任何的配置

  具体如何搭建spring-boot请参考:http://www.cnblogs.com/lic309/p/4073307.html

4.aop名称

  先介绍一些aop的名词,其实这些名词对使用aop没什么影响,但为了更好的理解最好知道一些

       其中重要的名词有:切面,切入点

 5.简单例子:

 maven:

<?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>com.xncoding</groupId>
    <artifactId>springboot-aop</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>springboot-aop</name>
    <description>SpringBoot AOP演示</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.6.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.20</version>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

    可能直接说会很模糊,这里我先做了一个小例子:直接上代码

 



/**
 * 日志切面
 */
@Aspect
@Component
public class LogAspect {
    @Pointcut("execution(public * com.xncoding.aop.controller.*.*(..))")
    public void webLog(){}

    @Before("webLog()")
    public void deBefore(JoinPoint joinPoint) throws Throwable {
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 记录下请求内容
        System.out.println("URL : " + request.getRequestURL().toString());
        System.out.println("HTTP_METHOD : " + request.getMethod());
        System.out.println("IP : " + request.getRemoteAddr());
        System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));

    }

    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) throws Throwable {
        // 处理完请求,返回内容
        System.out.println("方法的返回值 : " + ret);
    }

    //后置异常通知
    @AfterThrowing("webLog()")
    public void throwss(JoinPoint jp){
        System.out.println("方法异常时执行.....");
    }

    //后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
    @After("webLog()")
    public void after(JoinPoint jp){
        System.out.println("方法最后执行.....");
    }

    //环绕通知,环绕增强,相当于MethodInterceptor
    @Around("webLog()")
    public Object arround(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("方法环绕start.....");
        try {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            Object obj = pjp.proceed(pjp.getArgs());
            stopWatch.stop();
            long cost = stopWatch.getTotalTimeMillis();
            MethodSignature signature = (MethodSignature) pjp.getSignature();
            String methodName = signature.getDeclaringTypeName() + "." + signature.getName();
            System.out.println("----------- 执行" + methodName + "方法, 用时: " + cost + "ms -----------");
            System.out.println("方法环绕proceed,结果是 :" + obj);
            return obj;
        } catch (Throwable e) {
            throw e;
        }
    }
}

看上面的例子就是实现了一个切面,其中有一些特殊的东西,下面一一解释:

6.使用的注解:

   @Aspect:描述一个切面类,定义切面类的时候需要打上这个注解

   1. @Pointcut:声明一个切入点,切入点决定了连接点关注的内容,使得我们可以控制通知什么时候执行。Spring AOP只支持Spring bean的方法执行连接点。所以你可以把切入点看做是Spring bean上方法执行的匹配。一个切入点声明有两个部分:一个包含名字和任意参数的签名,还有一个切入点表达式,该表达式决定了我们关注那个方法的执行。

  注:作为切入点签名的方法必须返回void 类型

  Spring AOP支持在切入点表达式中使用如下的切入点指示符:    

         其中execution使用最频繁,即某方法执行时进行切入。定义切入点中有一个重要的知识,即切入点表达式,我们一会在解释怎么写切入点表达式。

   切入点意思就是在什么时候切入什么方法,定义一个切入点就相当于定义了一个“变量”,具体什么时间使用这个变量就需要一个通知。

   即将切面与目标对象连接起来。

  如例子中所示,通知均可以通过注解进行定义,注解中的参数为切入点。

  spring aop支持的通知:

  @Before:前置通知:在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。

  @AfterReturning :后置通知:在某连接点正常完成后执行的通知,通常在一个匹配的方法返回的时候执行。

       @AfterThrowing:异常通知:在方法抛出异常退出时执行的通知。       

    @After 最终通知。当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。

    @Around:环绕通知:包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。

      环绕通知最麻烦,也最强大,其是一个对方法的环绕,具体方法会通过代理传递到切面中去,切面中可选择执行方法与否,执行方法几次等。

      环绕通知使用一个代理ProceedingJoinPoint类型的对象来管理目标对象,所以此通知的第一个参数必须是ProceedingJoinPoint类型,在通知体内,调用ProceedingJoinPoint的proceed()方法会导致后台的连接点方法执行。proceed 方法也可能会被调用并且传入一个Object[]对象-该数组中的值将被作为方法执行时的参数。

 

下面写上测试列子:


/**
 * Description:
 */
@RestController
public class UserController {
    @RequestMapping("/first")
    public Object first() {
        return "first controller";
    }

    @RequestMapping("/doError")
    public Object error() {
        return 1 / 0;
    }
}

返回:

方法环绕start.....
URL : http://localhost:8092/second
HTTP_METHOD : POST
IP : 0:0:0:0:0:0:0:1
CLASS_METHOD : com.xncoding.aop.controller.UserController.second
ARGS : []
second around:老铁们来咯
second before
----------- 执行com.xncoding.aop.controller.UserController.second方法, 用时: 6ms -----------
方法环绕proceed,结果是 :second controller
方法最后执行.....
方法的返回值 : second controller
 

 

接下来介绍自定义式注解式日志

 

直接上代码:

 1:首先定义一个注解

  


@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAccess {
    String desc() default "无信息";
}

2:定义一个切面类



@Component
@Aspect
public class UserAccessAspect {

    @Pointcut(value = "@annotation(com.xncoding.aop.aspect.UserAccess)")
    public void access() {

    }

    @Before("access()")
    public void deBefore(JoinPoint joinPoint) throws Throwable {
        System.out.println("second before");
    }

    @Around("@annotation(userAccess)")
    public Object around(ProceedingJoinPoint pjp, UserAccess userAccess) {
        //获取注解里的值
        System.out.println("second around:" + userAccess.desc());
        try {
            return pjp.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            return null;
        }
    }
}

3:写一个接口测试

@RestController
public class UserController {
   
	@RequestMapping("/second")
    @UserAccess(desc = "老铁们来咯")
    public Object second() {
        return "second controller";
    }

}

返回:

方法环绕start.....
URL : http://localhost:8092/second
HTTP_METHOD : POST
IP : 0:0:0:0:0:0:0:1
CLASS_METHOD : com.xncoding.aop.controller.UserController.second
ARGS : []
second around:老铁们来咯
second before
方法环绕proceed,结果是 :second controller
方法最后执行.....
方法的返回值 : second controller

 

我们一般使用AOP主要来做:

     

场景一: 记录日志

场景二: 监控方法运行时间 (监控性能)

场景三: 权限控制

场景四: 缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 )

场景五: 事务管理 (调用方法前开启事务, 调用方法后提交关闭事务 )

 

标签:SpringBoot,aop,连接点,Spring,切入点,Aop,AOP,方法
来源: https://blog.csdn.net/xyzx123456/article/details/117258509