在使用spring框架的时候,我们经常会感叹注解式编程真是大大简化了开发的时间,几个小小的注解,就能解决一系列的配置问题,让写代码像写诗一样轻松明快。
我们都知道,在spring框架的前期,大多使用XML配置进行开发。XML配置起来有时候冗长,如实体类的映射,使用XML进行开发会显得十分复杂。同时注解在处理一些不变的元数据时有时候比XML方便的多,比如spring 声明式事务管理,如果用XML写的代码会多的多。注解与Java Bean紧密结合,既大大减少了配置文件的体积,又增加了Java Bean的可读性与内聚性。
当然,不管使用注解还是XML,满足需求的前提下,采用最简单的方法才是最合适的。
今天我就以一个简单的例子来给大家讲解,如何进行自定义注解,帮助我们使用注解开发项目。
一、元注解
首先,我们定义一个类需要用到元注解。
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface NotifyMonitor {
String value() default "";
}
1、@Target
@Target注解,是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的。它使用一个枚举类型定义如下:
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
/** 类,接口(包括注解类型)或枚举的声明 */
TYPE,
/** Field declaration (includes enum constants) */
/** 属性的声明 */
FIELD,
/** Method declaration */
/** 方法的声明 */
METHOD,
/** Formal parameter declaration */
/** 方法形式参数声明 */
PARAMETER,
/** Constructor declaration */
/** 构造方法的声明 */
CONSTRUCTOR,
/** Local variable declaration */
/** 局部变量声明 */
LOCAL_VARIABLE,
/** Annotation type declaration */
/** 注解类型声明 */
ANNOTATION_TYPE,
/** Package declaration */
/** 包的声明 */
PACKAGE,
}
就像我们之前定义的,@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD,ElementType.TYPE})就是可以运用在注解、方法和类上。
2、@Retention
@Retention注解,翻译为持久力、保持力。即用来修饰自定义注解的生命力。
注解的生命周期有三个阶段:
- 1、Java源文件阶段;
- 2、编译到.class文件阶段;
- 3、运行期阶段。
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
(注解将被编译器忽略掉)
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
(注解将被编译器记录在class文件中,但在运行时不会被虚拟机保留,这是一个默认的行为)
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*(注解将被编译器记录在.class文件中,而且在运行时会被虚拟机保留,因此它们能通过反射被读取到)
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
我们使用的@Retention(RetentionPolicy.RUNTIME) 是让注解将被编译器记录在.class文件中,而且在运行时会被虚拟机保留,所以它能通过反射被读取到。
3、@Inherited
在注解上使用@Inherited 表示该注解会被子类继承,注意,仅针对类,成员属性、方法并不受此注释的影响。
对于类来说,子类要继承父类的注解需要该注解被 @Inherited 标识。
对于成员属性和方法来说,非重写的都会保持和父类一样的注解,而被实现的抽象方法,被重写的方法都不会有父类的注解。
当@NotifyMonitor注解加在某个类A上时,假如类B继承了A,则B也会带上该注解。
我们可以看到,在springboot,很多类也加上了这个注解。
4、@Documented
除了我们在注解类上应用到的之外,@Documented注解的作用是在使用 javadoc 工具为类生成帮助文档时保留其注解信息。
如果去掉了这个注解,那么在生成的工具文档上就不会出现这个注解,对于一些内部工具类注解来说可有无可。
二、利用AOP实现自定义注解
我们来实现下面这个场景,执行一个任务,如果任务报错,我们就通过钉钉通知指定的人员让他进行处理。
要实现这个功能,我们可能会想到try-catch方式。当然,没有什么不对,但是如果要在一百个不同的方法中加入这个逻辑,岂不是要实现100次?于是乎,使用自定义注解的方式或许是不错的主意。
我写了一个类来实现上诉方法:
@Slf4j
@Aspect
@Component
public class NotifyMonitorAspect {
@Autowired
private DingDingOpe dingDingOpe;
@Autowired
private StringRedisTemplate stringRedisTemplate;
//相比大家对aop都不会陌生
@Pointcut("@annotation(com.nanju.aop.NotifyMonitor)")
private void monitor() {}
/**
* 处理任务
point.proceed()是用来执行原来的任务
dingDingOpe.sendRobotMsg 是自定了一个方法用来通知钉钉
*
* @param point
*/
@Around("monitor()")
public Object doAround(ProceedingJoinPoint point) {
String jobName = getJobName(point);
Object object = null;
try {
object = point.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
String url = getUrl(jobName);
dingDingOpe.sendRobotMsg(url, "任务处理失败:"+"{"+throwable.getMessage()+"}", false);
}
return object;
}
/**
* 获取Job名称,这个方法就是利用了NotifyMonitor中的value值,根据不同的方法使用不同的通知
* @param point 切点
*/
private String getJobName(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
NotifyMonitor jobs = method.getAnnotation(NotifyMonitor.class);
if ("".equals(jobs.value())){
return null;
}
return jobs.value();
}
/**
* 根据Job名称获取通知地址,使用了stringRedisTemplate,提前将输入埋入redis,也可以放在数据库里,配置通知地址
* @param notifyMonitor
*
*/
private String getUrl(String notifyMonitor) {
if (Objects.isNull(notifyMonitor)){
return TextUtils.dealNull(stringRedisTemplate.opsForValue().get("warn:dingdingUrl:"));
}else{
return TextUtils.dealNull(stringRedisTemplate.opsForValue().get("warn:dingdingUrl:"+notifyMonitor));
}
}
}
我们测试一下
1、在方法上加上注解@NotifyMonitor
2、调用方法
3、执行成功
我们还可以尝试一下,在@NotifyMonitor加上value(因为只有一个属性,所以value=”xxx” 与 “xxx” 等价)
4、执行结果
这样,一个注解式的任务处理、通知功能就完成了。自定义注解不仅能够在方法执行前后进行扩展、获取到实现注解的方法、所在类等信息、修改参数和返回值,还能够实现包括线程池、分布式锁、类数据校验等等你能想到的大部分操作,我在工作中也实现了其中一些功能,减少了大量的重复代码,也让代码的可读性提高了。
了解到这里,不妨你也自己动手来写一个自定义注解来简化我们的项目吧。
大家好,我是练习java两年半时间的南橘,下面是我的微信,需要之前的导图或者想互相交流经验的小伙伴可以一起互相交流哦。