注解的概念:
可以简单的将注解理解为标签(标签里面的内容就是注解内的元素),想钉在哪就钉在哪,比如:钉在类型上(类/接口/枚举),钉在类的数据成员上,钉在类的方法成员上,钉在方法的入参上,甚至钉在别的注解上等等。
官方给的注解的概念:注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非代码本身的一部分。注解对于代码的运行效果没有直接影响。
注解的作用:
注解是附加在代码中的一些元信息,用于一些工具/框架在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。主要如下:
- 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
- 编译阶段时的处理: 软件/工具/框架可以读取注解信息来生成代码/Html文档或者其它处理
- 运行时的处理: 注解信息可以在程序运行的时候被提取出来加以利用
注解的定义:
注解:
注解的定义是 @interface 定义的一种类类型,是个语法糖。注解的超类都是java.lang.Annotation,注解内的属性是域变量,不是方法。 | |
注解定义的语法 | 注解定义的含义 |
@interface MyAnno1 { } | 无内容的注解 |
@interface MyAnno2 { int value(); } | 拥有单值属性的注解 |
@interface MyAnno3 { int value(); String name(); } |
拥有多个属性的注解 |
@interface MyAnno4 { int value() ; String name() default "anno" ; } |
拥有多个属性的注解, 有的属性还携带了默认值 |
@Retention(RetentionPolicy.RUNTIME) @interface MyAnno5{ } |
可以对自定义注解进行元注, 元注解是Java预留的,用来管控自定义注解的生态 |
元注解:
我就简单说它是元注,它是用来管控自定义注解的生态的。元注是java预留的,一共有5种。@Retention @Target @Documented @Inherited @Repeatable
元注解@Retention 管控自定义注解的保留期(/生命) | ||
元注解 | 自定义注解的保留期(/生命) | 元注下自定义注解的生命:说明 |
@Retention | RetentionPolicy.SOURCE | 代码阶段,作用:代码维护人员看看而已 |
RetentionPolicy.CLASS | 编译阶段,作用:给其它工具/框架来使用 |
|
RetentionPolicy.RUNTIME | 运行阶段,作用:代码运行时可以提取自定义注解内容来消费 |
元注解@Target 管控自定义注解可以钉在哪里 ( 默认:无@Target元注的自定义注解可以钉在任何地方 ) |
||
元注解 | 元注下自定义注解的目标范畴 | 自定义注解可以钉在哪里 |
@Target | ElementType.TYPE | 钉在类型(类/接口/枚举)上 |
ElementType.FIELD | 钉在类的数据成员上 | |
ElementType.METHOD | 钉在类的方法成员上 | |
ElementType.PARAMETER | 钉在方法的入参上 | |
ElementType.LOCAL_VARIABLE | 钉在方法的局部变量上 | |
ElementType.CONSTRUCTOR | 钉在类的构造函数上 | |
ElementType.ANNOTATION_TYPE | 钉在别的注解上 | |
ElementType.PACKAGE | 钉在包上 |
元注解@Inherited 管控自定义注解可以被子类继承 |
元注解@Documented 管控自定义注解种的内容可以用来制作JavaDoc文档 |
元注解@Repeatable 是jdk8才引入的,以上的四个元注解中的内容元素都是单值类型的,而@Repeatable中的元素是数组类型的,并且该元素必须是个注解 | |
@interface Persons { //定义:容器
@Repeatable(Persons.class) //元注:Persons容器
@Person(role="artist") //代码中声明注解 |
java预留注解:
标记注解:无内容的注解,仅用来描述现象之用,告诉编译器在编译源代码的时候,注意检查这些标记注解的地方是否满足注解描述的现象。
java预留的标记注解 | 作用 |
@Deprecated | 仅用来说明方法是过时的 |
@Override | 仅用来说明子类中必须重写的父类的方法 |
@FunctionalInterface | 仅用来说明是个函数式接口(/单方法接口) |
@SuppressWanings | 仅用来压抑编译时的警告信息 |
@SafeVarargs | 仅用来压抑编译时的警告信息 只能用于varargs方法,final或static构造函数 |
jdk8引入的类型注解:
能够使用类类型的地方,基本上都能使用类型注解。类型注解的设计,是想通过第三方工具(/插件)对代码在编译的时候有额外的检查,检查类类型是否妥当,从而避免运行时的错误。当然,javac本身而言,一般是不执行这些检查的,除非是javac编译时 指明 javac -processor MyClass.java ,检查类型注解都是靠第三方的工具(/插件),比如:Checker Framework插件。
类型注解的作用:支持在Java程序中做强类型检查。配合第三方插件工具Checker Framework,可以在编译的时候检测出运行时异常RuntimeException(eg:UnsupportedOperationException;NumberFormatException;NullPointerException异常等都是RuntimeException),以提高代码质量。
类型注解钉在想要检查的类类型的前面,想要检查的类类型这个目标,如下表所示
类型注解:钉在类类型的前面,用以检测这个类类型是否妥当 | ||
类型注解 | 含义 | 钉的目标 |
@TypeAnno | 修饰的类类型必须是某某 | ElementType.TYPE_USE |
@MaxLen | 修饰的类类型的最大长度 | ElementType.TYPE_USE |
@NotZeroLen | 修饰的类类型是非零的 | ElementType.TYPE_USE |
@Unique | 修饰的类类型是唯一的 | ElementType.TYPE_USE |
@What | 描述方法的泛型参数 | ElementType.TYPE_PARAMETER |
@EmptyOK | 修饰数据成员 | ElementType.FIELD |
@Recommonded | 修饰方法成员 | ElementType.METHOD |
一个小示例说明jdk8引入的类型注解的意义:
/**
* @NonNull注解:表征obj不能是NULL
* 在利用Checker Framework插件时,编译此类无法通过
*/
public class A{
void sample() {
/*@NonNull*/ Object obj = null; //为了兼容jdk7以前 可以用/**/将类型注解包括进去
}
}
注解的用法:
注解的使用是离不开java反射技术的,当代码中想要获取注解的内容加以消费时,都是从反射着手的。所以说,想用好注解,要先学会java反射。
注解用法三部曲:
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno{ //注解定义
String name() default "12345";
int value();
}
@MyAnno(name="haha",value=123) //注解钉点(钉TYPE / FIELD / METHOD等地方)
class B {}
//官方注解定义:说注解对于代码没影响,指的是注解钉点的类不受影响。不是注解消费的类!
Annotation annos[] = B.class.getAnnotations(); //注解消费 通过反射来获取注解
以上三部曲的完整小示例:
import java.lang.reflect.*;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno{ //注解定义
String name() default "12345";
int value();
}
@MyAnno(name="haha",value=123) //注解钉点
class B {
public static void main(String[] args){
if( B.class.isAnnotationPresent(MyAnno.class) ){
Annotation annos[] = B.class.getAnnotations(); //消费注解
for(Annotation anno : annos){
System.out.println(anno);
System.out.println( ((MyAnno)anno).name() );
System.out.println( ((MyAnno)anno).value() );
System.out.println( anno.annotationType().getName() );
}
}
}
}
一般注解都是结合java反射一起来使用的,那为什么反射就能用起来注解呢?主要是因为:Class对象以及反射的对象(包括Field字段对象,Method方法对象,Constructor构造函数对象,Parameter方法入参对象等)都实现了AnnotatedElement接口,该接口中的方法自然就都可访问。
让我们看看AnnotatedElement接口都有哪些主要的方法:
返回值 | 方法 | 说明 |
boolean | isAnnotationPresent(MyAnno.class) | 钉点上是否有该注解 |
MyAnno | getAnnotation(MyAnno.class) | 获取某注解 |
Annotation[ ] | getAnnotations() | 返回所有注解 |
MyAnno[ ] | getAnnotationsByType(MyAnno.class) | 获取jdk8的重复注解 兼容getAnnotation |
抛开@Inherited继承性,直接钉点上就出现的注解(getDeclare*的若干方法) | ||
MyAnno | getDeclaredAnnotation(MyAnno.class) | |
Annotation[ ] | getDeclaredAnnotations() | |
MyAnno[ ] | getDeclaredAnnotationsByType(MyAnno.class) |
注解的运用场景:
注解有什么用?给谁用?给编译器 或者 框架用的,当然我们自己在代码里也可以通过反射获取注解后加以消费注解中的内容。说白了,注解还是标签,标签是携带着内容的,我们获得标签后,就能消费标签里面的内容。标签钉在哪里,看我们现实的需要而定。
我们感兴趣的其实是:消费标签,至于怎么消费标签,那就五花八门了。标签定义,标签钉点,都不是我们感兴趣的。谈到这里,有个浅显的总结注解的运用场景:我们的业务逻辑主要基于反射机制,反射点(字段/方法/类)这里还想获取更多的外来消息,那就标签去钉反射点,相当于给反射点注入了更多的外来信息以备给我们的基于反射机制的业务主逻辑使用。
至此,我们了解了大量的第三方框架大量使用注解的原因所在,因为它们的主题核心都是基于反射机制的,尤其是springboot更是用了大量的注解。
1:框架:JUnit、Spring、MyBatis、Struts2、Hibernate、springboot等等
2:Controller层 / Service层 / Dao层
常见第三方注解
@Repository 用于标注数据访问组件,即DAO组件
@Service 用于标注业务层组件
@Transactional 声明每一个业务方法开始时都会打开一个事务
@Controller 控制层
@Component 把该中立的类交给spring管理
@Autowired 自动装配,将bean容器里的值自动注入到bean
@Path 处理REST请求,接口路径
奉上一个小示例:自实现dao层的框架:
表:注解Table
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table { //注解Table
String value() default "";
}
字段:注解Column
import java.lang.annotation.*;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Column { //注解Column
String value() default "";
}
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Id{ //注解主键Id
}
将注解钉点到我们的实体对象VO上,实体对象VO代表数据库表中的一行记录
/**
* 实体对象User 代表数据库tb_user表中的一行记录
*/
@Table("tb_user")
public class User {
@Id
@Column("user_id")
private String id;
@Column("user_name")
private String name;
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
这里就是我们自定义的dao框架,框架的主体逻辑是以反射机制来编写的。框架利用反射,以及注解钉点带来的更多额外信息来实现底层的业务逻辑。
/**
* 简易dao框架:以反射机制为主要业务逻辑,消费注解带来的更多的额外内容
*/
import java.lang.reflect.*;
public class DaoTemplate {
public static String selectSQL(Object obj) {
StringBuffer sqlBuf = new StringBuffer();
Class> vo = obj.getClass();
Table table = vo.getAnnotation(Table.class); //获取注解内容:表名
String tableName = table.value();
String sql = " select * from " + tableName + " where 1=1 " ;
sqlBuf.append(sql);
Field[] fileds = vo.getDeclaredFields();
for (Field f : fileds) {
String fieldName = f.getName();
String methodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
try {
Column column = f.getAnnotation(Column.class); //获取注解内容:字段名
if (column != null) {
Method method = vo.getMethod(methodName);
Object fv = method.invoke(obj);
if (fv != null && !fv.equals(""))
if( f.isAnnotationPresent(Id.class) ){ //是主键id
sqlBuf.append(" and " + column.value() + "=" + fv + " ");
}
else{
String sfv = fv.toString();
if (sfv.contains(",")) //支持sql的in()操作
sqlBuf.append(" and " + column.value() + " in (" + sfv + ") ");
else
sqlBuf.append(" and " + column.value() + " like '%" + sfv + "%' ");
}
}
}
} catch (Exception e) { e.printStackTrace(); }
}
return sqlBuf.toString();
}
}
下面我们写个测试类,测试下框架:
public class AnnotationDaoTemplateTest {
public static void main(String[] args) {
User user0 = new User();
User user1 = new User();
user1.setId(10);
User user2 = new User();
user2.setName("haha");
User user3 = new User();
user3.setId(100);
user3.setName("100");
User user4 = new User();
user4.setName("'123','abc','^o^'");
String sql0 = DaoTemplate.selectSQL(user0);
String sql1 = DaoTemplate.selectSQL(user1);
String sql2 = DaoTemplate.selectSQL(user2);
String sql3 = DaoTemplate.selectSQL(user3);
String sql4 = DaoTemplate.selectSQL(user4);
System.out.println(sql0); //查询全表
System.out.println(sql1); //根据主键id查询
System.out.println(sql2); //根据名称字段模糊查询
System.out.println(sql3); //根据主键id和名称来查询
System.out.println(sql4); //用in来查询
}
}
注解的小示例:
点击学习注解小示例,本示例主要体现提取注解内容的7种方法的运用。