SpringBoot2.x + 自定义注解

【简介】

项目中经常使用自定义注解和切面,实现操作日志、权限、统计执行时间等功能,本文以操作日志为例


【本文 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)有:

    1. CONSTRUCTOR:用于描述构造器
    2. FIELD:用于描述域
    3. LOCAL_VARIABLE:用于描述局部变量
    4. METHOD:用于描述方法
    5. PACKAGE:用于描述包
    6. PARAMETER:用于描述参数
    7. TYPE:用于描述类、接口(包括注解类型) 或enum声明
  • @Retention
    @Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
    取值(RetentionPoicy)有:

    1. SOURCE:在源文件中有效(即源文件保留)
    2. CLASS:在class文件中有效(即class保留)
    3. 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
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值