1. 程式人生 > >SpringBoot自定義註解

SpringBoot自定義註解

我們經常使用自定義註解和AOP實現操作日誌、許可權、統計執行時間等功能,本文記錄使用SpringBoot實現自定義註解

本文在一個ssm專案的基礎上進行的
SpringBoot搭建SSM專案:https://blog.csdn.net/qidasheng2012/article/details/84233549

開始正文
1. 新增依賴

<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>

	<groupId>com.springbootssm</groupId>
	<artifactId>ssm</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>ssm</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.18.BUILD-SNAPSHOT</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<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>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.2</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

	<repositories>
		<repository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/snapshot</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>

	<pluginRepositories>
		<pluginRepository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/snapshot</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</pluginRepository>
		<pluginRepository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</pluginRepository>
	</pluginRepositories>
	
</project>

2. 編寫自定義註解類

package com.springbootssm.ssm.annotation;

import java.lang.annotation.*;

/**
 * 操作日誌自定義註解
 */
@Target({ElementType.METHOD}) // 作用在方法上
@Retention(RetentionPolicy.RUNTIME) // 註解會在class位元組碼檔案中存在,在執行時可以通過反射獲取到
@Documented // 說明該註解將被包含在javadoc中
public @interface OperatingLogAnnotation {
    /**
     * 記錄操作描述
     */
    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是一個標記註解,沒有成員。

3. 編寫註解類切面

package com.springbootssm.ssm.annotation;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * 操作日誌切面
 */
@Aspect
@Component
public class OperatingLogAspect {

    // 切入點簽名
    @Pointcut("@annotation(com.springbootssm.ssm.annotation.OperatingLogAnnotation)")
    private void cut() {
    }

    // 前置通知
    @Before("cut()")
    public void BeforeCall() {
        System.out.println("====前置通知start");

        System.out.println("====前置通知end");
    }

    // 環繞通知
    @Around(value = "cut()")
    public Object AroundCall(ProceedingJoinPoint joinPoint)  throws Throwable {
        System.out.println("====環繞通知start");

        // 註解所切的方法所在類的全類名
        String typeName = joinPoint.getTarget().getClass().getName();
        System.out.println("目標物件:" + typeName);

        // 註解所切的方法名
        String methodName = joinPoint.getSignature().getName();
        System.out.println("所切方法名:" + methodName);

        StringBuilder sb = new StringBuilder();
        // 獲取引數
        Object[] arguments = joinPoint.getArgs();
        for (Object argument : arguments) {
            sb.append(argument.toString());
        }
        System.out.println("所切方法入參:" + sb.toString());

        // 統計方法執行時間
        long start = System.currentTimeMillis();

        //執行目標方法,並獲得對應方法的返回值
        Object result= joinPoint.proceed();
        System.out.println("返回結果:" + result);

        long end = System.currentTimeMillis();
        System.out.println("====執行方法共用時:" + (end - start));

        System.out.println("====環繞通知之結束");
        return result;
    }

    // 後置通知
    @After("cut()")
    public void AfterCall() {
        System.out.println("====後置通知start");

        System.out.println("====後置通知end");
    }

    // 最終通知
    @AfterReturning("cut()")
    public void AfterReturningCall() {
        System.out.println("====最終通知start");

        System.out.println("====最終通知end");
    }

    // 異常通知
    @AfterThrowing(value = "cut()", throwing="ex")
    public void afterThrowing(Throwable ex) {
        throw new RuntimeException(ex);
    }

}
  • @Aspect : 作用是把當前類標識為一個切面供容器讀取

  • @component : 把普通pojo例項化到spring容器中,相當於配置檔案中的

  • @Pointcut : 定義一個切點

  • @Before : 標識一個前置增強方法,相當於BeforeAdvice的功能

  • @Around : 環繞增強方法

  • @After :後置增強方法

4. 在controler層使用註解

package com.springbootssm.ssm.controller;

import com.springbootssm.ssm.annotation.OperatingLogAnnotation;
import com.springbootssm.ssm.domain.Student;
import com.springbootssm.ssm.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class StudentController {
    @Autowired
    private StudentService studentService;

    @OperatingLogAnnotation(description = "獲取所有使用者資訊", clazz = Student.class)
    @RequestMapping("/getAll")
    public List<Student> getAll(Student student){
        System.out.println("=====真正執行方法 ");
        return studentService.getAll();
    }
}

5. 開啟瀏覽器,呼叫該介面,控制檯輸出

呼叫該介面:

http://localhost:8080/getAll?id=1&name=‘tom’

控制檯輸出:

====環繞通知start
目標物件:com.springbootssm.ssm.controller.StudentController
所切方法名:getAll
所切方法入參:Student{id=1, name=''tom'', age=null, sex='null'}
====前置通知start
====前置通知end
=====真正執行方法 
返回結果:[Student{id=1, name='Tom', age=13, sex='1'}, Student{id=2, name='jick', age=14, sex='1'}, Student{id=3, name='mery', age=15, sex='0'}]
====執行方法共用時:327
====環繞通知之結束
====後置通知start
====後置通知end
====最終通知start
====最終通知end