概要
前面用了自定义注解实现了限流,就顺便把自定义注解写一下
基本语法
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME) // 指定注解的保留策略
@Target({ElementType.TYPE, ElementType.METHOD}) // 指定注解的使用范围
public @interface MyAnnotation {
// 在这里定义注解的元素
String value(); // 示例元素,名为"value"
int count() default 1; // 示例元素,名为"count",并指定默认值
}
@Retention
Retention(RetentionPolicy.RUNTIME):指定了注解的保留策略。RetentionPolicy.RUNTIME表示注解会被保留到运行时,这样我们就可以通过反射来获取注解信息。
在 Java 中,@Retention 注解用于指定注解的保留策略,即注解在什么级别保存,有以下三种保留策略类型:
RetentionPolicy.SOURCE:源代码级别保留策略表示注解只会保留在源代码中,编译器会丢弃这些注解,不会包含在编译后的 class 文件中。这意味着无法通过反射在运行时获取这些注解的信息。
RetentionPolicy.CLASS:类级别保留策略表示注解会被保留到编译后的 class 文件中,但是 JVM 加载类时会丢弃这些注解。这意味着在运行时无法通过反射获取这些注解的信息,默认情况下,如果不指定 @Retention,则为 RetentionPolicy.CLASS。
RetentionPolicy.RUNTIME:运行时保留策略表示注解会被保留到运行时,编译器会将注解信息包含在编译后的 class 文件中,并且 JVM 加载类时会将这些注解加载到内存中,因此可以通过反射在运行时获取这些注解的信息。
@Target
@Target({ElementType.TYPE.ElementType.METHOD}):指定了注解可以应用的地方,比如类、方法、字段等。在这个例子中,MyAnnotation 注解可以应用在类和方法上。
以下是 ElementType 的值:
1. `ElementType.ANNOTATION_TYPE`:可以应用在注解类型上。
2. `ElementType.CONSTRUCTOR`:可以应用在构造方法上。
3. `ElementType.FIELD`:可以应用在字段(成员变量)上。
4. `ElementType.LOCAL_VARIABLE`:可以应用在局部变量上。
5. `ElementType.METHOD`:可以应用在方法上。
6. `ElementType.PACKAGE`:可以应用在包上。
7. `ElementType.PARAMETER`:可以应用在方法的参数上。
8. `ElementType.TYPE`:可以应用在类、接口(包括注解类型)或枚举上。
应用场景
限流
上一篇有提到,拦截器+自定义注解实现
权限验证
也是上一篇有提到,拦截器+redis缓存实现。也有SpringSecurity可以实现。这里可以用权限枚举+自定义注解
参数校验
之前的项目用的是validation-api里面的,如果用自定义注解的话,在自定义注解上加上校验类
这个API注解的链接:API注解
注解
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {IsDateTimeValidator.class}) // 标明由哪个类执行校验逻辑
public @interface IsDateTime {
}
校验类
public class IsDateTimeValidator implements ConstraintValidator<IsDateTime, String> {}
记录操作日志√
下面都是用的是切面+自定义注解实现的
自定义日志注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAround {
String value() default "";
}
切面
@Component
@Aspect
public class LogAroundAspect {
@Around(value = "anyMethod() && @annotation(logAround)")
public Object logAround(ProceedingJoinPoint jp, LogAround logAround) throws Throwable {
Logger log = LoggerFactory.getLogger(jp.getTarget().getClass());
String methodName = jp.getSignature().getName();
String value = logAround.value();
if (log.isInfoEnabled()) {
log.info("进入方法:" + value + "----" + methodName);
}
long start = System.currentTimeMillis();
Object o = jp.proceed();
long end = System.currentTimeMillis();
if (log.isInfoEnabled()) {
log.info("方法结束:" + value + "----" + methodName + ", 用时:" + (end - start) + "毫秒");
}
return o;
}
/**
* 第一个*表示要拦截的目标方法返回值任意(也可以明确指定返回值类型)
* 第二个*表示包中或者子包的任意类(也可以明确指定类)
* 第三个*表示类中的任意方法
* 最后面的两个点表示方法参数任意,个数任意,类型任意
*/
@Pointcut("execution(* com.bluewind.shorturl..*.*(..))")
public void anyMethod() {
}
@Pointcut("execution(public * *(..))")
public void anyPublicMethod() {
}
}
运行
事务管理
自定义注解(其实这部分相当于@Transacation了)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTransactional {
}
切面
@Aspect
@Component
public class MyTransactionAspect {
@Autowired
private MyTransactionalUtil transactionalUtil;
@Around(value = "@annotation(com.guqueyue.myTransactional.annotation.MyTransactional)")
public Object around(ProceedingJoinPoint joinPoint) {
Object result = null;
TransactionStatus begin = null;
try {
// 开启事务
begin = transactionalUtil.begin();
// 执行目标方法
result = joinPoint.proceed();
// 提交事务
transactionalUtil.commit(begin);
} catch (Throwable e) {
transactionalUtil.rollback(begin);
log.info("事务回滚...");
e.printStackTrace();
}
return result;
}
}
缓存管理
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheData {
/**
* 自定义缓存key前缀
* 默认:方法名
*/
String keyPrefix() default "";
/**
* 缓存过期时间
* 单位默认:秒,不设置默认10分钟
*/
int expireTime() default 600;
/**
* 缓存过期-随机数
* 单位默认:秒,默认0分钟
* 注:设置后实际过期时间,
* 会在expireTime基础上继续累积(0~randomExpire)之间的秒数,防止缓存大量失效大面积穿透,造成雪崩
*/
int randomExpire() default 0;
/**
* 是否存储为null 的返回
* 注:防止缓存穿透,默认true,建议查询为空时,也进行缓存
*/
boolean storageNullFlag() default true;
}
但是吧,我觉得一个方法可能会被很多地方调用,但是又不是所有调用方都需要缓存
数据源切换
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface DataSource {
String dataSourceName() default DataSourceNames.MYSQLDB1;
}
再加上定义数据源常量和切面实现动态切换数据库
但是吧,不知道什么个应用场景能用上它