注解是一种描述数据的数据。Annotations仅仅是元数据,和业务逻辑无关。
元注解
@Documented
注解是否将包含在JavaDoc中
@Retention
什么时候使用注解,注解的生命周期
RetentionPolicy.SOURCE
在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。
@Override, @SuppressWarnings都属于这类注解。
RetentionPolicy.CLASS
在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式。
RetentionPolicy.RUNTIME
始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们的自定义的注解通常使用这种方式。
@Target
注解用于什么地方。如果不明确指出,该注解可以放在任何地方。
- ElementType.TYPE 描述类、接口或enum声明
- ElementType.FIELD 用于描述实例变量
- ElementType.METHOD
- ElementType.PARAMETER
- ElementType.CONSTRUCTOR
- ElementType.LOCAL_VARIABLE
- ElementType.ANNOTATION_TYPE 另一个注解
- ElementType.PACKAGE 用于记录java文件的package信息
@Inherited
定义该注解和子类的关系,是否允许子类继承该注解。
注解用例
注解的功能很强大,Spring和Hebernate这些框架在日志和有效性中大量使用了注解功能。注解可以应用在使用标记接口的地方。不同的是标记接口用来定义完整的类,但你可以为单个的方法定义注释,例如是否将一个方法暴露为服务。
标示类
示例:写一个任务调度执行DEMO(写的比较随意,用处不大,实际使用需要详细拓展)
定义一个@Job注解
/** * Created by jianlin on 04/27/2018. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface Job { /** * 定时任务名称 */ String name() default ""; /** * 定时任务间隔时间 */ int interval() default 1; }
定义2个任务的实现以及任务的执行
public interface Task {
void execute();
boolean isRunning();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Job {
/**
* 定时任务名称
*/
String name() default "";
/**
* 定时任务间隔时间
*/
int interval() default 1;
}
@Component
@Job(name = "printTask", interval = 10)
public class PrintTask implements Task {
private volatile boolean isRunning = false;
@Override
public void execute() {
System.out.println("current date time is:" + LocalDateTime.now());
isRunning = true;
}
@Override
public boolean isRunning() {
return isRunning;
}
}
@Component
@Job(name = "calculateTask", interval = 5)
public class CalculateTask implements Task {
private volatile boolean isRunning = false;
private long total = 0L;
@Override
public void execute() {
total += ThreadLocalRandom.current().nextInt(1000);
System.out.println(String.format("current time %s, current value: %d", LocalDateTime.now().toString(), total));
isRunning = true;
}
@Override
public boolean isRunning() {
return isRunning;
}
}
@Component
public class TaskMap {
private final Executor threadPool = Executors.newFixedThreadPool(10);
private final ConcurrentHashMap<String, Task> concurrentHashMap = new ConcurrentHashMap<>();
public void addTask(String name, Task task) {
concurrentHashMap.putIfAbsent(name, task);
System.out.println("add task, name: " + name);
}
public boolean getTaskAndExecute(String name) {
Task task = concurrentHashMap.get(name);
if (task != null && !task.isRunning()) {
Job job = task.getClass().getAnnotation(Job.class);
threadPool.execute(() -> {
while (true) {
int interval = job.interval();
task.execute();
try {
TimeUnit.SECONDS.sleep(interval);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
return true;
}
return false;
}
}
遍历Spring容器中的所有类,将@Job注解标示的类加入TaskMap里。
/** * Created by jianlin on 04/27/2018. */ public class SchedulerBeanPostProcessor implements BeanPostProcessor, Ordered, BeanFactoryAware { private TaskMap taskMap; @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { taskMap = beanFactory.getBean(TaskMap.class); } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { Class<?> targetClass = AopUtils.getTargetClass(bean); System.out.println(targetClass); Job job = targetClass.getAnnotation(Job.class); if (job != null && bean instanceof Task) { taskMap.addTask(job.name(), (Task) bean); } return bean; } @Override public int getOrder() { return LOWEST_PRECEDENCE; } }
controller调用执行定时任务
@RestController @RequestMapping("/api") public class ApiController { @Autowired private TaskMap taskMap; @RequestMapping(value = "/executeTask") public String executeTask(@RequestParam(name = "taskName") String taskName) { return taskMap.getTaskAndExecute(taskName) ? "success" : "failed"; } }
代码写的比较简单,实际注解标示在类上还有其他用法,这里只是用于做一个类别筛选
标示属性
示例:写一个类似Spring @Value注解的实现。
定义一个@Value注解
/** * Created by jianlin on 04/27/2018. */ @Documented @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Value { String value() default ""; }
定义一个Class使用该注解
public class Server { @Value("127.0.0.1") private String ip; @Value("${port:8888}") private String port; @Value("${indexPage}") private String indexPage; public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public String getPort() { return port; } public void setPort(String port) { this.port = port; } public String getIndexPage() { return indexPage; } public void setIndexPage(String indexPage) { this.indexPage = indexPage; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("ip", ip) .add("port", port) .add("indexPage", indexPage) .toString(); } }
配置文件
ip=127.0.0.2 port=9900 indexPage=2
具体实现
@Test public void testFiledValueAnnotation() throws Exception { String regex = "\\$\\{(.*?)}"; Pattern pattern = Pattern.compile(regex); Properties properties = new Properties(); InputStream fis = this.getClass().getClassLoader().getResourceAsStream("values.properties"); properties.load(fis); System.out.println(properties); Class serverClass = Server.class; Field[] fields = serverClass.getDeclaredFields(); Server server = (Server) serverClass.newInstance(); for (Field field : fields) { Value value = field.getAnnotation(Value.class); String bindValue = value.value(); if (StringUtils.isBlank(bindValue)) { continue; } String fieldValue = null; Matcher matcher = pattern.matcher(bindValue); if (matcher.find()) { bindValue = matcher.group(1); int index = bindValue.lastIndexOf(":"); if (index < 0) { fieldValue = (String) properties.get(bindValue); } else { String key = bindValue.substring(0, index); fieldValue = properties.get(key) == null ? bindValue.substring(index + 1) : (String) properties.get(key); } } else { fieldValue = bindValue; } String methodName = "set" + String.valueOf(field.getName().charAt(0)).toUpperCase() + field.getName().substring(1); System.out.println(methodName + "------->" + fieldValue); Method method = serverClass.getMethod(methodName, String.class); method.invoke(server, fieldValue); } System.out.println(server); }
输出结果
{indexPage=2, port=9900, ip=127.0.0.2} setIp------->127.0.0.1 setPort------->9900 setIndexPage------->2 Server{ip=127.0.0.1, port=9900, indexPage=2}
注解跟XML区别
XML无需重新打包程序,注解需要重新编译打包。