【简介】
项目中经常使用自定义注解和切面,实现操作日志、权限、统计执行时间等功能,本文以操作日志为例
【本文 Demo】
https://github.com/qidasheng2012/springboot2.x_ssm/tree/V1.0.0
【项目结构】
【添加依赖】
【SpringBoot 项目】
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
【Spring 项目】
<!-- 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>
【pom.xml】
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.springboot</groupId>
<artifactId>springboot2.x_ssm</artifactId>
<version>1.0.0</version>
<description>Spring Boot2.x 搭建 SSM 项目</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--对于HTML页面要添加thymeleaf的相关依赖才能正确跳转-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- 支持 @ConfigurationProperties 注解 读取properties或yml配置文件中的值 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
【LogAnnotation】
自定义注解类
package com.springboot.ssm.annotation;
import java.lang.annotation.*;
/**
* 操作日志自定义注解
*/
@Target({ElementType.METHOD}) // 作用在方法上
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Documented // 说明该注解将被包含在javadoc中
public @interface LogAnnotation {
/**
* 记录操作描述
*/
String description() default "";
/**
* 增删改的数据的类型
*/
Class<?> clazz();
}
-
@Target
@Target 说明了Annotation所修饰的对象范围
取值(ElementType)有:- CONSTRUCTOR:用于描述构造器
- FIELD:用于描述域
- LOCAL_VARIABLE:用于描述局部变量
- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER:用于描述参数
- TYPE:用于描述类、接口(包括注解类型) 或enum声明
-
@Retention
@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
取值(RetentionPoicy)有:- SOURCE:在源文件中有效(即源文件保留)
- CLASS:在class文件中有效(即class保留)
- RUNTIME:在运行时有效(即运行时保留)
-
@Documented
@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。
【LogAspect】
切面类
package com.springboot.ssm.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 操作日志切面
*/
@Slf4j
@Aspect
@Component
public class LogAspect {
// 切入点签名
@Pointcut("@annotation(com.springboot.ssm.annotation.LogAnnotation)")
private void cut() {
}
// 前置通知
@Before("cut()")
public void BeforeCall() {
log.info("====前置通知start");
log.info("====前置通知end");
}
// 环绕通知
@Around(value = "cut()")
public Object AroundCall(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("====环绕通知start");
// 注解所切的方法所在类的全类名
String typeName = joinPoint.getTarget().getClass().getName();
log.info("目标对象:[{}]", typeName);
// 注解所切的方法名
String methodName = joinPoint.getSignature().getName();
log.info("所切方法名:[{}]", methodName);
StringBuilder sb = new StringBuilder();
// 获取参数
Object[] arguments = joinPoint.getArgs();
for (Object argument : arguments) {
sb.append(argument.toString());
}
log.info("所切方法入参:[{}]", sb.toString());
// 统计方法执行时间
long start = System.currentTimeMillis();
//执行目标方法,并获得对应方法的返回值
Object result = joinPoint.proceed();
log.info("返回结果:[{}]", result);
long end = System.currentTimeMillis();
log.info("====执行方法共用时:[{}]", (end - start));
log.info("====环绕通知之结束");
return result;
}
// 后置通知
@After("cut()")
public void AfterCall() {
log.info("====后置通知start");
log.info("====后置通知end");
}
// 最终通知
@AfterReturning("cut()")
public void AfterReturningCall() {
log.info("====最终通知start");
log.info("====最终通知end");
}
// 异常通知
@AfterThrowing(value = "cut()", throwing = "ex")
public void afterThrowing(Throwable ex) {
throw new RuntimeException(ex);
}
}
-
@Aspect : 作用是把当前类标识为一个切面供容器读取
-
@component : 把普通pojo实例化到spring容器中,相当于配置文件中的
-
@Pointcut : 定义一个切点
-
@Before : 标识一个前置增强方法,相当于BeforeAdvice的功能
-
@Around : 环绕增强方法
-
@After :后置增强方法
【UserController】
在controler层使用注解
package com.springboot.ssm.controller;
import com.springboot.ssm.annotation.LogAnnotation;
import com.springboot.ssm.domain.User;
import com.springboot.ssm.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/toUserListPage")
public String toUserListPage() {
return "user/userList";
}
@LogAnnotation(description = "获取所有用户信息", clazz = User.class)
@RequestMapping("/getAll")
@ResponseBody
public List<User> getAll() {
return userService.getAll();
}
}
【测试】
访问: http://localhost:8080/user/getAll?id=1&name=tom
日志:
2019-07-22 13:48:32.183 INFO 7112 --- [nio-8080-exec-1] com.springboot.ssm.aspect.LogAspect : ====环绕通知start
2019-07-22 13:48:32.184 INFO 7112 --- [nio-8080-exec-1] com.springboot.ssm.aspect.LogAspect : 目标对象:[com.springboot.ssm.controller.UserController]
2019-07-22 13:48:32.188 INFO 7112 --- [nio-8080-exec-1] com.springboot.ssm.aspect.LogAspect : 所切方法名:[getAll]
2019-07-22 13:48:32.188 INFO 7112 --- [nio-8080-exec-1] com.springboot.ssm.aspect.LogAspect : 所切方法入参:[User(id=1, name=tom, age=null)]
2019-07-22 13:48:32.188 INFO 7112 --- [nio-8080-exec-1] com.springboot.ssm.aspect.LogAspect : ====前置通知start
2019-07-22 13:48:32.188 INFO 7112 --- [nio-8080-exec-1] com.springboot.ssm.aspect.LogAspect : ====前置通知end
2019-07-22 13:48:32.219 INFO 7112 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2019-07-22 13:48:32.221 WARN 7112 --- [nio-8080-exec-1] com.zaxxer.hikari.util.DriverDataSource : Registered driver with driverClassName=com.mysql.jdbc.Driver was not found, trying direct instantiation.
2019-07-22 13:48:32.491 INFO 7112 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2019-07-22 13:48:32.545 INFO 7112 --- [nio-8080-exec-1] com.springboot.ssm.aspect.LogAspect : 返回结果:[[User(id=1, name=张三, age=23), User(id=2, name=李四, age=24)]]
2019-07-22 13:48:32.545 INFO 7112 --- [nio-8080-exec-1] com.springboot.ssm.aspect.LogAspect : ====执行方法共用时:[357]
2019-07-22 13:48:32.545 INFO 7112 --- [nio-8080-exec-1] com.springboot.ssm.aspect.LogAspect : ====环绕通知之结束
2019-07-22 13:48:32.545 INFO 7112 --- [nio-8080-exec-1] com.springboot.ssm.aspect.LogAspect : ====后置通知start
2019-07-22 13:48:32.545 INFO 7112 --- [nio-8080-exec-1] com.springboot.ssm.aspect.LogAspect : ====后置通知end
2019-07-22 13:48:32.545 INFO 7112 --- [nio-8080-exec-1] com.springboot.ssm.aspect.LogAspect : ====最终通知start
2019-07-22 13:48:32.545 INFO 7112 --- [nio-8080-exec-1] com.springboot.ssm.aspect.LogAspect : ====最终通知end