Spring个人学习笔记(三)——Spring AOP

首先再说AOP之前,可以先去了解一些代理模式,Spring的AOP功能就是基于JDK动态代理和Cglib代理实现的,关于代理模式可以去看我的这篇文章——代理模式

简介

AOP,简单的来说就是面向切面编程,它的全称是Aspect Oriented Programming,它能够将我们的业务逻辑和横切的问题进行分离(横切问题和我们业务逻辑关系不大),达到解耦的目的,使代码的重用性和开发效率提高。

图片描述

通过上面这张图,AOP是环绕在我们业务层代码外的一个模块,AOP中也细分了很多部件,我们常说的切点,切面等都是它内部的组成部分。

AOP组成部分

img

  • 切面( Aspect):它其实就是一个类,也就是我们切面需要完成的功能,我们可以通过一个注解声明一个切面类。
  • 通知(Advice):通知有五种:前置通知、后置通知、环绕通知、异常抛出通知和返回通知,这里就将它们理解为切面里的方法,也就是说我们定义的这个代理类需要做什么,什么时候去做。
  • 切入点(Pointcut):对连接点进行过滤,匹配出需要执行的连接点(Joint point),在这些连接点上织入通知(Adice),通俗的讲就是对符合条件的连接点进行代理。
  • 连接点(Joint point):可以理解为我们需要代理的目标类中所有可以能的方法
  • 目标对象(Target):也就是需要被代理的对象

通知(Advice) 的类型

  • 前置通知(before):在 join point 前被执行的 advice
  • 后置通知(after):在一个 join point 正常返回后执行的 advice
  • 环绕通知(around):在 join point 前和 joint point 后都执行的 advice
  • 异常抛出通知(after throwing advice):当一个 join point 抛出异常后执行的 advice
  • 返回通知(after(final) advice):无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice

AOP一般应用于:日志,事务,权限,缓存(session)等

AOP使用示例

对于AOP的使用方法,我这边详细介绍下通过注解的方式,它还有通过实现官方接口或者自定义切面类的方式来实现,但是在实际开发过程中,推荐使用注解方式。

简单示例

切面类:

package com.example.demo.spring.aop.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author: sunzhinan
 * @create: 2020-08-09 00:57
 * @description: 通过注解方式实现AOP--这里主要讲述他们的简单应用
 */

//标记切面类的处理优先级,值越小,优先级别越高;
//可以注解类,也能注解到方法上
@Order(1)
@Aspect
@Slf4j
//这里有个坑,spring boot扫描不会自动注入@Aspect这个注解的类,所有需要手动加上,
//如果不加就不会进行代理,因为@Aspect只会代理IOC容器内的对象
@Component
public class TestLogAspect {

    //通过切入点:可以通过|| && 方式进行组合
//    @Pointcut("execution(* com.example.demo.spring.aop.service..*.*(..)) " + " || execution(* com.example.demo.spring.aop.service.TestService.query())")
    @Pointcut("execution(* com.example.demo.spring.aop.controller.TestController.*(..))")
    private void beforePointCut(){}

    @Pointcut("execution(* com.example.demo.spring.aop.service..*.*(..))")
    private void afterPointCut(){}

    @Pointcut("execution(* com.example.demo.spring.aop.service.TestService.*(..))")
    private void aroundPointCut(){}


    /**
     * 对于通知Advice也可以自己定义切入点规则,不需要通过自定义切入点
     */
//    @Before("execution(* com.example.demo.spring.aop.controller.TestController.*(..))")
//    @Before("execution(* com.example.demo.spring.aop.service..*.*(..))")
    @Before(value = "beforePointCut()")
    public void logBefore(){
        log.info("方法调用前打印日志");
    }

    /**
     * 最终通知
     */
    @After(value = "afterPointCut()")
    public void logAfter(){
        log.info("方法调用后打印日志");
    }

