在Spring Boot的开发中,经常会有与其他业务系统对接或封装公共API接口等需求,为了使开发过程更加便捷性,并降低系统之间的耦合性,通常会使用自定义注解来封装一些业务,在后续开发时直接使用自定义注解即可,无需再关注注解内部业务流程的实现。本文将介绍如何在Spring Boot框架中实现自定义注解。
1 添加依赖
<!-- 引入aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2 创建注解类
项目结构如下:
创建annotation包(自定义名称),在该包下创建注解类,右键annotation包,选择添加Java Class。此类的类型为@interface,在Kind处选择Annotation。
注解类常用注解的作用如下:
注解 | 参数类型 | 作用 |
@Target | ElementType | 指定写入注解的合法位置。 |
@Retention | RetentionPolicy | 指定注解的生存周期。 |
@Documented | 表明由 javadoc记录,注释成为公共API的一部分。 | |
@Inherited | 允许子类继承父类中的注解。 |
常见的注解使用目标位置为:类、字段、方法等,ElementType枚举类型的定义如下:
public enum ElementType {
/** 类, 接口 (包括注释类型), 或 枚举 声明 */
TYPE,
/** 字段声明(包括枚举常量) */
FIELD,
/** 方法声明(Method declaration) */
METHOD,
/** 正式的参数声明 */
PARAMETER,
/** 构造函数声明 */
CONSTRUCTOR,
/** 局部变量声明 */
LOCAL_VARIABLE,
/** 注释类型声明 */
ANNOTATION_TYPE,
/** 包声明 */
PACKAGE,
/**
* 类型参数声明
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* 使用的类型
*
* @since 1.8
*/
TYPE_USE
}
RetentionPolicy枚举类型的定义如下:
public enum RetentionPolicy {
/**
* 注释只在源代码级别保留,编译时被忽略
*/
SOURCE,
/**
* 注释将被编译器在类文件中记录
* 但在运行时不需要JVM保留。这是默认的
* 行为.
*/
CLASS,
/**
*注释将被编译器记录在类文件中
*在运行时保留VM,因此可以反读。
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
注解类示例代码如下:
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyCache {
String name() default "";
String action() default "";
String key() default "";
boolean filter() default false;
}
实现注解类之后,就可以开始在类或字段上使用自定义的注解了。示例如下:
public class User implements Serializable {
private Long id;
@MyCache(name = "user1")
private String account;
private String password;
private Date createTime;
private Date modifyTime;
private Integer deleted;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Date getModifyTime() {
return modifyTime;
}
public void setModifyTime(Date modifyTime) {
this.modifyTime = modifyTime;
}
public Integer getDeleted() {
return deleted;
}
public void setDeleted(Integer deleted) {
this.deleted = deleted;
}
@Override
public String toString() {
return "id:" + id
+ ",account:" + account
+ ",password:" + password
+ ",createTime:" + createTime
+ ",modifyTime:" + modifyTime
+ ",deleted:" + deleted;
}
}
字段加上注解后,可使用反射方法获取注解,实现相关业务逻辑。示例如下:
private void showName() {
// 获取类的字段定义
Field[] fields = User.class.getDeclaredFields();
for (Field field : fields) {
// 判断字段定义是否存在指定注解
if (field.isAnnotationPresent(MyCache.class)) {
// 允许获取字段信息
field.setAccessible(true);
// 获取注解属性的值
String name = field.getAnnotation(MyCache.class).name();
System.out.println("name: " + name);
}
}
}
3 创建切面类
实现方法的注解时,除注解类外还需要创建切面类,用于实现被注解方法执行前后需要执行的业务逻辑。创建aspect包(自定义名称),在该包下创建切面类,右键aspect包,选择添加Aspect,Kind选择@Aspect。或直接创建一个Java Class,然后在类上方添加@Aspect注解。
切面类示例代码如下:
@Aspect
@Component
public class MyCacheAspect {
/**
* 定义切点
*/
@Pointcut("@annotation(com.cetc.cloud.annotation.MyCache)")
public void annotationPointcut() {
}
/**
* 方法开始执行之前
*/
@Before("annotationPointcut()")
public void doBeforeRunning (JoinPoint joinPoint) {
}
/**
* 方法开始执行之后
*/
@After("annotationPointcut()")
public void doAfterRunning (JoinPoint joinPoint) {
}
/**
* 当方法执行完返回结果时
* returning属性指定方法参数的result拦截返回结果
* 可获取或修改返回结果
*/
@AfterReturning(value = "annotationPointcut()", returning = "result")
public void doAfterReturning (JoinPoint joinPoint, Object result) {
}
/**
* 当方法执行异常时
* throwing属性指定方法参数的e获取异常参数
* 可查看异常信息
*/
@AfterThrowing(value = "annotationPointcut()", throwing = "e")
public void doAfterThrowing (JoinPoint joinPoint, Exception e) {
}
/**
* 切点环绕通知
* 可跳过执行原方法
*/
@Around("annotationPointcut()")
public Object aroundPointcut (ProceedingJoinPoint joinPoint) throws Throwable {
//原方法执行之前
...
//执行原方法,可跳过
Object obj = joinPoint.proceed();
//原方法执行之后
...
//返回方法执行结果
return obj;
}
}
实现注解类和切面类之后,就可以开始使用自定义的方法注解了。示例如下:
@Override
@MyCache(name = "userCache", action = "query", key = "#user.account", filter = false)
public User getUserByAccount(User user) {
...
}
4 方法注解的执行顺序
切面类中几种注解的运行顺序如下:
@Around
@Before
原方法
@Around
@After
@AfterThrowing
@AfterReturning
当一个方法上有多个注解时,默认按照从上到下的顺序设置注解的Order,即注解执行顺序,Order小的注解先被触发。
注解即Spring AOP,Spring AOP基于面向切面编程思想,即所有注解和方法是一个同心圆,要执行的方法为圆心。最外层的Order最小,从最外层按照AOP1、AOP2的顺序依次执行Around方法、Before方法,然后执行原方法,最后按照AOP2、AOP1的顺序依次执行Around方法、After方法、AfterThrow方法、AfterReturn方法。也就是说对于多个AOP来说,先执行Before的,一定后执行After。见下图:
也可以自己指定AOP的执行顺序Order,在切面上使用@Order注解,如:
@Aspect
@Component
@Order(1)
public class Aspect1 {
}
@Aspect
@Component
@Order(2)
public class Aspect2 {
}