一、注解基础知识
自定义注解和反射的应用是框架的基础,在我们使用Spring时会发现,Spring为我们提供了大量的注解,这些注解简化了xml配置文件的使用,在我们平时开发中,也会自定义一些注解结合拦截器或者AOP的使用来优化我们的代码,因此,我们需要对Java注解有一个深入的认识。
注解(Annotation)也被称为元数据,是JavaSE5引入的重要特性之一
1 标准注解
Java在java.lang包中内置了三种标准注解
1.1 @Override
表示当前的方法定义将覆盖超类中的方法,即重写
1.2 @Deprecated
编译器会对注解有@Deprecated的元素发出警告信息,通常表示该元素有危险或者有更好的选择,不建议开发人员使用该元素,例如Thread类中的stop()
@Deprecated
public final void stop() {
}
1.3 @SuppressWarnings
关闭不当的编译器警告信息
该注解使用必须添加参数,参数列表如下
例:
@SuppressWarnings(value = "all")
关键字 | 用途 |
---|---|
all | to suppress all warnings (抑制所有警告) |
boxing | to suppress warnings relative to boxing/unboxing operations (抑制装箱、拆箱操作时候的警告) |
cast | to suppress warnings relative to cast operations (抑制映射相关的警告) |
dep-ann | to suppress warnings relative to deprecated annotation (抑制启用注释的警告) |
deprecation | to suppress warnings relative to deprecation (抑制过期方法警告) |
fallthrough | to suppress warnings relative to missing breaks in switch statements (抑制确在switch中缺失breaks的警告) |
finally | to suppress warnings relative to finally block that don’t return (抑制finally模块没有返回的警告) |
hiding | to suppress warnings relative to locals that hide variable(抑制相对于隐藏变量的局部变量的警告) |
incomplete-switch | to suppress warnings relative to missing entries in a switch statement (enum case)(忽略没有完整的switch语句) |
nls | to suppress warnings relative to non-nls string literals( 忽略非nls格式的字符) |
null | to suppress warnings relative to null analysis( 忽略对null的操作) |
rawtypes | to suppress warnings relative to un-specific types when using generics on class params( 使用generics时忽略没有指定相应的类型) |
restriction | to suppress warnings relative to usage of discouraged or forbidden references( 抑制禁止使用劝阻或禁止引用的警告) |
serial | to suppress warnings relative to missing serialVersionUID field for a serializable class( 忽略在serializable类中没有声明serialVersionUID变量) |
static-access | to suppress warnings relative to incorrect static access( 抑制不正确的静态访问方式警告) |
synthetic-access | to suppress warnings relative to unoptimized access from inner classes( 抑制子类没有按最优方法访问内部类的警告) |
unchecked | to suppress warnings relative to unchecked operations( 抑制没有进行类型检查操作的警告) |
unqualified-field-access | to suppress warnings relative to field access unqualified( 抑制没有权限访问的域的警告) |
unused | to suppress warnings relative to unused code( 抑制没被使用过的代码的警告) |
2 元注解
元注解主要是负责注解其他注解
JavaSE5定义了四种元注解,从Java7开始额外添加了三种
2.1 @Target
表示该注解可以用在什么地方
ElementType取值如下
ElementType取值 | 范围 |
---|---|
ElementType.ANNOTATION_TYPE | 可以给一个注解进行注解 |
ElementType.CONSTRUCTOR | 可以给构造方法进行注解 |
ElementType.FIELD | 可以给属性进行注解 |
ElementType.LOCAL_VARIABLE | 可以给局部变量进行注解 |
ElementType.METHOD | 可以给方法进行注解 |
ElementType.PACKAGE | 可以给一个包进行注解 |
ElementType.PARAMETER | 可以给一个方法内的参数进行注解 |
ElementType.TYPE | 可以给一个类型进行注解,比如类、接口、枚举 |
2.2 @Retention
表示需要在什么级别保存该注解信息,RetentionPolicy取值如下
RetentionPolicy取值 | 生命周期 |
---|---|
RetentionPolicy.SOURCE | 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。 |
RetentionPolicy.CLASS | 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。 |
RetentionPolicy.RUNTIME | 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。 |
基本上在我们的开发中都是使用RUNTIME
2.3 @Documented
将此注解包含在Javadoc中
2.4 @Inherited
允许子类继承父类中的注解
例如:
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface TestInherited(){
}
@TestInherited
public class A{
}
public class B extend A{
}
上面的例子中B也被@TestInherited注解
2.5 @SafeVarargs
Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告
2.6 @FunctionalInterface
Java 8 开始支持,标识一个匿名函数或函数式接口
2.7 @Repeatable
Java 8 开始支持,标识某注解可以在同一个声明上使用多次
例如:一个人可能同时具有多个职业
@Repeatable(Persons.class)
@interface Person{
String value() default "";
}
@interface Persons{
Person[] value();
}
@Person("gamer)
@Person("programmer")
class MaoJian{
}
@Repeatable后面括号的类是一个容器注解,容器注解就是存放其他注解的地方,它本身也是一个注解
按照规定,它里面必须有个value属性,属性类型是一个被@Repeatable注解过的注解数组
3 自定义注解
使用@interface自定义注解时,会自动继承java.lang.annotation.Annotation接口
3.1 注解的格式
public @Interface 注解名
3.2 注解的属性
注解的属性也叫做成员变量,注解只有成员变量,没有方法,注解的成员变量在注解的定义中,以无形参的方法的形式来声明,其方法名定义了成员变量的名称,返回值定义了成员变量的类型,默认值用default来指定,注解的属性值类型必须是八种基本数据类型及类,接口,注解以及他们的数组
3.3 示例
现在给出一种自定义注解的例子,使用自定义注解结合策略模式去掉if-else
如果我们要对一个数组进行排序,那么排序算法就有多种选择
3.3.1 自定义策略注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SortHandler {
String value();
}
3.3.2 注解的参数取值
也就是if else的类型,示例列举计数排序,插入排序,希尔排序三种
public interface SortType {
String counter = "1";
String insertion = "2";
String shell = "3";
}
3.3.3 定义所有的类型策略
public interface Sort {
void sort();
}
@SortHandler(SortType.counter)
@Component
public class CounterSort implements Sort {
@Override
public void sort() {
System.out.println("计数排序");
}
}
@SortHandler(SortType.insertion)
@Component
public class InsertSort implements Sort {
@Override
public void sort() {
System.out.println("插入排序");
}
}
@Component
@SortHandler(SortType.shell)
public class ShellSort implements Sort {
@Override
public void sort() {
System.out.println("希尔排序");
}
}
3.3.4 将策略实现类添加到spring容器中
@Component
public class SortHandlerContext {
@Resource
private ApplicationContext applicationContext;
public static Map<String,Class<Sort>> sortHandlerMap = new HashMap<>();
public Sort getSort(String type){
Class<Sort> sortClass = sortHandlerMap.get(type);
if (sortClass == null){
throw new IllegalArgumentException("没有与之对应的排序算法");
}
return applicationContext.getBean(sortClass);
}
}
@Component
public class SortHandlerProcessor implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String,Object> sortMap = applicationContext.getBeansWithAnnotation(SortHandler.class);
sortMap.forEach((k,v)->{
Class<Sort> sort = (Class<Sort>) v.getClass();
String type = sort.getAnnotation(SortHandler.class).value();
SortHandlerContext.sortHandlerMap.put(type,sort);
});
}
}
3.3.5 Service业务逻辑
public interface SortService {
void sort(String type);
}
@Service
public class SortServiceImpl implements SortService {
@Autowired
private SortHandlerContext sortHandlerContext;
@Override
public void sort(String type) {
sortHandlerContext.getSort(type).sort();
}
}
3.3.6 测试
public class AnnoTest {
@Test
public void test(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext("io.github.maojian.demo");
applicationContext.getBean(SortServiceImpl.class).sort("1");
}
}
运行结果如下图所示
二、注解处理器
如果没有用来读取注解的工具,那么注解也不会比注释更有用
注解分为两种:一种是运行时注解,一种是编译期注解
1 运行时注解
运行时注解处理相对比较简单,利用JavaSE5提供的反射机制API在运行时获取注解信息就可以了
在Spring中常用的注解有一个@Value,它的作用是读取配置文件对应的属性并且赋值给属性,我们实现一个赋值功能,省略读取配置文件属性的功能
定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyValue {
String value();
}
使用注解
public class UseMyValue {
@MyValue("素还真")
private String userName;
@Override
public String toString() {
return "UseMyValue{" +
"userName='" + userName + '\'' +
'}';
}
}
利用反射赋值及测试
@Test
public void testRef() throws NoSuchFieldException, IllegalAccessException {
Field field = UseMyValue.class.getDeclaredField("userName");
UseMyValue useMyValue = new UseMyValue();
field.setAccessible(true);
if (field.isAnnotationPresent(MyValue.class)){
MyValue myValue = field.getAnnotation(MyValue.class);
String userName = myValue.value();
field.set(useMyValue,userName);
}
System.out.println(useMyValue);
}
运行结果
2 编译期注解
编译时解析有两种机制,分别简单描述下:
2.1 Annotation Processing Tool
apt自JDK5产生,JDK7已标记为过期,不推荐使用,JDK8中已彻底删除,自JDK6开始,可以使用Pluggable Annotation Processing API来替换它,apt被替换主要有2点原因:
1.api都在com.sun.mirror非标准包下
2.没有集成到javac中,需要额外运行
2.2 Pluggable Annotation Processing API
JSR 269.自JDK6加入,作为apt的替代方案,它解决了apt的两个问题,javac在执行的时候会调用实现了该API的程序,这样我们就可以对编译器做一些增强,这时javac执行的过程如下:
在我们的开发中使用的插件lombok就是基于JSR 269开发的,