日志编号 | 说明 |
---|---|
C-2020-08-11 | 第一次创建 |
反射
反射(reflect)一个贯穿Java始终的技术点,同时也是贯穿Java面试始终的知识点。不论是何种水平的Java开发人员,在面试的时候都有概率被问到Java反射机制。
针对反射过程里的Class是怎么来的,粗略的示意图如下:
获取Class< T >的几种途径:
- Class clazz = Class.forName(" ");
- Class clazz = T.class();
- Class clazz = t.getClass();
上述这三种方法里,第一种我们应该很熟悉,在做JDBC的时候,就是这么去加载驱动,同时也最推荐通过这种方式去获取Class。因为,如果使用第二种方法,会要求目标类已经在当前工程里。第三种方法,实属进行了一个重复的过程。
对于Class< T >对象而言,有两套近乎一样的API,一类是通过Declared开头,另一类是没有Declared开头。这两套API对于不熟悉的同志来说,有一些困扰。简单归纳总结如下:
- Declared开头:获取当前类中定义的方法和属性,是不是public无所谓。
- 没有Declared开头:获取当前类和各层父类中所有public修饰的属性和方法。
注,上述简单归类中,针对没有Declared开头的获取构造器方法,只能获取当前类的公共构造方法,不会拿到父类的构造方法。
一些常用的方法
-
T t = clazz.newInstance();
返回clazz的一个实例,这个方法会执行clazz中定义的无参构造方法。也因为这个,我们都要求不管有用没有到,都要把无参构造方法写上。 -
xx.setAccessible(true);
这个方法很强大,一般都是对方法或者属性使用。如果某个方法或者属性是private或者其他什么限定词,使得这个方法或者属性不能被除过本类之外的其他类使用,或者不能被重写的时候,可以通过这个方法获取对方法,属性的管理权(这里说管理权不太合适,但就是那种感觉)。被设置成true之后,我们就可以对这个方法,属性进行我们希望的更改和操作,并且不用考虑原有的封装性。
自定义注解
首先先说注解,java.lang.annotation.Annotation接口,是JDK1.5中提出的。注解本质上是起到一个辅助说明的作用。用注解我们可以简单的表述出类,方法,属性所具有的特性。当然,我知道我现在说的这个解释与大家印象中的注解和使用场景完全不一样。那是因为,我们基于这种表述,通过反射机制,对满足不同注解的类,方法,属性,进行了特殊操作所致。只是不可否认的是,注解本身就是一个辅助说明的东西。
在Java中本身就给我们提供了一些注解,辅助我们的日常开发,其中,最常见的肯定是@Override,表示方法的重写。今天并不讲解这些注解,只是点一下,让我们能觉得注解离我们并不遥远,他也不是一个神秘莫测的事务。
现在开始将正章,自定义注解。
元注解
这个可能有点绕,什么是元注解?就是用于定义注解的注解,就叫做元注解。
最常使用的元注解包括如下几个:
- @Documented
这个注解表示,自定义注解是否要作用于javaDoc文档。如果一个类型声明是有文档说明的,它的注释将成为注释元素的公共的接口的一部分 - @Inherited
标注这个注解是否自动继承。如果一个类被带有@Inherited的注解标记,那当前类的子类会自动添加上这个注解。 - @Repeatable
Repeatable用于指示其(元)注释的注释类型是可重复的。 - @Retention()
表示注解在什么时候起作用。可选值有SOURCE,CLASS,RUNTIME,分别是源码,编辑,运行时。
三者关系是:SOURCE<CLASS<RUNTIME,一般都用RUNTIME - @Target()
表示当前注解可以添加到哪些层级。()内的属性,就是各种各样的层级
下面列举的是允许出现在Target内的参数。注,target接收的是ElementType[]数组
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
除此之外,还有一个我一直没用过的注解是@ Native,它的解释倒不复杂,是指在实际开发中,我一直没有用过,这里也不好进行具体,因此就不讲这个了。
自定义注解的具体代码示例:
package com.phl.demoone.annotation;
import java.lang.annotation.*;
/**
* 自定义注解的demo
* 自定义注解的写法
* 1,注解必须是@interface这种形式
* 2,在自定义注解上,必须添加元注解进行修饰
*/
@MyAnnotation()
public class AnnotationDemo {
@MyAnnotation(2)
public static void main(String[] args) {
//可以直接在这里进行各种测试
}
}
//下面的是自定义注解和代码注释
/*
* @Documented,这个注解表示,自定义注解是否要作用于javaDoc文档。如果一个类型声明是有文档说明的,
* 它的注释将成为注释元素的公共的接口的一部分
* @Inherited,标注这个注解是否自动继承。如果一个类被带有@Inherited的注解标记,那当前类的父类会自动添加上这个注解。
* @Target,表示当前注解可以添加到哪些层级。()内的属性,就是各种各样的层级
* @Retention,表示注解在什么时候起作用。可选值有SOURCE,CLASS,RUNTIME,分别是源码,编辑,运行时。
* 三者关系是:SOURCE<CLASS<RUNTIME,一般都用RUNTIME
*
* */
@Documented
@Inherited
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
/*
* 在自定义注解内部,可以写很多看起来像是方法的内容。
* 这些只是看起来像方法,其实不是。
* 这些内容,是注解在使用的时候,需要在后面()内填写的属性。比如:
* @MyAnnotation(name = "test", desc = "test")
* 但是,在上述元注解中,@Retention(RetentionPolicy.RUNTIME),里面并没有方法名称。
* 那是因为,如果方法名称是value且只有一个方法时,则可以省略不写方法名
* 同样,这些方法可以设置默认值。当设置了默认值之后,就可以不再指明。
* @MyAnnotation(2),比如这个。虽然有三个方法,但我只给定了第一个方法,其他沿用默认值。
* 又因为我用的方法名是value,因此可以将方法名省略。
*
*
* 注意:方法名省略的写法,只会在()内有且仅有1个方法,并且方法名是value的时候才有用。
* */
int value() default 1;
String name() default "test";
String desc() default "test";
}
使用注解
在使用注解的过程里,肯定是通过反射进行的。
在Class上的时候,提供了这些方法获取注解。
在Method上的时候,也提供了如上述这样的获取方Method上注解的方法。除此之外,还提供了类似getParameterAnnotation这样的,获取方法入参注解的方法,还有这样getAnnotatedReturnType获取返回类型的注解。
在Field上,同样也提供了各种各样的获取注解的方法。
通过这些获取注解的方法,就可以获取到被加到类,方法,属性,参数,返回值上的注解内容。
然后我们根据要处理的目标注解,假设是我们自定义的MyAnnotation ,此时我的目标是,如果一个类被定义了这个注解,并且注解里name的值设置成了out。那么在执行这个类的任意一个方法之前,都会在方法执行之前,把这个类上定义的desc的值打印出来。
首先提一下,这个demo中运动了JDK的动态代理。对应的简易类图是这个样子的.
其中TestHandler是动态代理处理器;
TargetClass和NormalClass分别都实现了ITestInterface接口。其中TargetClass在注解里name配置的是out,desc的内容是test_out.
TestClass里面是通过动态代理的形式进行调用的。
上述类图里涉及的源码如下。
接口ITestInterface:
package com.phl.demoone.annotation;
public interface ITestInterface {
void say();
void print();
}
TestHandler处理器:
package com.phl.demoone.annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TestHandler implements InvocationHandler {
private Object obj;
public TestHandler(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Class<?> aClass = obj.getClass();
MyAnnotation annotation = aClass.getAnnotation(MyAnnotation.class);
if(annotation.name().equalsIgnoreCase("out")){
System.out.println(annotation.desc());
}
Object invoke = method.invoke(this.obj, args);
return invoke;
}
}
TargetClass:
package com.phl.demoone.annotation;
@MyAnnotation(name = "out",desc = "test_out")
public class TargetClass implements ITestInterface{
public TargetClass() {
}
public void say(){
System.out.println("TargetClass method say-----");
}
public void print(){
System.out.println("TargetClass method print-----");
}
}
NormalClass:
package com.phl.demoone.annotation;
@MyAnnotation(name = "normal",desc = "test_out")
public class NormalClass implements ITestInterface{
public NormalClass() {
}
public void say(){
System.out.println("NormalClass method say-----");
}
public void print(){
System.out.println("NormalClass method print-----");
}
}
TestClass测试类:
package com.phl.demoone.annotation;
import java.lang.reflect.Proxy;
public class TestClass {
public static void main(String[] args) {
//实例化对象
TargetClass targetClass = new TargetClass();
NormalClass normalClass = new NormalClass();
//实例化动态代理Handler
TestHandler targetHandler = new TestHandler(targetClass);
TestHandler normalHandler = new TestHandler(normalClass);
//创建动态代理对象targetProxy
ITestInterface targetProxy = (ITestInterface)Proxy.newProxyInstance(targetClass.getClass().getClassLoader(),
targetClass.getClass().getInterfaces(),
targetHandler);
//创建动态代理对象normalProxy
ITestInterface normalProxy = (ITestInterface)Proxy.newProxyInstance(normalClass.getClass().getClassLoader(),
normalClass.getClass().getInterfaces(),
normalHandler);
//执行相应方法
targetProxy.say();
targetProxy.print();
System.out.println("-------------------");
normalProxy.say();
normalProxy.print();
}
}
这里面用到的注解就是自定义注解上面的那个注解,这里不重复粘贴。
运行结果如下图:
根据运行截图,我们成功的根据类注释中不同的值,实现了不同的执行效果。
这个就是一个简易的基于注解内容的应用。