怎样优雅地使用java注解?
9 个回答
优雅的使用Java注解的前提是理解Java注解,并学习优秀的Java注解的使用demo。
注解作用:每当你创建描述符性质的类或者接口时,一旦其中包含重复性的工作,就可以考虑使用注解来简化与自动化该过程。
Java提供了四种元注解,专门负责新注解的创建工作。
比如Junit3和Junit4 ,比如Servlet2与Servlet3 比如Hibernate3与Hibernate4 比如Spring2之后的Spring版本,都引用注解这一机制,作用就是利用注解将一些本来重复性的工作,变成程序自动完成,简化和自动化该过程(PostScript:上述各个组件我也不是很熟悉,具体加入注解的版本是几不一定正确)。
元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解:
1.@Target,
2.@Retention,
3.@Documented,
4.@Inherited
这些类型和它们所支持的类在java.lang.annotation包中可以找到。下面我们看一下每个元注解的作用和相应分参数的使用说明。
@Target
@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
取值(ElementType)有:
1.CONSTRUCTOR:用于描述构造器
2.FIELD:用于描述域
3.LOCAL_VARIABLE:用于描述局部变量
4.METHOD:用于描述方法
5.PACKAGE:用于描述包
6.PARAMETER:用于描述参数
7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
例子:
Entity.java
/***
*
* 实体注解接口
*/
@Target(value = {ElementType.TYPE}) //仅应用于类、接口、enum声明、注解类型
@Retention(value = RetentionPolicy.RUNTIME) //运行时有效
public @interface Entity {
/***
* 实体默认firstLevelCache属性为false
* @return boolean
*/
boolean firstLevelCache() default false;
/***
* 实体默认secondLevelCache属性为false
* @return boolean
*/
boolean secondLevelCache() default true;
/***
* 表名默认为空
* @return String
*/
String tableName() default "";
/***
* 默认以""分割注解
*/
String split() default "";
}
@Retention
@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
取值(RetentionPoicy)有:
1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在运行时有效(即运行时保留)
/***
* 字段注解接口
*/
@Target(value = {ElementType.FIELD})//注解可以被添加在属性上
@Retention(value = RetentionPolicy.RUNTIME)//注解保存在JVM运行时刻,能够在运行时刻通过反射API来获取到注解的信息
public @interface Column {
String name();//注解的name属性
}
-------------------------------------分割线---------------------------------------------------------
一个完整的例子
注解:DBTable.java
package annotations.database;
import java.lang.annotation.*;
@Target(ElementType.TYPE) // 应用于类、接口、enum、注解类型
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
public String name() default "";
}
注解:Constraints.java
package annotations.database;
import java.lang.annotation.*;
@Target(ElementType.FIELD) //用于变量名
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
boolean primaryKey() default false;
boolean allowNull() default true;
boolean unique() default false;
}
注解:SQLString.java
package annotations.database;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
int value() default 0;
String name() default "";
Constraints constraints() default @Constraints;
}
注解:SQLInteger.java
package annotations.database;
import java.lang.annotation.*;
@Target(ElementType.FIELD)//FIELD 用于变量名
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
String name() default "";
Constraints constraints() default @Constraints;
}
类Member.java
package annotations.database;
@DBTable(name = "MEMBER")
public class Member {
@SQLString(30) String firstName;
@SQLString(50) String lastName;
@SQLInteger Integer age;
@SQLString(value = 30,constraints = @Constraints(primaryKey = true))
String handle;
static int memberCount;
public String getHandle() { return handle; }
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public String toString() { return handle; }
public Integer getAge() { return age; }
}
------------------------------分割线-----------------------------------
注解处理器TableCreator.java
下面是一个注解处理器的例子,它将读取一个类文件,并检查其上的数据库注解,并生成用来创建数据库的SQL命令
package com.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import javax.swing.SpringLayout.Constraints;
public class TableCreator
{
public static void main(String[] args) throws Exception
{
if (args.length < 1)
{
System.out.println("arguments: annotated classes");
System.exit(0);
}
for (String className : args)
{
Class<?> cl = Class.forName(className);
DBTable dbTable = cl.getAnnotation(DBTable.class);
if (dbTable == null)
{
System.out.println("No DBTable annotations in class " + className);
continue;
}
String tableName = dbTable.name();
// If the name is empty, use the Class name:
if (tableName.length() < 1)
tableName = cl.getName().toUpperCase();
List<String> columnDefs = new ArrayList<String>();
for (Field field : cl.getDeclaredFields())
{
String columnName = null;
Annotation[] anns = field.getDeclaredAnnotations();
if (anns.length < 1)
continue; // Not a db table column
if (anns[0] instanceof SQLInteger)
{
SQLInteger sInt = (SQLInteger) anns[0];
// Use field name if name not specified
if (sInt.name().length() < 1)
columnName = field.getName().toUpperCase();
else
columnName = sInt.name();
columnDefs.add(columnName + " INT" + getConstraints(sInt.constraints()));
}
if (anns[0] instanceof SQLString)
{
SQLString sString = (SQLString) anns[0];
// Use field name if name not specified.
if (sString.name().length() < 1)
columnName = field.getName().toUpperCase();
else
columnName = sString.name();
columnDefs.add(columnName + " VARCHAR(" + sString.value() + ")" + getConstraints(sString.constraints()));
}
StringBuilder createCommand = new StringBuilder("CREATE TABLE " + tableName + "(");
for (String columnDef : columnDefs)
createCommand.append("\n " + columnDef + ",");
// Remove trailing comma
String tableCreate = createCommand.substring(0, createCommand.length() - 1) + ");";
System.out.println("Table Creation SQL for " + className + " is :\n" + tableCreate);
}
}
}
private static String getConstraints(Constraints con)
{
String constraints = "";
if (!con.allowNull())
constraints += " NOT NULL";
if (con.primaryKey())
constraints += " PRIMARY KEY";
if (con.unique())
constraints += " UNIQUE";
return constraints;
}
} /*
* Output: Table Creation SQL for annotations.database.Member is : CREATE
* TABLE MEMBER( FIRSTNAME VARCHAR(30)); Table Creation SQL for
* annotations.database.Member is : CREATE TABLE MEMBER( FIRSTNAME
* VARCHAR(30), LASTNAME VARCHAR(50)); Table Creation SQL for
* annotations.database.Member is : CREATE TABLE MEMBER( FIRSTNAME
* VARCHAR(30), LASTNAME VARCHAR(50), AGE INT); Table Creation SQL for
* annotations.database.Member is : CREATE TABLE MEMBER( FIRSTNAME
* VARCHAR(30), LASTNAME VARCHAR(50), AGE INT, HANDLE VARCHAR(30) PRIMARY
* KEY);
*/// :~
可以先了解一下什么是注解 ,学习注解的基本用法~下面是我写过的一篇文章,有帮助可点个赞 ~
前言
今天要讲的是注解,对于本章节,最好是有Servlet基础的人查阅~因为单纯是Java基础的话,可能用不上注解这个东西。但如果开发过Servlet,就对@WebServlet
不会陌生。
现在的开发都推崇使用注解来进行开发,这样就可以免去写XML配置了,十分方便的一项技术~
学习注解可以更好地理解注解是怎么工作的,看见注解了就可以想到它的运行原理了~。
如果有错的地方请大家多多包涵并欢迎在评论区指正~
一、什么是注解?
注解:Annotation....
注解其实就是代码中的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相对应的处理。
二、为什么我们需要用到注解?
传统的方式,我们是通过配置文件(xml文件)来告诉类是如何运行的。
有了注解技术以后,我们就可以通过注解告诉类如何运行
例如:我们以前编写Servlet的时候,需要在web.xml文件配置具体的信息
我们使用了注解以后,可以直接在Servlet源代码上,增加注解...Servlet就被配置到Tomcat上了。也就是说,注解可以给类、方法上注入信息。
明显地可以看出,这样是非常直观的,并且Servlet规范是推崇这种配置方式的。
三、基本Annotation
在java.lang包下存在着5个基本的Annotation,其中有3个Annotation我们是非常常见的了。
3.1@Overried
重写注解
如果我们使用IDE重写父类的方法,我们就可以看见它了。那它有什么用呢??
@Overried是告诉编译器要检查该方法是实现父类的...可以帮我们避免一些低级的错误...
比如,我们在实现equals()方法的时候,把euqals()打错了,那么编译器就会发现该方法并不是实现父类的,与注解@Overried冲突,于是就会给予错误。
3.2@Deprecated
过时注解
该注解也非常常见,Java在设计的时候,可能觉得某些方法设计得不好,为了兼容以前的程序,是不能直接把它抛弃的,于是就设置它为过时。
Date对象中的toLocalString()就被设置成过时了
@Deprecated
public String toLocaleString() {
DateFormat formatter = DateFormat.getDateTimeInstance();
return formatter.format(this);
}
当我们在程序中调用它的时候,在IDE上会出现一条横杠,说明该方法是过时的。
3.3@SuppressWarnings
抑制编译器警告注解
该注解在我们写程序的时候并不是很常见,我们可以用它来让编译器不给予我们警告
当我们在使用集合的时候,如果没有指定泛型,那么会提示安全检查的警告
如果我们在类上添加了@SuppressWarnings这个注解,那么编译器就不会给予我们警告了
3.4@SafeVarargs
Java 7“堆污染”警告
什么是堆污染呢??当把一个不是泛型的集合赋值给一个带泛型的集合的时候,这种情况就很容易发生堆污染....
这个注解也是用来抑制编译器警告的注解...用的地方并不多,我也不详细说明了......有用到的时候再回来填坑吧。
3.5@FunctionalInterface
@FunctionalInterface用来指定该接口是函数式接口
用该注解显示指定该接口是一个函数式接口。
四、自定义注解基础
上面讲解的是java.lang包下的5个注解,我们是可以自己来写注解,给方法或类注入信息。
4.1标记Annotation
没有任何成员变量的注解称作为标记注解,@Overried就是一个标记注解
//有点像定义一个接口一样,只不过它多了一个@
public @interface MyAnnotation {
}
4.2元数据Annotation
我们自定义的注解是可以带成员变量的,定义带成员变量的注解叫做元数据Annotation
在注解中定义成员变量,语法类似于声明方法一样....
public @interface MyAnnotation {
//定义了两个成员变量
String username();
int age();
}
注意:在注解上定义的成员变量只能是String、数组、Class、枚举类、注解
有的人可能会奇怪,为什么注解上还要定义注解成员变量??听起来就很复杂了....
上边已经说了,注解的作用就是给类、方法注入信息。那么我们经常使用XML文件,告诉程序怎么运行。XML经常会有嵌套的情况
<书>
<作者>zhongfucheng</作者>
<价钱>22222</价钱>
</书>
那么,当我们在使用注解的时候,也可能需要有嵌套的时候,所以就允许了注解上可以定义成员变量为注解。
4.3使用自定义注解
上面我们已经定义了一个注解了,下面我们来使用它吧
4.3.1常规使用
下面我有一个add的方法,需要username和age参数,我们通过注解来让该方法拥有这两个变量!
//注解拥有什么属性,在修饰的时候就要给出相对应的值
@MyAnnotation(username = "zhongfucheng", age = 20)
public void add(String username, int age) {
}
4.3.2默认值
当然啦,我们可以在注解声明属性的时候,给出默认值。那么在修饰的时候,就可以不用具体指定了。
public @interface MyAnnotation {
//定义了两个成员变量
String username() default "zicheng";
int age() default 23;
}
- 在修饰的时候就不需要给出具体的值了
@MyAnnotation()
public void add(String username, int age) {
}
4.3.3注解属性为value
还有一种特殊的情况,如果注解上只有一个属性,并且属性的名称为value,那么在使用的时候,我们可以不写value,直接赋值给它就行
public @interface MyAnnotation2 {
String value();
}
- 使用注解,可以不指定value,直接赋值
@MyAnnotation2("zhongfucheng")
public void find(String id) {
}
4.4把自定义注解的基本信息注入到方法上
上面我们已经使用到了注解,但是目前为止注解上的信息和方法上的信息是没有任何关联的。
我们使用Servlet注解的时候,仅仅调用注解,那么注解的就生效了。这是Web容器把内部实现了。我们自己写的自定义注解是需要我们自己来处理的。
那现在问题来了,我们怎么把注解上的信息注入到方法上呢???我们利用的是反射技术
步骤可分为三部:
- 反射出该类的方法
- 通过方法得到注解上具体的信息
- 将注解上的信息注入到方法上
//反射出该类的方法
Class aClass = Demo2.class;
Method method = aClass.getMethod("add", String.class, int.class);
//通过该方法得到注解上的具体信息
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
String username = annotation.username();
int age = annotation.age();
//将注解上的信息注入到方法上
Object o = aClass.newInstance();
method.invoke(o, username, age);
当我们执行的时候,我们发现会出现异常...
此时,我们需要在自定义注解上加入这样一句代码(下面就会讲到,为什么要加入这句代码)
@Retention(RetentionPolicy.RUNTIME)
再次执行的时候,我们就会发现,可以通过注解来把信息注入到方法中了。
五、JDK的元Annotation
前面我们已经介绍了java.lang包下的几个基本Annotation了。在JDK中除了java.lang包下有Annotation,在java.lang.annotation下也有几个常用的元Annotation。
在annotation包下的好几个元Annotation都是用于修饰其他的Annotation定义。
5.1@Retention
上面在将注解信息注入到方法中的时候,我们最后加上了@Retention的注解....不然就会报错了..那它是干什么用的呢?
@Retention只能用于修饰其他的Annotation,用于指定被修饰的Annotation被保留多长时间。
@Retention 包含了一个RetentionPolicy类型的value变量,所以在使用它的时候,必须要为value成员变量赋值
value变量的值只有三个:
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
java文件有三个时期:编译,class,运行。@Retention默认是class
前面我们是使用反射来得到注解上的信息的,因为@Retention默认是class,而反射是在运行时期来获取信息的。因此就获取不到Annotation的信息了。于是,就得在自定义注解上修改它的RetentionPolicy值
5.2@Target
@Target也是只能用于修饰另外的Annotation,它用于指定被修饰的Annotation用于修饰哪些程序单元
@Target是只有一个value成员变量的,该成员变量的值是以下的:
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE
}
如果@Target指定的是ElementType.ANNOTATION_TYPE,那么该被修饰的Annotation只能修饰Annotaion
5.3@Documented
@Documented用于指定被该Annotation修饰的Annotation类将被javadoc工具提取成文档。
该元Annotation用得挺少的....
5.4@Inherited
@Inherited也是用来修饰其他的Annotation的,被修饰过的Annotation将具有继承性。。。
例子:
- @xxx是我自定义的注解,我现在使用@xxx注解在Base类上使用....
- 使用@Inherited修饰@xxx注解
- 当有类继承了Base类的时候,该实现类自动拥有@xxx注解
六、注入对象到方法或成员变量上
6.1把对象注入到方法上
前面我们已经可以使用注解将基本的信息注入到方法上了,现在我们要使用的是将对象注入到方法上.....
上边已经说过了,注解上只能定义String、枚举类、Double之类的成员变量,那怎么把对象注入到方法上呢?
6.1.2模拟场景:
- Person类,定义username和age属性,拥有uername和age的getter和setter方法
public class Person {
private String username;
private int age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
- PersonDao类,PersonDao类定义了Person对象,拥有person的setter和getter方法
public class PersonDao {
private Person person;
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
- 现在我要做的就是:使用注解将Person对象注入到setPerson()方法中,从而设置了PersonDao类的person属性
public class PersonDao {
private Person person;
public Person getPerson() {
return person;
}
//将username为zhongfucheng,age为20的Person对象注入到setPerson方法中
@InjectPerson(username = "zhongfucheng",age = 20)
public void setPerson(Person person) {
this.person = person;
}
}
步骤:
①: 自定义一个注解,属性是和JavaBean类一致的
//注入工具是通过反射来得到注解的信息的,于是保留域必须使用RunTime
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectPerson {
String username();
int age();
}
②:编写注入工具
//1.使用内省【后边需要得到属性的写方法】,得到想要注入的属性
PropertyDescriptor descriptor = new PropertyDescriptor("person", PersonDao.class);
//2.得到要想注入属性的具体对象
Person person = (Person) descriptor.getPropertyType().newInstance();
//3.得到该属性的写方法【setPerson()】
Method method = descriptor.getWriteMethod();
//4.得到写方法的注解
Annotation annotation = method.getAnnotation(InjectPerson.class);
//5.得到注解上的信息【注解的成员变量就是用方法来定义的】
Method[] methods = annotation.getClass().getMethods();
//6.将注解上的信息填充到person对象上
for (Method m : methods) {
//得到注解上属性的名字【age或name】
String name = m.getName();
//看看Person对象有没有与之对应的方法【setAge(),setName()】
try {
//6.1这里假设:有与之对应的写方法,得到写方法
PropertyDescriptor descriptor1 = new PropertyDescriptor(name, Person.class);
Method method1 = descriptor1.getWriteMethod();//setAge(), setName()
//得到注解中的值
Object o = m.invoke(annotation, null);
//调用Person对象的setter方法,将注解上的值设置进去
method1.invoke(person, o);
} catch (Exception e) {
//6.2 Person对象没有与之对应的方法,会跳到catch来。我们要让它继续遍历注解就好了
continue;
}
}
//当程序遍历完之后,person对象已经填充完数据了
//7.将person对象赋给PersonDao【通过写方法】
PersonDao personDao = new PersonDao();
method.invoke(personDao, person);
System.out.println(personDao.getPerson().getUsername());
System.out.println(personDao.getPerson().getAge());
③:总结一下步骤
其实我们是这样把对象注入到方法中的:
- 得到想要类中注入的属性
- 得到该属性的对象
- 得到属性对应的写方法
- 通过写方法得到注解
- 获取注解详细的信息
- 将注解的信息注入到对象上
- 调用属性写方法,将已填充数据的对象注入到方法中
6.2把对象注入到成员变量
上面已经说了如何将对象注入到方法上了,那么注入到成员变量上也是非常简单的。
步骤:
①:在成员变量上使用注解
public class PersonDao {
@InjectPerson(username = "zhongfucheng",age = 20) private Person person;
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
②:编写注入工具
//1.得到想要注入的属性
Field field = PersonDao.class.getDeclaredField("person");
//2.得到属性的具体对象
Person person = (Person) field.getType().newInstance();
//3.得到属性上的注解
Annotation annotation = field.getAnnotation(InjectPerson.class);
//4.得到注解的属性【注解上的属性使用方法来表示的】
Method[] methods = annotation.getClass().getMethods();
//5.将注入的属性填充到person对象上
for (Method method : methods) {
//5.1得到注解属性的名字
String name = method.getName();
//查看一下Person对象上有没有与之对应的写方法
try {
//如果有
PropertyDescriptor descriptor = new PropertyDescriptor(name, Person.class);
//得到Person对象上的写方法
Method method1 = descriptor.getWriteMethod();
//得到注解上的值
Object o = method.invoke(annotation, null);
//填充person对象
method1.invoke(person, o);
} catch (IntrospectionException e) {
//如果没有想对应的属性,继续循环
continue;
}
}
//循环完之后,person就已经填充好数据了
//6.把person对象设置到PersonDao中
PersonDao personDao = new PersonDao();
field.setAccessible(true);
field.set(personDao, person);
System.out.println(personDao.getPerson().getUsername());
七、总结
①:注入对象的步骤:得到想要注入的对象属性,通过属性得到注解的信息,通过属性的写方法将注解的信息注入到对象上,最后将对象赋给类。
②:注解其实就是两个作用:
- 让编译器检查代码
- 将数据注入到方法、成员变量、类上
③:在JDK中注解分为了
- 基本Annotation
- 在lang包下,用于常用于标记该方法,抑制编译器警告等
- 元Annotaion
- 在annotaion包下,常用于修饰其他的Annotation定义
- 海量视频资源
- Java精美脑图
- Java学习路线
- 开发常用工具
- 精美整理好的PDF电子书
在公主号下回复「888」即可获取!!
本已收录至我的GitHub精选文章,欢迎Star:https://github.com/ZhongFuCheng3y/3y
求点赞 求关注️ 求分享 求留言 对我来说真的 非常有用!!!