蓝桥杯完美的代价java_【进阶之路】自定义注解介绍与实战

在使用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两年半时间的南橘,下面是我的微信,需要之前的导图或者想互相交流经验的小伙伴可以一起互相交流哦。

> ![]()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值