笔者前几天看代码的时候,看到了之前的一位大佬用注解写的事务,出于好奇之下,查了些资料后也尝试去写了一个,在这里记录一下。
目录
一、使用自定义注解的前提
- 1、项目必须是一个spring项目
- 2、需要导入一个额外的依赖aspectjweaver,通过该依赖中的某些注解实现自定义注解
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
二、如何实现自定义注解
1、第一步:自定义一个注解类
注解类中没有额外的具体实现,只需要设置一些必要的参数和注解的作用域、生命周期。
package com.muyichen.demo.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 校验账号是否有权限
* @author muyichen
* @date 2021-04-15
*/
@Target(ElementType.METHOD) //表明该注解的目标是方法
@Retention(RetentionPolicy.RUNTIME) //指定注解的生命周期(作用域) source < class < runtime
public @interface CheckAcct {
/**
* 用户名
* @return
*/
String name() default "";
/**
* 权限
* @return
*/
String[] authorities() default "";
/**
* 是否开启校验
* @return
*/
boolean isActive() default true;
}
2、第二步:创建自定义注解类的实现类
之前的注解类只相当于一个标签,并没有对应的具体实现,需要通过@Aspect注解创建一个切片,为对应的标签加上对应的实现方法。具体如下:
package com.muyichen.demo.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* 校验切面类
*/
@Aspect //把当前类标识为一个切面供容器读取
@Component //把当前类标记为一个组件加入容器
public class CheckAcctAop {
/**
* 在所有标记了@CheckAcct注解的方法之前执行
* @param joinPoint aspectj内置参数
*/
@Before("@annotation(com.muyichen.demo.aop.CheckAcct)")
public void checkAcct(JoinPoint joinPoint) throws Exception {
//JoinPoint是aspectj内置参数,同HttpServletRequest类似,通过它可获取注解中的参数信息
MethodSignature ms = (MethodSignature) joinPoint.getSignature();
Method method = ms.getMethod();
//获取配置参数isActive,判断是否开启校验,默认开启
boolean active = method.getAnnotation(CheckAcct.class).isActive();
if (!active) {
return;
}
String name = method.getAnnotation(CheckAcct.class).name();
System.out.println("注解中的配置参数name为:" + name);
String[] authorities = method.getAnnotation(CheckAcct.class).authorities();
System.out.println("注解中的配置参数authorities为:" + Arrays.asList(authorities));
//这里填写业务逻辑
if (name.equals("Smith") && Arrays.asList(authorities).contains("sys:delete")) {
System.out.println("校验成功");
} else {
System.out.println("校验失败");
}
}
}
- @Aspect: 作用是把当前类标识为一个切面供容器读取;
- @Pointcut:Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码;
- @Around:环绕增强,相当于MethodInterceptor;
- @AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行;
- @Before:标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有;
- @AfterThrowing:异常抛出增强,相当于ThrowsAdvice;
- @After: final增强,不管是抛出异常或者正常退出都会执行;
通过上述两步之后就已经完成了一个注解的创建,笔者接下来要说一下,自定义注解使用过程中的一些注意事项。
三、自定义注解注意事项
1、注解一定要在spring组件中使用
- 注解一定要在spring组件中使用,也就是必须要在spring容器启动成功后才能使用。如果容器没有启动,是无法找到注解的具体实现的。笔者试过用main方法调用一个加了注解,但不在Spring组件中的方法,发现注解的方法根本没有触发,代码如下:
package com.muyichen.demo.aop;
import org.springframework.stereotype.Component;
/**
* 注解测试类
*/
//@Component
public class TestAop {
@CheckAcct(name = "Smith", authorities = "sys:delete")
public static void getUserInfo() {
System.out.println("当前用户的信息是xxxxxx!");
}
public static void main(String[] args) {
getUserInfo();
}
}
结果:
当前用户的信息是xxxxxx!
笔者正常启用,是通过spring test组件,通过单元测试的方式来完成对注解的校验的:
package com.muyichen.demo;
import com.muyichen.demo.aop.TestAop;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class DemoApplicationTests {
@Autowired
private TestAop testAop;
@Test
public void test(){
testAop.getUserInfo();
}
}
正常的结果为:
注解中的配置参数name为:Smith
注解中的配置参数authorities为:[sys:delete]
校验成功
当前用户的信息是xxxxxx!
2、使用注解时需要注意它的生命周期
注解按生命周期来划分可分为3类:
- RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
- RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
- RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
3、使用注解时需要注意它的作用域
注解的作用域主要都在ElementType这个枚举类型中
public enum ElementType {
/**标明该注解可以用于类、接口(包括注解类型)或enum声明*/
TYPE,
/** 标明该注解可以用于字段(域)声明,包括enum实例 */
FIELD,
/** 标明该注解可以用于方法声明 */
METHOD,
/** 标明该注解可以用于参数声明 */
PARAMETER,
/** 标明注解可以用于构造函数声明 */
CONSTRUCTOR,
/** 标明注解可以用于局部变量声明 */
LOCAL_VARIABLE,
/** 标明注解可以用于注解声明(应用于另一个注解上)*/
ANNOTATION_TYPE,
/** 标明注解可以用于包声明 */
PACKAGE,
/**
* 标明注解可以用于类型参数声明(1.8新加入)
* @since 1.8
*/
TYPE_PARAMETER,
/**
* 类型使用声明(1.8新加入)
* @since 1.8
*/
TYPE_USE
}