Spring AOP 代码示例

AOP(Aspect Oriented Programming)称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等。其实说起aop大家都对其有一定的概念。今天主要是总结一下他代码中的实现方式,以方便后边查看使用。

说起AOP,首先我们需要先了解其中的几个概念:

1.通知(Advice)

   AOP在特定的切入点上执行的增强处理,就是你想要的功能,也就是上面说的日志、事务、权限等。有before(前置),after(后置),afterReturning(最终),afterThrowing(异常),around(环绕),就是在上面这几中类型所实现的功能。比如在使用redis操作前打印日志。

2.连接点(JoinPoint)

  就是spring允许你使用通知的地方,例如每个方法的前,后(两者都有也行),或抛出异常时,也就是说和方法有关的前后(抛出异常),都是连接点。Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器,如aspectJ。

3.切入点(Pointcut)

  就是带有通知的连接点,在连接点的基础上来定义切入点,假如一个类里面有5个方法,那就有5个连接点,但是并不想在所有方法上都实现切面的功能,只是想让其中的几个方法在调用之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。在程序中主要体现为书写切入点表达式。

4.切面(Aspect)

  通常是一个类,里面可以定义切入点和通知。通知说明了干什么和什么时候干(之前,之后,还是异常),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。

5.引入(introduction)

  在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。就是把我们定义的功能用到目标类中。

6.目标(target)

  包含连接点的对象。也被称作被通知或被代理对象。就是上面引入中所提到的目标类。

7.代理(proxy)

   AOP框架创建的对象,代理就是目标对象的加强版本。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类。

8.织入(weaving)

   把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时。

下面我们用注解的方式实现下AOP的功能。
增加AOP所需要的依赖包
        <!--AOP依赖包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

定义一个切面类。

package com.example.demo.aop;

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

@Component //让spring能够扫描到
@Aspect //定义这是一个切面类
public class LogAspect {

    /**
     * modifier-pattern:用于匹配public、private等访问修饰符
     * ret-type-pattern:用于匹配返回值类型,不可省略
     * declaring-type-pattern:用于匹配包类型
     * modifier-pattern(param-pattern):用于匹配类中的方法,不可省略
     * throws-pattern:用于匹配抛出异常的方法
     *
     *
     * 多个表达式之间使用连接符匹配多个条件, 如使用||表示“或”,使用 &&表示“且”
     */
    @Pointcut("@annotation(com.example.demo.annotation.LogAop) &&" +
            "execution(public * com.example.demo.service.impl.AopTestServiceImpl.get*(..))")
    public void log(){

    }

    /**
     * 匹配com.example.demo.service.impl包下所有类下的方法名以update结尾、参数类型不限的public方法
     */
    @Pointcut("execution(public * com.example.demo.service.impl.*.*update(..))")
    public void say(){

    }

    @Order(2)
    @Before("log()")
    public void beforeLog2(){
        System.out.println("后执行,增加log()方法...");
    }

    @Order(1) // Order 代表优先级,数字越小优先级越高
    @Before("log()||say()") //多个的话用 @Before("log()||say()")
    public void beforeLog1(){
        System.out.println("先执行,增加log()方法...");
    }

    /**
     * Advice注解一共有五种,分别是:
     * 1.@Before前置通知
     * 前置通知在切入点运行前执行,不会影响切入点的逻辑
     * 2.@After后置通知
     * 后置通知在切入点正常运行结束后执行,如果切入点抛出异常,则在抛出异常前执行
     * 3.@AfterThrowing异常通知
     * 异常通知在切入点抛出异常前执行,如果切入点正常运行(未抛出异常),则不执行
     * 4.@AfterReturning返回通知
     * 返回通知在切入点正常运行结束后执行,如果切入点抛出异常,则不执行
     * 5.@Around环绕通知
     * 环绕通知是功能最强大的通知,可以在切入点执行前后自定义一些操作。环绕通知需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行
     */
    @Before("say()")
    public void beforeSay(){
        System.out.println("增加say()方法...");
    }
}

此类就是一个切面,其中一共定义了两个切入点,一个是log(),一个是say()。在满足切入点要求的方法前会做三中类型的通知,也就是beforeLog21(),beforeLog2(),beforeSay()。为什么是方法前呢,因为用的注解是@Before。其他具体的信息,可以看代码里面的注释。里面用到了一个我们自定义的注解,其代码如下:

package com.example.demo.annotation;

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

@Retention(RetentionPolicy.CLASS)
public @interface LogAop {

    /**
     * 使用@annotation、@within、@target、@args匹配注解
     * //匹配标注有LogAop注解的方法
     * @Pointcut("@annotation(com.example.demo.annotation.LogAop)")
     * public void matchAnno() {}
     *
     * //匹配标注有LogAop的类底下的方法,要求annotation的Retention级别为CLASS
     * @Pointcut("@within(com.example.demo.annotation.LogAop)")
     * public void matchWithin() {}
     *
     * //匹配标注有LogAop的类底下的方法,要求annotation的Retention级别为RUNTIME
     * @Pointcut("@target(com.example.demo.annotation.LogAop)")
     * public void matchTarget() {}
     *
     * //匹配传入的参数类标注有LogAop注解的方法
     * @Pointcut("@args(com.example.demo.annotation.LogAop)")
     * public void matchArgs() {}
     */
}

然后就是我们的业务处理代码,也就是目标对象。

package com.example.demo.service.impl;

import com.example.demo.annotation.LogAop;
import com.example.demo.model.Student;
import com.example.demo.service.GetStudentService;
import org.springframework.stereotype.Service;

/**
* @Description: AOP测试
* @Author:      haoqiangwang3
* @CreateDate:  2020/1/13
*/
@Service
public class AopTestServiceImpl implements GetStudentService {

    @LogAop
    @Override
    public Student getStudentInfo() {
        System.out.println("调用业务处理中的get()方法...");
        return null;
    }

    @Override
    public int update() {
        System.out.println("调用业务处理中的update()方法...");
        return 0;
    }
}

再就是程序的入口,controlle方法

package com.example.demo.controller;

import com.example.demo.service.impl.AopTestServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @Description: AOP测试controller
* @Author:      haoqiangwang3
* @CreateDate:  2020/1/13
*/
@RestController
public class AopController {
    @Autowired
    private AopTestServiceImpl aopTestServiceImpl;

    @RequestMapping("/boot/getAop")
    public String aopGet(){
        aopTestServiceImpl.getStudentInfo();
        return "success";
    }

    @RequestMapping("/boot/updateAop")
    public String aopUpdate(){
        aopTestServiceImpl.update();
        return "success";
    }
}

上面的代码是在我之前学习spring boot程序的基础上增加的。以上完成后,就可以测试我们的aop功能了,测试结果就不粘贴了,亲测是可以生效的。

posted @ 2020-01-13 14:36  wanghq1994  阅读(2660)  评论(0编辑  收藏  举报