/

java注解概念及运用

注解是一种描述数据的数据。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(写的比较随意,用处不大,实际使用需要详细拓展)

  1. 定义一个@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. 定义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;
   }
}
  1. 遍历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;
        }
    }
    
  2. 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注解的实现。

  1. 定义一个@Value注解

    /**
     * Created by jianlin on 04/27/2018.
     */
    @Documented
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Value {
        String value() default "";
    }
    
    
  2. 定义一个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();
        }
    }
    
  3. 配置文件

    ip=127.0.0.2
    port=9900
    indexPage=2
    

  4. 具体实现

    @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);
        }
    
  5. 输出结果

    {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无需重新打包程序,注解需要重新编译打包。