当前位置: 移动技术网 > IT编程>开发语言>Java > Spring AOP面向切面编程介绍及实例

Spring AOP面向切面编程介绍及实例

2020年08月10日  | 移动技术网IT编程  | 我要评论
Spring AOP简介AOP组成部分AOP使用示例简单示例结合注解示例注解说明AOP常用注解自定义注解首先再说AOP之前,可以先去了解一些代理模式,Spring的AOP功能就是基于JDK动态代理和Cglib代理实现的,关于代理模式可以去看我的这篇文章——代理模式简介AOP,简单的来说就是面向切面编程,它的全称是Aspect Oriented Programming,它能够将我们的业务逻辑和横切的问题进行分离(横切问题和我们业务逻辑关系不大),达到解耦的目的,使代码的重用性和开发效率提高。通过上面



首先再说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进行详细的介绍,先挖个坑。

本文地址:https://blog.csdn.net/qq_34754162/article/details/107897623

如您对本文有疑问或者有任何想说的,请 点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网