    /**
     *  aroud和其他的有点区别,他需要在内部调用被代理类的方法
     * @param joinPoint
     * @return
     */
//    @Around(value = "aroundPointCut()")
    @Around(value = "execution(* com.example.demo.spring.aop.controller.TestController.*(..))")
    public Object logAround(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
        System.out.println("----Around----");

        //获得目标对象的class
        Class<?> targetClass = joinPoint.getTarget().getClass();

        //获得方法参数类型
        Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
        //获得目标方法的入参
        Object[] args = joinPoint.getArgs();

        //获得目标对象的方法名字
        String methodName = joinPoint.getSignature().getName();
        Method method = targetClass.getMethod(methodName, parameterTypes);  //获取目标方法


        log.info("我调用的方法名字是 : "+ methodName);
        log.info("method is : " + method);
        for (int i = 0; i < args.length; i++) {
            log.info("方法的参数是 : "+args[i]);
        }
        try {
            //执行目标类方法:这个是核心的
            Object result = joinPoint.proceed();

            //获得方法的执行结果
            log.info("result: " + result);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        return "Around";
    }

    /**
     * AfterReturning(后置通知) 会在 After之后执行
     */
    @AfterReturning(pointcut = "afterPointCut() || beforePointCut()")
    public void afterRun(){
        log.info("执行 AfterReturning");
    }

    /**
     * 异常通知
     * 注意: 使用@AfterThrowing与@Around时,这两个advice的切入点不能重合,如何这里@Around的切入点也是afterPointCut(),那么@AfterThrowing不会生效
     */
    @AfterThrowing(throwing="throwable"
            , value="afterPointCut()")
    public void afterThrow(Throwable throwable){
        log.info("执行 ---- AfterThrowing");
    }
}

控制器:

package com.example.demo.spring.aop.controller;

import com.example.demo.spring.aop.service.TestService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author: sunzhinan
 * @create: 2020-08-09 00:56
 * @description: 控制器
 */

@RestController
@Slf4j
public class TestController {

    @Autowired
    private TestService testService;

    @RequestMapping(value = "/test1",method = RequestMethod.GET)
    public String test1(){
        System.out.println("test");

        System.out.println("----开始调用add方法----");
        testService.add(27,"sunzhinan");
        System.out.println("----调用add方法结束----");
        return "hello world!";
    }

    @RequestMapping(value = "/test2",method = RequestMethod.GET)
    public String test2(){

        System.out.println("----开始调用query方法----");

        try {
            testService.query("sunzhinan");
        } catch (Exception e) {
            log.info("Controller 捕获异常");
        }
        System.out.println("----调用query方法结束----");
        return "hello world!";
    }
}

接口:

package com.example.demo.spring.aop.service;

import java.util.Map;

/**
 * @author: sunzhinan
 * @create: 2020-08-09 00:59
 * @description: 业务层
 */
public interface TestService {
    public void add(int i,String name);

    public String query(String id);
}

实现类:

package com.example.demo.spring.aop.service.impl;

import com.example.demo.spring.aop.service.TestService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * @author: sunzhinan
 * @create: 2020-08-09 00:59
 * @description: 业务实现类
 */
@Service
@Slf4j
public class TestServiceImpl implements TestService {

    @Override
    public void add(int i,String name) {
        log.info("----------------------------");
        log.info("新增方法实现功能    " + i + "      " + name);
        log.info("----------------------------");
    }

    @Override
    public String query(String id){
        log.info("----------------------------");
        log.info("查询方法实现功能    " + id );
        log.info("----------------------------");

        //测试异常通知
        //if(true){
        //    log.info("-----异常----");
        //    throw new ArrayIndexOutOfBoundsException("数组越界");
        //}

        return "21545";
    }
}

结合注解示例

接下来展示通过结合注解来实现切面

切面类:

package com.example.demo.spring.aop.aspect;

import com.alibaba.fastjson.JSONObject;
import com.example.demo.spring.aop.annotation.Authority;
import com.example.demo.spring.aop.annotation.Roles;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

/**
 * @author: sunzhinan
 * @create: 2020-08-09 13:24
 * @description: 角色注解切面处理类
 */
@Aspect
@Slf4j
@Component
public class TestRoleAspect {

    @Pointcut("@annotation(com.example.demo.spring.aop.annotation.Authority)")
    private void authority(){}

