注解和反射整理【抄的方便个人查看】
注解和反射整理
一、注解
JDK元注解
java.lang下提供了5个基本的基本注解,分别是@Retention、@Target、@Documented、@Inherited,以及Java8新增的@Repeatable注解、类型注解(指定注解可以用于任何地方)。
@Retention注解
指定注解可以保留多长时间,包括一个RetentionPolicy(枚举)类型的value成员变量。
- RetentionPolicy.Class: 将注解记录在class文件中,运行java程序时,JVM不可获得注解信息,为默认。
- RetentionPolicy.RUNTIME: 编译器将注解记录在calss文件中,JVM可以获得注解信息(用的最多)
- RetentionPolicy.SOURCE: 注解只保留在源代码中。
@Target注解
此注解指定被修饰注解能用于哪些单元,如是用在方法还是用在类或者参数上。
- **ElementType.ANNOTATION_TYPE:**修饰注解
- ElementType.CONSTRUCTOR:只能修饰构造器
- **ElementType.FIELD:**修饰成员变量
- ElementType.LOCAL_VARIABLE:只能修饰局部变量
- ElementType.METHOD:修饰方法
- ElementType.PACKAGE:修饰包定义
- **ElementType.PARAMETER:**修饰参数
- ElementType.TYPE:可以修饰类、接口(包括注解类型)或枚举定义。
@Documented(个人感觉不重要)
@Documented(指定注解具有继承性)
自定义注解
定义及使用注解
直接上例子吧:自定义注解只能使用在成员变量上,使得其能初始化成员变量。
//1.定义@PersonAnno注解,这个注解只能应用于成员变量上。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface PersonAnno {
String name();
int age();
}
//2.提供了Person类,这里省略了
public String name;
public int age;
public String getName() {
return name;
}
//set,get,toString方法....
}
//主类
public class Demo {
/*添加注解,指定属性值,这里只这样写肯定是不能初始化成员变量,因为注解是不能自己生效的,我们必须
提供一个工具告诉这个注解是如何工作的,这个方法如下doAnno()方法。
*/
@PersonAnno(age = 20, name = "张三")
private Person person=new Person();
private Person getPerson() {
return person;
}
public static void main(String[] args) throws Exception {
Demo demo = new Demo();
demo.doAnno();
Person person = demo.getPerson();
System.out.println(person);
}
private void doAnno() throws Exception{
//注意使用getDeclaredField才可以获得私有成员变量值。
//注意全限定类名
PersonAnno annoInfo = Class.forName("org.jcut.day04.Demo").getDeclaredField("person").getAnnotation(PersonAnno.class);
//得到这个注解的两个属性值赋值给成员变量。
int age = annoInfo.age();
String name = annoInfo.name();
person.setAge(age);
person.setName(name);
}
}
- 1
提取注解信息
当自定义了注解后,这些注解不会自动生效,必须由我们开发人员提供相应的工具类来提取和处理注解信息,如上例doAnno()方法。
java.lang.reflect包下新增了AnnoatatedElement接口,并且Class,Method,Constructor等都实现了这个接口,所以它可以通过所提供的如下方法访问注解信息:
- getAnnoatation(Class annnotationClass)
- getDeclaredAnnnotation(Class annotationClass)
- Annotation[] getAnnotations()
- …
Java8新增的重复的注解@Repeatable
直接看怎么使用吧(这个例子是网上粘贴的https://blog.csdn.net/weixin_42245133/article/details/99678509:
//定义Values注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Values {
Value[] value();
}
//定义要重复使用的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(Values.class)
public @interface Value {
String value() default "value";
}
//使用方法
public class AnnotationClass {
@Value("hello")
@Value("world")
public void test(String var1, String var2) {
System.out.println(var1 + " " + var2);
}
}
//测试用例
// 获取使用`@Value`注解的`test`方法,并打印这个方法上的注解长度和信息
@Test
public void testValue() {
Method[] methods = AnnotationClass.class.getMethods();
for (Method method : methods){
if (method.getName().equals("test")) {
Annotation[] annotations = method.getDeclaredAnnotations();
System.out.println(annotations.length);
System.out.println(method.getName() + " = " + Arrays.toString(annotations));
}
}
}
//运行结果:
1
test = [@com.example.annotations.Values(value=[@com.example.annotations.Value(value=hello), @com.example.annotations.Value(value=world)])]
- 1
结果显示,test
方法上的注解长度为 1 , 且打印信息为@Values
注解,它的值包含了使用的两个注解。
因此可知在jdk8中,相同注解只是以集合的方式进行了保存,原理并没有变化。
Java8新增的类型注解
ElementType枚举增加了TYPE_PARAMETER,TYPE_USER两个枚举值,如定义的时候使用@Target(ElemntType.TYPE_USER)修饰,这种注解被称位类型注解,类型注解可以用于修饰在任何地方出现的任何类型。
二、反射
通过反射查看类信息
1.通过注反射获得Class对象
获得Class对象的方式通常有三种:
- 使用Class.forName(String className)静态方法。className为全限定类名。
- 调用某个类的class属性。如Person.class.
- 调用某个对象的getClass()方法。
相比之下,第二种方式更有优势:
- 代码更安全,程序在编译阶段就可以检查需要访问的Class对象是否存在。
- 程序性能更好,因为无需调用方法,所以性能更好。
2.从Class中获取信息
Class类提供了大量的实例方法来获取Class对象所对应的详细信息,详细查看API文档。
3.Java8新增的方法参数反射
Java8在反射包下新增了Executable抽象基类,其提供的方法甚至可以获得形参名。如isNamePresent()方法判断生成的class文件是否包含方法的形参名信息。getName()获得形参名。
需要注意的是:使用javac命令编译的源文件的时候,默认生成的class文件并不包含方法的形参名信息,因此调用isNamePresent()方法时返回false,调用getName()是也不能得到参数的形参名,所以在编译的时候需要为编译命令指定-parameters指令时才可以生成形参信息。
使用反射生成并操作对象(重点)
1.创建对象
在很多JavaEE框架中都需要根据配置文件信息来创建对象,从配置文件读取的知识某个类的字符串类名,程序需要根据该字符串来通过反射来创建对应实例。
下例实现了从配置文件中读取key-value对,然后创建对象并将对象放到HashMap中:
public class ObjectPoolFactory {
private HashMap<String, Object> map=new HashMap<>();
private static Properties prop=new Properties();
static {
//生成流文件
InputStream resource = ObjectPoolFactory.class.getClassLoader().getResourceAsStream("a.properties");
prop=new Properties();
try {
//加载资源文件
prop.load(resource);
} catch (IOException e) {
e.printStackTrace();
}
}
//初始化对象池
public void initPool() throws Exception {
Set<String> stringPropertyNames = prop.stringPropertyNames();
for (String string : stringPropertyNames) {
map.put(string, createObject(prop.getProperty(string)));
}
}
//创建对象实例
private Object createObject(String property) throws Exception {
Class<?> class1 = Class.forName(property);
//调用该类构造器再使用newInstance创建实例对象。
return class1.getConstructor().newInstance();
}
//获取实例对象
private Object getObject(String name){
//从map中获取指定name的实例。
Object object = map.get(name);
return object;
}
public static void main(String[] args) throws Exception {
ObjectPoolFactory factory=new ObjectPoolFactory();
factory.initPool();
Object object = factory.getObject("a");
Object object2 = factory.getObject("b");
System.out.println(object);
System.out.println(object2);
}
}
- 1
另外也可以使用指定构造器来创建java对象。
如上例中Date类为已知类,通常情况下没有使用反射创建该种实例,毕竟通过反射创建对象时性能要稍微低一些。实际上,只有当程序需要动态创建某个类的对象时才会考虑使用反射,所以在通用性框架中大量使用。
2.调用方法
当获得了某个类对应的Class对象,就可以通过该Class对象的getMethods()方法或者getMethod()方法获得全部或者指定的方法。
Method里包含一个invoke()方法,Object invoke(Object obj,Object args):obj为执行该方法的主调,args是执行该方法的时传入的实参。
上例子(这个例子直接粘贴书上的):加强上面的一个例子,(在配置文件中设置一个值,这个值由方法读取到并且利用该类去为对象对应的setter方法设置值)
public class ExtendedObjectPoolFactory {
// 定义一个对象池,前面是对象名,后面是实际对象
private Map<String, Object> objectPool = new HashMap<>();
private Properties config = new Properties();
// 从指定属性文件中初始化Properties对象
public void init(String fileName) {
try {
InputStream resource = ObjectPoolFactory.class.getClassLoader().getResourceAsStream(fileName);
config.load(resource);
} catch (IOException ex) {
System.out.println("读取" + fileName + "异常");
}
}
// 定义一个创建对象的方法
// 该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象
private Object createObject(String clazzName) throws Exception {
// 根据字符串来获取对应的Class对象
Class<?> clazz = Class.forName(clazzName);
// 使用clazz对应类的默认构造器创建实例
return clazz.getConstructor().newInstance();
}
// 该方法根据指定文件来初始化对象池
// 它会根据配置文件来创建对象
public void initPool() throws Exception {
for (String name : config.stringPropertyNames()) {
// 每取出一个key-value对,如果key中不包含百分号(%)
// 这就表明是根据value来创建一个对象
// 调用createObject创建对象,并将对象添加到对象池中
if (!name.contains("%")) {
objectPool.put(name, createObject(config.getProperty(name)));
}
}
}
// 该方法将会根据属性文件来调用指定对象的setter方法
public void initProperty() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
for (String name : config.stringPropertyNames()) {
// 每取出一对key-value对,如果key中包含百分号(%)
// 即可认为该key用于控制调用对象的setter方法设置值
// %前半为对象名字,后半控制setter方法名
if (name.contains("%")) {
// 将配置文件中的key按%分割
String[] objAndProp = name.split("%");
// 取出调用setter方法的参数值
Object target = getObject(objAndProp[0]);
// 获取setter方法名:set + "首字母大写" + 剩下部分
String mtdName = "set" + objAndProp[1].substring(0, 1).toUpperCase() + objAndProp[1].substring(1);
// 通过target的getClass()获取它的实现类所对应的Class对象
Class<?> targetClass = target.getClass();
// 获取希望调用的setter方法
Method mtd = targetClass.getMethod(mtdName, String.class);