本文实现了,自定义个注解,用来标注切入点,就是说,你想让哪些个方法执行切面的方法,只需要在这些方法上面,添加自定义注解,然后,就可以执行切面的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
<?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">
命名空间完了之后,就是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包的依赖关系图
自定义注解:MethodLog
package 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.java
package 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,重点就是要知道怎么切,切哪,切完之后,能拿到什么参数,这个包括所切方法的参数,所切方法的返回值,等等。