    @Around(value = "authority()")
    public String advice(ProceedingJoinPoint joinPoint) throws Throwable{

        String response = null;

        // 获得切入的 Method
        MethodSignature joinPointObject = (MethodSignature) joinPoint.getSignature();
        // 获得方法
        Method method = joinPointObject.getMethod();

        RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
        RequestMethod[] methods =requestMapping.method();

        //我这里为了方便,直接从入参中取出
        Object[] args = joinPoint.getArgs();

        //根据入参形式不同进行取参
//        String params = null;
//        // GET请求
//        if(methods.length > 0 && requestMapping.method()[0] == RequestMethod.GET){
//            params = getRequestParams(method,args);
//        }else{
//            if(args != null && args.length > 0){
//                params = (String) args[0];
//            }
//        }

        // 通过session的方式
//        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
//        String role = (String) request.getSession().getAttribute("role");

        Authority annotation = method.getAnnotation(Authority.class);
        Roles[] roles = annotation.role();

        System.out.println("----------------------");

        if ("".equals(args[0])) {
            return "没有权限";
        }

        if (checkRole(roles, (String) args[0])){
            return "没有权限";
        }

        System.out.println("----------------------");

        // 执行切面方法
        try{

            response = (String) joinPoint.proceed();
        }catch (Throwable throwable){

        }

        return response;
    }

    private String getRequestParams(Method requestMethod,Object[] requestArgs){

        String str = "";

        if (requestArgs == null || requestArgs.length == 0){
            return null;
        }

        Annotation[][] paramsAns = requestMethod.getParameterAnnotations();
        if (paramsAns.length > 0){
            for (int i = 0; i < paramsAns.length; i++) {
                Annotation[] paramAns = paramsAns[i];
                if (paramAns != null && paramAns.length > 0){
                    for (int j = 0; j < paramAns.length; j++) {
                        if(paramAns[j] instanceof RequestParam){
                            str = (String) requestArgs[i];
                            break;
                        }
                    }
                }
            }
        }

        return str;

    }

    private static boolean checkRole(Roles[] roles,String role){
        boolean flag = true;
        for (int i = 0; i < roles.length; i++) {
            if (role.equals(roles[i].getRoleName())){
                System.out.println(roles[i]);
                flag = false;
                break;
            }
        }
        return flag;
    }
}

注解类:

package com.example.demo.spring.aop.annotation;


import java.lang.annotation.*;

/**
 * @author: sunzhinan
 * @create: 2020-08-09 11:28
 * @description: 注解类--权限
 */
@Inherited
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Authority {

    /**
     * 角色
     * @return
     */
    Roles[] role();
}

角色枚举:

package com.example.demo.spring.aop.annotation;

/**
 * @author: sunzhinan
 * @create: 2020-08-09 13:22
 * @description: 角色
 */
public enum Roles {


    MANAGER("manager"),
    ROOT("root"),
    TOURIST("Tourist"),
    NORMAL("normal");

    private String roleName;

    Roles(String roleName) {
        this.roleName = roleName;
    }

    public String getRoleName() {

        return roleName;
    }
}

控制器:

package com.example.demo.spring.aop.controller;

import com.example.demo.spring.aop.annotation.Authority;
import com.example.demo.spring.aop.annotation.Roles;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author: sunzhinan
 * @create: 2020-08-09 11:26
 * @description: 测试通过注解来实现面向切面编程
 */

@RestController
@Slf4j
public class TestAnnotationController {

    @Authority(role= Roles.ROOT)
    @RequestMapping(value = "/annotation1",method = RequestMethod.GET)
    public String test1(@RequestParam("role") String role){
        log.info("----我来啦 1----");
        return "hello Annotation1!";
    }

    @Authority(role= Roles.TOURIST)
    @RequestMapping(value = "/annotation2",method = RequestMethod.GET)
    public String test2(@RequestParam("role") String role){
        log.info("----我来啦 2----");
        return "hello Annotation2!";
    }

