最先接触注解的时候,仍是在继承那里讲到覆盖父类的方法的时候,子类覆盖了父类的方法的时候:html
@Override
public int hashCode() {
return Objects.hash(XXX);
}
复制代码
这个@Override注解用以标识这个方法覆盖了父类的方法,其实把注解去掉也没事。那么问题就来了,注解在代码中究竟是怎么样一种存在,既然无关紧要,为何还用途这么广呢?
引用《Java编程思想》中的话来定义注解的话,就是注解(也被称为元数据)为咱们在代码中添加信息提供了一种形式化的方法,使咱们在稍后某个时刻很是方便地使用这些数据。其实说到底,注解就是类中的一小段信息,能够携带参数,能够在运行时被阅读。可是要知道,这小段信息编译器是不会去识别而且解析的,只是为咱们去实现本身的逻辑所服务的。git
前面提到的@Override是Java SE5内置注解中的其中一种,在这先详细介绍这三种内置注解:
程序员
那么如何新建一个符合咱们需求的注解呢?与新建类同样:github
public @interface SpringBootApplication {
}
复制代码
注意:注解都隐含的继承了Annotation类。
点击运行的话,能够看到在target目录下,为注解也生成了一个临时文件 数据库
@SpringBootApplication
public class MyStringHashMapDecorator {
@SpringBootApplication
HashMap hashMap = new HashMap();
@SpringBootApplication
public void put(String key, String value) {
hashMap.put(key, value);
}
复制代码
但有时候咱们必须限制注解可以标记的位置,或者想保留注解到运行期间,这时候就须要元注解(标记注解的注解)了,Java目前只内置了五种元注解:编程
注解 |
含义
|
---|---|
@Target (默认状况下是能够标记在任何元素上) |
表示该注解能够用于什么地方。可选范围包括: CONSTRUCTOR:构造器的声明 FIELD:域声明(包括enum) LOCAL_VARIABLE:局部变量声明 METHOD:方法声明 PACKAGE:包声明 PARAMETER:参数声明 TYPE:类、接口(包括注解类型)或者enum枚举声明 ANNOTATION_TYPE:标记注解的注解 在JDK1.8以后,新添了两个: TYPE_USE:类型使用声明 TYPE_PARAMETER:类型参数声明 |
@Retention (默认为CLASS级别) |
表示须要在什么级别保存该注解信息。 可选的RetentionPolicy参数包括: SOURCE:注解将会被编译器丢弃 CLASS:注解在class文件中可用,但会被VM丢弃 RUNTIME:VM将在运行期也保留注解,所以能够经过反射机制读取注解的信息 |
@Document | 将此注解包含在Javadoc中 |
@Inherited | 容许子类继承父类的注解 |
@Repeatable | 容许注解重复 |
public class MyStringHashMapDecorator {
String name;
@SpringBootApplication
public MyStringHashMapDecorator(String name) {
this.name = name;
}
复制代码
@SpringBootApplication
public static final int number=1;
@SpringBootApplication
String name;
@SpringBootApplication
HashMap hashMap = new HashMap();
enum spell{
@SpringBootApplication
a,
b,
c,
d
}
复制代码
public void put(String key, String value) {
@SpringBootApplication
int i=0;
}
复制代码
@SpringBootApplication
public void put(String key, String value) {
}
复制代码
@SpringBootApplication
package com.github.hcsp.test;
复制代码
public void put(@SpringBootApplication String key, String value) {
}
复制代码
@SpringBootApplication
enum spell{
a,
b,
c,
d
}
复制代码
@SpringBootApplication
public @interface SpringBootApplications {
}
复制代码
public class MyStringHashMapDecorator<@SpringBootApplication T>
复制代码
通常状况下,重复声明注解都是会报错的: 缓存
public @interface SpringBootApplications {
SpringBootApplication[] value();
}
@Repeatable(SpringBootApplications.class)
public @interface SpringBootApplication {
}
复制代码
在注解中一样能够声明成员变量。可是对成员变量的类型有要求:bash
注意:不能使用Integer等装箱类型,也不能使用Object。
同时,还能够给注解元素设置默认值:
框架
public @interface SpringBootApplication {
public int id() default 0;
public String klass() default " ";
}
复制代码
可是注意:默认值不能为null,换句话说就是不能有不肯定的值。dom
若是不人为对注解进行处理,注解就相似于注释了。因此,下面用两个例子来介绍注解在平常中的用处:
public class AnnotationPractise {
private static void queryDatabase(){
System.out.println("queryDatabase...");
}
private static void insertDatabase(){
System.out.println("insertDatabase...");
}
public static void main(String[] args) {
queryDatabase();
selectDatabase();
}
}
复制代码
查询数据库和往数据库中插入数值的操做,如今咱们想每次操做开始前,都显示一下当时的时间,好比这样:
14:51:58.210
queryDatabase...
14:51:58.210
14:51:58.211
selectDatabase...
14:51:58.211
复制代码
这并非什么难事,在先后加上LocalTime.now便可,可是问题来了,若是我有成百上千个方法都要这样作呢,兴许某一天我不想要时间了,而是加一些别的话呢,没有人会去一个一个改吧?因此这时候就能够用上注解了。
实现思路在于,新建一个Log注解,为每一个须要添加额外信息的方法添加Log注解,接着在主函数中利用反射机制,拿到类中的全部方法,过滤出其中带有Log标记的方法,使用动态字节码加强技术,实现功能的扩展。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)//保留到运行期间,不然不能经过反射拿到方法
public @interface Log {
}
复制代码
public class AnnotationPractise {
@Log
public void queryDatabase() {
System.out.println("queryDatabase...");
}
@Log
public void selectDatabase() {
System.out.println("selectDatabase...");
}
public void foo() {
System.out.println("noLog...");
}
}
复制代码
接下来使用动态字节码加强技术实现功能,这一步可选的方法有不少:
在这里,使用byte-buddy实现,固然也有中文版的:Byte Buddy 教程,先引入ByteBuddy maven仓库。接着根据教程中的代码,修改为符合本身需求的代码:
/**
* 实现动态加强字节码
* @return 返回新的AnnotationPractise实例
* @throws NoSuchMethodException 方法没有找到
* @throws IllegalAccessException 访问了private方法
* @throws InvocationTargetException 被访问的方法丢出了异常,可是没有被接收
* @throws InstantiationException 不能建立这样一个实例,好比说是抽象类和接口
*/
private static AnnotationPractise enhanceAnnotation() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
return new ByteBuddy()
//动态的生成AnnotationPractise的子类
.subclass(AnnotationPractise.class)
//匹配带Log注解的方法
.method(ElementMatchers.isAnnotatedWith(Log.class))
//将方法拦截并委托给LoggerInterceptor
.intercept(MethodDelegation.to(LoggerInterceptor.class))
//建立一个新的AnnotationPractise实例
.make()
.load(Main.class.getClassLoader())
.getLoaded()
.getConstructor()
.newInstance();
}
复制代码
/**
* 实现截取父类中带Log注解的方法,而后添加输出语句
*/
public static class LoggerInterceptor {
public static void log(@SuperCall Callable<Void> zuper)
throws Exception {
System.out.println(LocalTime.now());
try {
zuper.call();
} finally {
System.out.println(LocalTime.now());
}
}
}
}
复制代码
效果以下:
08:51:56.916
selectDatabase...
08:51:56.917
08:51:56.917
queryDatabase...
08:51:56.917
Process finished with exit code 0
复制代码
能够看到,我没有对源代码作任何修改,就实现了对源代码功能的加强。从这里能够看出,注解就是为咱们本身的业务逻辑所服务的,再次印证了那句话“若是对注解不作处理,那么注解也不会比注释有用”。
意思就是模拟数据库的查找操做,第一次查找的时候将获取的数值加入到缓存中,若是第二次查找的仍是这个数则直接返回这个数。这样就省去了重复查找同一个数的时间。
实现思路:
前部分与上例中相似,显示截取类中带有Cache注解的方法,对其实现动态字节码加强。
新建注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface Cache {
// 标记缓存的时长(秒),默认60s
int cacheSeconds() default 60;
}
复制代码
新建两个测试方法,一个带注解一个不带
public class DataService {
/**
* 根据数据ID查询一列数据,有缓存。
*
* @param id 数据ID
* @return 查询到的数据列表
*/
@Cache
public List<Object> queryData(int id) {
// 模拟一个查询操做
Random random = new Random();
int size = random.nextInt(10) + 10;
return IntStream.range(0, size)
.mapToObj(i -> random.nextInt(10))
.collect(Collectors.toList());
}
/**
* 根据数据ID查询一列数据,无缓存。
*
* @param id 数据ID
* @return 查询到的数据列表
*/
public List<Object> queryDataWithoutCache(int id) {
// 模拟一个查询操做
Random random = new Random();
int size = random.nextInt(10) + 1;
return IntStream.range(0, size)
.mapToObj(i -> random.nextBoolean())
.collect(Collectors.toList());
}
}
复制代码
使用ByteBuddy加强原方法:
@SuppressWarnings("unchecked")
public static <T> Class<T> decorate(Class<T> klass) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
return (Class<T>) new ByteBuddy()
//子类化,为原先的类添加新的功能
.subclass(klass)
.method(ElementMatchers.isAnnotatedWith(Cache.class))
.intercept(MethodDelegation.to(CacheInterceptor.class))
.make()
.load(klass.getClassLoader())
.getLoaded();
}
复制代码
拦截器中的具体内容:
public class CacheInterceptor {
private static ConcurrentHashMap<CacheKey, CacheValue> hashMap = new ConcurrentHashMap<>();
@RuntimeType //这个注解的做用在于当拦截方法发生时,ByteBuddy可以找到对应的方法去调用它
public static Object cache(
//声明调用父类的方法
@SuperCall Callable<Object> superCall,
//当前正在被调用的方法
@Origin Method method,
//实体方法的对象
@This Object object,
//得到全部的参数
@AllArguments Object[] arguments) throws Exception {
CacheKey cacheKey = new CacheKey(method.getName(), object, arguments);
CacheValue value = hashMap.get(cacheKey);
if (value != null) {
//缓存中存在对应的值直接返回
if (isNotOvertime(method, value)) {
return value.result;
}
return getObject(superCall,cacheKey);
} else {
//不然调用父类中的方法,拿到值
//方法一:使用method.invoke(object,arguments)调用方法,这样很慢
//方法二:使用ByteBuddy提供的@SuperCall调用父类方法
return getObject(superCall, cacheKey);
}
}
private static boolean isNotOvertime(@Origin Method method, CacheValue value) {
return (System.currentTimeMillis()-value.time)<method.getAnnotation(Cache.class).cacheSeconds()*1000;
}
private static Object getObject(@SuperCall Callable<Object> superCall, CacheKey cacheKey) throws Exception {
Object realResult = superCall.call();
long time = System.currentTimeMillis();
CacheValue cacheValue = new CacheValue(realResult,time);
hashMap.put(cacheKey, cacheValue);
return realResult;
}
static class CacheValue{
private Object result;
private long time;
public CacheValue(Object result, long time) {
this.result = result;
this.time = time;
}
}
static class CacheKey {
private String methodName;
private Object thisObject;
private Object[] arguments;
public CacheKey(String methodName, Object thisObject, Object[] arguments) {
this.methodName = methodName;
this.thisObject = thisObject;
this.arguments = arguments;
}
//Map的key遵循的是equals和hashcode约定,CacheKey必须重写equals和hashCode方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CacheKey cacheKey = (CacheKey) o;
return Objects.equals(methodName, cacheKey.methodName) &&
Objects.equals(thisObject, cacheKey.thisObject) &&
Arrays.equals(arguments, cacheKey.arguments);
}
@Override
public int hashCode() {
int result = Objects.hash(methodName, thisObject);
result = 31 * result + Arrays.hashCode(arguments);
return result;
}
}
}
复制代码
其实照这样看来,注解自己起不了多大的做用,关键是看程序员或者框架自己对注解的理解与使用。因此,一方面要熟记基本语法,另外一方面想深究的话能够去看下框架对注解具体是怎么处理的。
1.《Java编程思想》[第四版] (美)Bruce Eckel 著;陈浩鹏译.——北京:机械工业出版社.2007.6(2018.9重印)
2.掘金.《Java注解详解》点击此处查看源文章
3.博客园.《@SuppressWarning 抑制警告注解》点击此处查看源文章