Spring MVC 集成 AOP,自定义注解,在切面获得方法参数,以及自定义注解的参数。
2017-12-14 13:28
495 查看
本文实现了,自定义个注解,用来标注切入点,就是说,你想让哪些个方法执行切面的方法,只需要在这些方法上面,添加自定义注解,然后,就可以执行切面的advice啦。
我们在切面可以拿到:
1,当前执行方法的参数。
2,自定义注解上定义的参数。
3,顺便获得当前session里面的用户吧。
要在spring mvc里面集成aop,那么就得先看如何完善配置文件。
这有个前提。
就是你的项目已经是spring mvc啦,我这就不逼逼spring mvc需要的配置啦,下面只说aop相关的配置。
首先是:applicationContext.xml
引入命名空间,aop相关的就是下面的2行,为了方便观众,我就把整个的命名空间给贴在下面。
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
我们在切面可以拿到:
1,当前执行方法的参数。
2,自定义注解上定义的参数。
3,顺便获得当前session里面的用户吧。
要在spring mvc里面集成aop,那么就得先看如何完善配置文件。
这有个前提。
就是你的项目已经是spring mvc啦,我这就不逼逼spring mvc需要的配置啦,下面只说aop相关的配置。
首先是:applicationContext.xml
引入命名空间,aop相关的就是下面的2行,为了方便观众,我就把整个的命名空间给贴在下面。
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">[/code]命名空间完了之后,就是aop的注解,我这顺便把这个spring 的扫描也给写这,是因为,声明切面的时候,这个切面也是要声明成一个组件,交给spring 容器帮忙处理一下的。
要是有些老铁,不了解原理的话,直接就只扫描controller或者service文件夹,那不就扫描不到这个切面类了吗,那就运行不起来啦。那就尴尬啦。后面,我会上我的整个测试项目的文件目录概览图,仅供参考吧。<!-- 开启spring的扫描注入,使用如下注解 --> <!-- @Component,@Repository,@Service,@Controller--> <context:component-scan base-package="com.lxk"/> <!-- aop 注解实现 --> <aop:aspectj-autoproxy/>
然后是:pom.xml引入AOP的jar依赖<!-- AspectJ --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.6.10</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.7.2</version> </dependency>这地方,可能有老铁,会只是单独引入上面那个,不过,你跟着我的教程来,那肯定不会出下面这个错啦。
aspectj 使用spring AOP切面编程的时候报错:ReflectionWorld$ReflectionWorldException NoClassDefFoundError 的处理
看看在项目里面,这个jar包的依赖关系图
可以看到,引入的2个jar包是直接被这个项目依赖的,后面没有出现复杂的嵌套依赖,这依赖关系,一目了然。清晰。。。
想知道,这个图怎么来吗?看下面链接。
Intellij IDEA 中如何查看maven项目中所有jar包的依赖关系图
自定义注解:MethodLogpackage com.lxk.annotation; import java.lang.annotation.*; /** * 记录数据库相关的操作如:新建,更新,删除。 * * @author lxk on 2017/12/7 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Documented public @interface MethodLog { /** * 记录操作描述 */ String description() default ""; /** * 增删改的数据的类型 */ Class<?> clazz(); }
这个注解,是使用在方法上的。对于注解的使用,还是白板的小伙伴,麻烦移步到下面这个链接,简单瞅瞅,这个注解是怎么声明,怎么使用的
注解之概念的理解
好,注解定义OK之后,就是该使用了,咱先看切面的代码吧。
切面类:CalendarAspect.javapackage com.lxk.aop; import com.lxk.annotation.MethodLog; import com.lxk.httpModel.SessionInfo; import com.lxk.model.User; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; 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.lang.reflect.Method; /** * 日历的日志切面操作 * * @author lxk on 2017/12/7 */ @Component @Aspect public class CalendarAspect { //声明个切面,切哪呢?切到 com.lxk.service.StudentService 这个目录下,以save开头的方法,方法参数(..)和返回类型(*)不限 @Pointcut("execution(* com.lxk.service.StudentService.save*(..))") private void aa() { }//切入点签名 /** * 前置通知 */ @Before("aa()") public void beforeMethod(JoinPoint joinPoint) { System.out.println("before method start ..."); System.out.println("before method end ..."); } /** * 环绕通知 around advice * 这个切的是 com.lxk.service 包下面以及子包下所有,后面又 && 同时满足带有注解 MethodLog */ @Around(value = "(execution(* com.lxk.service..*(..))) && @annotation(methodLog)", argNames = "joinPoint, methodLog") public Object methodAround(ProceedingJoinPoint joinPoint, MethodLog methodLog) throws Throwable { System.out.println("Around method start......................."); User user = getUserFromSession(); if (user != null) { System.out.println("Around method " + user.toString()); } //获得自定义注解的参数 System.out.println("Around method methodLog 的参数,remark:" + methodLog.description() + " clazz:" + methodLog.clazz()); //执行目标方法,并获得对应方法的返回值 Object result = joinPoint.proceed(); System.out.println("Around method 返回结果:" + result); System.out.println("Around method end......................."); return result; } /** * 最终通知 after advice * 使用的是在上面声明的切面,并且带上个注解,意思是除了满足上面aa()方法的条件还得带上注解才OK */ @After(value = "aa() && @annotation(methodLog)", argNames = "joinPoint, methodLog") public void methodAfter(JoinPoint joinPoint, MethodLog methodLog) throws Throwable { System.out.println("After method start......................."); //获得自定义注解的参数 System.out.println("After method methodLog 的参数,remark:" + methodLog.description() + " clazz:" + methodLog.clazz()); MethodLog remark = getMethodRemark(joinPoint); System.out.println("After method end......................."); } /** * 后置通知 * */ @AfterReturning(value = "(execution(* com.lxk.service..*(..))) && @annotation(methodLog)", argNames = "joinPoint, methodLog, result", returning = "result") public void methodAfterReturning(JoinPoint joinPoint, MethodLog methodLog, Object result) throws Throwable { System.out.println("AfterReturning method start......................."); System.out.println("AfterReturning method 返回的结果:" + result); User user = getUserFromSession(); if (user != null) { System.out.println("AfterReturning " + user.toString()); } System.out.println("AfterReturning method end......................."); } /** * 从session里面获得user对象 */ private User getUserFromSession() { //获取到当前线程绑定的请求对象 HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); //已经拿到session,就可以拿到session中保存的用户信息了。 User user = null; try { SessionInfo sessionInfo = (SessionInfo) request.getSession().getAttribute("sessionInfo"); user = sessionInfo.getUser(); } catch (Exception ignored) { } return user; } /** * 获取方法的中文备注____用于记录用户的操作日志描述 */ private MethodLog getMethodRemark(JoinPoint joinPoint) throws Exception { //返回目标对象 Object target = joinPoint.getTarget(); String targetName = target.getClass().getName(); //返回当前连接点签名 String methodName = joinPoint.getSignature().getName(); //获得参数列表 Object[] arguments = joinPoint.getArgs(); Class targetClass = Class.forName(targetName); Method[] method = targetClass.getMethods(); //这个怎么这么low呢。 for (Method m : method) { if (m.getName().equals(methodName)) { Class[] tmpCs = m.getParameterTypes(); if (tmpCs.length == arguments.length) { MethodLog methodCache = m.getAnnotation(MethodLog.class); if (methodCache != null && !("").equals(methodCache.description())) { return methodCache; } break; } } } return null; } }这个是切面的代码,在aop里面切面的代码,英文对应advice,通知。大概有有如上,前置,后置,最终,环绕,还有个异常,我这个切面没体现,
上面这个代码很多,没使用过的,肯定都不知道啥是啥,当然我也是这么个状态。
分享点东西。酌情看看,你是否需要看一下。
1,execution(* com.lxk.service.StudentService.save*(..))
这个叫切入点表达式,具体表达什么意思,什么语法,可参考下面的链接文章中,xml配置部分,顺便看看最原始的aop的样子。
spring AOP 之 xml 配置实现(附 Java 代码实例)
2,关于JoinPoint的方法和简单注释。package org.aspectj.lang; import org.aspectj.lang.reflect.SourceLocation; public interface JoinPoint { String toString(); //连接点所在位置的相关信息 String toShortString(); //连接点所在位置的简短相关信息 String toLongString(); //连接点所在位置的全部相关信息 Object getThis(); //返回AOP代理对象 Object getTarget(); //返回目标对象 Object[] getArgs(); //返回被通知方法参数列表 Signature getSignature(); //返回当前连接点签名 SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置 String getKind(); //连接点类型 StaticPart getStaticPart(); //返回连接点静态部分 }
3,自定义注解的时候,自己的理解。
spring aop 中@annotation()的使用,绝壁原创的文章
然后是调用的地方,如何调用。
先是controller层的代码@ResponseBody @RequestMapping(value = "createNewStudent", method = RequestMethod.POST) public JsonResult create(@RequestBody Student student) { if (student == null) { return new JsonResult(false, "student is null"); } //Student result = studentService.save(student); Student result = studentService.saveEmptyData(student); return result == null ? new JsonResult(false, "查无结果") : new JsonResult(true, "查找成功", result); }这地方,没啥,就是让观众知道,我这地方轮换着调用了2个save开头的方法。
正儿八经调用的是在service层里面。@MethodLog(description = "保存-方法名称save", clazz = Student.class) public Student save(Student student) { if (student != null) { student.setCreateTime(new Date()); } return dao.save(student); } @MethodLog(description = "保存-方法名称saveEmptyData", clazz = Student.class) public Student saveEmptyData(Student student) { return null; }这个时候,启动spring 项目,然后切着保存一下,看看切面代码的执行效果如何//这个是单独执行:studentService.save(student)方法的时候的运行结果。 Around method start....................... Around method methodLog 的参数,remark:保存-方法名称save clazz:class com.lxk.model.Student before method start ... before method end ... Around method 返回结果:Student{id='5a3207d8684fc4586f07f0a4', name='李学凯新建', age=18, sex=true, money=null, floor=null} Around method end....................... After method start....................... After method methodLog 的参数,remark:保存-方法名称save clazz:class com.lxk.model.Student After method end....................... AfterReturning method start....................... AfterReturning method 返回的结果:Student{id='5a3207d8684fc4586f07f0a4', name='李学凯新建', age=18, sex=true, money=null, floor=null} AfterReturning method end....................... //这个是单独执行:studentService.save(student)方法的时候的运行结果。 Around method start....................... Around method methodLog 的参数,remark:保存-方法名称saveEmptyData clazz:class com.lxk.model.Student before method start ... before method end ... Around method 返回结果:null Around method end....................... After method start....................... After method methodLog 的参数,remark:保存-方法名称saveEmptyData clazz:class com.lxk.model.Student After method end....................... AfterReturning method start....................... AfterReturning method 返回的结果:null AfterReturning method end.......................
从这个运行结果上,我们在切面,
确实可以拿到,咱自定义注解的各个参数的值。这是一个。
还可以拿到咱切入点,也就是目标方法的参数。joinPoint.getArgs()
方法名称啥的,也是可以拿到的,当前所切的类,所切的方法,所切方法的参数,以及所切方法的返回参数。
我这也拿了session里面的 sessionInfo,这个是我自定义的一个对象,然后把这个东西放到session里面。因为我这没放,所以,没有取到user对象。
所以,你得把你的user先放到session里面才能在这取。这个就先不说啦。
还有,就是可以看到这些个不同的切面通知的执行先后的情况
哦,对啦,我的这个测试项目的文件目录结构概览图,还没奉上呢。
差不多都这个样子吧
AOP,重点就是要知道怎么切,切哪,切完之后,能拿到什么参数,这个包括所切方法的参数,所切方法的返回值,等等。
相关文章推荐
- AOP 切面的使用,以及如何在通知上获取切入方法的注解和参数
- 日志--切面(AOP)的使用,以及配合使用自定义注解
- 利用AOP获取自定义标签的参数以及方法的参数
- Spring MVC AOP通过自定义注解方式拦截Controller等实现日志管理
- AOP实现拦截对象以及获取切入目标方法和注解
- 用AOP拦截自定义注解并获取注解属性与上下文参数(基于Springboot框架)
- 详解使用Spring AOP和自定义注解进行参数检查
- 在方法内获取调用此方法参数(指定获得注解的值)
- Spring aop 注解方式怎么获得执行了目标的某个方法?
- Spring mvc (八) [基于注解的案例][formbean的传递以及map传递参数]
- AOP实现拦截对象以及获取切入目标方法和注解
- Spring MVC通过添加自定义注解格式化数据的方法
- AOP实现拦截对象以及获取切入目标方法和注解
- spring aop使用注解从切面中获取参数
- AOP后置通知获取目标方法的参数以及配置详解
- 【转】bootbox自定义dialog、confirm、alert样式,以及基本设置方法setDefaults中可用参数
- JAVAWEB开发之Servlet3.0新特性的使用以及注解的详细使用和自定义注解的方法、动态代理的使用、利用动态代理实现细粒度的权限控制以及类加载和泛型反射
- bootbox自定义dialog、confirm、alert样式,以及基本设置方法setDefaults中可用参数
- dwr3.0与Spring mvc的全注解集成方法( @RemoteMethod)(@RemoteProxy)
- AOP实现拦截对象以及获取切入目标方法和注解