    @Authority(role= {Roles.TOURIST,Roles.MANAGER})
    @RequestMapping(value = "/annotation3",method = RequestMethod.GET)
    public String test3(@RequestParam("role") String role){
        log.info("----我来啦 3----");
        return "hello Annotation3!";
    }
}

示例:

请求地址:http://localhost:8080/annotation3?role=manager 权限校验通过

请求地址:http://localhost:8080/annotation3?role=normal 权限校验不通过

其实,写了两个示例总结起来,只要能在切面获得请求参数,对请求参数进行处理,就能完成我们需要的功能,所有以后再面向切面编程的时候,我们需要注意如何取到入参,就基本能完成功能了。

注解说明

AOP常用注解

注解说明
@Aspect把当前类声明为切面类
@Before表示在切入点执行前需要进行的操作或者需要执行的方法
@After表示在切入点执行后,进行哪些操作
@AfterReturning表示在切入点方法处理成功后才会进行操作
@AfterThrowing表示在切入点出现异常后,进行哪些操作;参数:throwing与pointcut/value
@Around既可以在切入目标方法之前进行操作,也可以在切入目标方法之后织入进行操作;参数:ProceedingJoinPoint
@Pointcut切入点,对规则内的类进行代理
@EnableAspectJAutoProxy开启注解切面;参数:proxyTargetClass 默认false采用JDK动态代理,true采用Cglib代理
@ControllerAdvice全局异常处理、全局数据绑定、全局数据预处理

对于@Pointcut 规则表达式说明:

表达式关键词说明示例
execution最常用!用来匹配方法,方法使用全限定名,包括访问修饰符(public/private/protected)、返回类型,包名、类名、方法名、参数,其中返回类型,包名,类名,方法,参数是必须的@Pointcut(“execution(* com.example.demo.spring.aop.service…* . * (…))”):第一个通配符匹配所有返回值类型,第二个匹配这个类里的所有类,第三个匹配这个类里的所有方法,()括号表示参数列表,括号里的用两个点号表示匹配任意个参数,包括0个
within和execution差不多,可以用来匹配某个包下面所有类的方法(包括子包下面的所有类方法)@Pointcut(“within(com.example.demo.spring.aop.service…*)”):织入这个包下面的所以方法
this如果我们需要代理的类没有实现任何接口,或者:proxyTargetClass设为true时,这时应该使用this@Pointcut(“this(com.example.demo.spring.aop.controller.TestController)”)
target与this的区别就是,targer是使用JDK动态代理时用的,而this是使用Cglib时使用的,两者用法一样@Pointcut(“target(com.example.demo.spring.aop.controller.TestController)”)
@annotation常用!这个指示器匹配那些有指定注解的连接点@Pointcut("@annotation(com.example.demo.spring.aop.annotation.LogAnnotation)")
args该函数接收一个类名,表示目标类方法入参对象是指定类(包含子类)时,切点匹配。(可以去了解以下@args用法)@Pointcut(“args(com.example.demo.spring.Person)”)

自定义注解

@Inherited当@InheritedAnno注解加在某个类A上时,假如类B继承了A,则B也会带上该注解
@Target注解的作用目标:
@Target(ElementType.TYPE)——接口、类、枚举、注解
@Target(ElementType.FIELD)——字段、枚举的常量
@Target(ElementType.METHOD)——方法
`@Target(ElementType.PARAMETER)——方法参数
@Target(ElementType.CONSTRUCTOR) ——构造函数
@Target(ElementType.LOCAL_VARIABLE)——局部变量
@Target(ElementType.ANNOTATION_TYPE)——注解
@Target(ElementType.PACKAGE)——包
@Retention注解的保留位置
RetentionPolicy.SOURCE:这种类型的Annotations只在源代码级别保留,编译时就会被忽略,在class字节码文件中不包含。
RetentionPolicy.CLASS:这种类型的Annotations编译时被保留,默认的保留策略,在class文件中存在,但JVM将会忽略,运行时无法获得。
RetentionPolicy.RUNTIME:这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。
@Document说明该注解将被包含在javadoc中

以上就是Spring AOP的一些简单应用,这章就介绍到这,后面有时间,会对AOP进行详细的介绍,先挖个坑。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值