18反射与注解
反射
反射与泛型、通配符密不可分。“Class类对象”=“Class对象”=“类对象”。
一、概念的区分
先区分下Class<T>、Class<?>、Class、Collection<E>与Map<K,V>、Collection<?>与Map<?,?>、Object类与Class<T>类之间的区别。
1、Class<T>
Class<T>:是一个类且是一个泛型类,具体化T类型后,就实例化为T类型所对应的Class类对象,对象名为stringClass。通过T.class创建。
// 创建String类所对应的Class类对象的引用类型变量,命名为stringClass
Class<String> stringClass;
// 将对象变量stringClass实例化为具体的String类所对应的Class类对象
stringClass = String.class;
2、Class<?>
Class<?>:指各种泛型Class(Class<String>、Class<Integer>、Class<Double>、……)的父类。因为与具体化的Class<T>类有继承关系,所以适用于继承分析中的四种实例化对象方式,可以用多态的方式进行实例化。通过Class.forName(“全类名”)和对象.getClass()创建。
// 1、直接实例化,父类引用类型变量指向实例化父类对象
Class<?> fatherClass = Class.forName("java.lang.String");
// 或:Class<?> fatherClass = "".getClass();
// 2、父类引用类型变量指向子类引用类型变量的向上转型 = 父类引用类型变量指向实例化子类对象
Class<String> stringClass = String.class;
Class<?> fatherClass = (Class<?>)stringClass;
// 父类引用类型变量指向实例化子类对象 = 父类引用类型变量指向子类引用类型变量的向上转型
Class<?> fatherClass = String.class;
// 3、子类引用类型变量指向实例化子类对象
Class<String> stringClass = String.class;
// 4、子类引用类型变量指向父类引用类型变量的向下转型(向下转型之前一定要进行向上转型!,不能直接向下转型)
Class<?> fatherClass = String.class; // 父类引用类型变量指向子类引用类型变量的向上转型
Class<String> stringClass = (Class<String>)fatherClass;
3、Class
Class是Class<T>泛型类的类名,用于调用Class<T>泛型类中的静态方法。
还有一种情况:Class是Class<?>的省略<?>的写法,不推荐使用。
4、Collection<E>与Map<K,V>
Collection<E>:是一个类且是一个泛型类,实例化为E类型对象所组成的集合。
Map<K,V>:是一个类且是一个泛型类,实例化为K类型对象与V类型对象组成的键值对集合。
5、Collection<?>与Map<?,?>
Collection<?>:指各种泛型Collection的父类。
Map<?,?>:指各种泛型Map的父类。
像Class<?>一样,可以用多态的方式进行实例化。
6、Object类与Class<T>类
Object类是所有Java类的父类,即使不声明,也是默认继承了Object类。Class<T>类也是继承于Object类。
Class<T>类用于Java的反射机制,所有Java类都有一个对应的Class对象,他是一个final类。
7、泛型方法
泛型方法的声明,必须在方法的修饰符之后,返回值声明之前。
其中第一个<T>是泛型形参用于表示这是一个泛型方法,后面的T是返回值的类型,代表方法必须返回T类型(由传入参数Class<T>决定)。
// 解释以下getBean方法:
<T> T getBean(Class<T> v) throws BeansException;
二、Class对象
(一)获取Class对象
1、Class.forName(“全类名”)
将字节码文件加载进内存,返回Class<?>对象。多用于配置文件,将类名定义在配置文件中。读取文件,加载类。
2、类名.class
叫做“类字面量”。通过类名的属性class获取,返回Class<T>对象。用于参数的传递。在编译时就可以确定。
3、对象.getClass()
getClass()方法在Object类中定义着,返回Class<?>对象。用于对象获取字节码的方式。在运行时才可以确定。
注意:
方法 | 说明 |
---|---|
public static Class<?> forName(String className) throws ClassNotFoundException | Class<T>类中的静态方法。注意返回值是Class<?>,不是Class<T>。 |
public final native Class<?> getClass() | Object类中的成员方法。注意返回值是Class<?>,不是Class<T>。 |
(二)Class对象功能
1、获取成员变量Field对象
Field[] getFields():获取所有public修饰的成员变量
Field getField(String name):获取指定名称的public修饰的成员变量
Field[] getDeclaredFields():获取所有的成员变量,不考虑修饰符
Field getDeclaredField(String name):获取指定名称的成员变量,不考虑修饰符
2、获取成员方法Method对象
Method[] getMethods()
Method getMethod(String name, 类<?>... parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, 类<?>... parameterTypes)
3、获取构造方法Constructor对象
Constructor<?>[] getConstructors()
Constructor<T> getConstructor(类<?>... parameterTypes)
Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
Constructor<?>[] getDeclaredConstructors()
4、获取全类名
String getName()
三、Constructor对象
(一)获取Constructor对象
Constructor<?>[] getConstructors()
Constructor<T> getConstructor(类<?>... parameterTypes)
Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
Constructor<?>[] getDeclaredConstructors()
(二)Constructor对象功能
1、创建对象
T newInstance(Object... initargs)
四、Field对象
(一)获取Field对象
Field[] getFields():获取所有public修饰的成员变量
Field getField(String name):获取指定名称的public修饰的成员变量
Field[] getDeclaredFields():获取所有的成员变量,不考虑修饰符
Field getDeclaredField(String name):获取指定名称的成员变量,不考虑修饰符
(二)Field对象功能
1、设置值
void set(Object obj, Object value)
2、获取值
get(Object obj)
3、忽略访问权限修饰符
setAccessible(true)
五、Method对象
(一)获取Method对象
Method[] getMethods()
Method getMethod(String name, 类<?>... parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, 类<?>... parameterTypes)
(二)Method对象功能
1、执行方法
Object invoke(Object obj, Object... args)
2、获取方法名
String getName
六、通过反射获取泛型和注解
(一)获取泛型
Java新增了ParameterizedType、GenericArrayType、TypeVariable和WildcardType类型来表示不能被归一到Class类中的类型,但又和原始类型齐名的类型。
- ParameterizedType:表示参数化类型,比如Collection<String>。
- GenericArrayType:表示元素类型是参数化类型或类型变量的数组类型。
- TypeVariable:是各种类型变量的公共父接口。
- WildcardType:表示一种通配符类型表达式。
public class ReflectGeneric {
// 测试方法01
public void test01(Map<String, String> map, List<String> list){
System.out.println("test01");
}
// 测试方法02
public Map<String, String> test02(){
System.out.println("test02");
return null;
}
// 主方法
public static void main(String[] args) throws Exception {
// 通过反射获取test01方法的Method对象
Method method01 = ReflectGeneric.class.getMethod("test01", Map.class, List.class);
// 通过test01方法的Method对象的getGenericParameterTypes方法获取泛型参数的类型,因为参数可能有多个所以是数组
Type[] genericParameterTypes = method01.getGenericParameterTypes();
// 遍历该数组得到参数的类型
for (Type genericParameterType : genericParameterTypes) {
System.out.println(genericParameterType);
// 判断方法的参数类型是不是参数化类型
if (genericParameterType instanceof ParameterizedType){
// 是参数化类型,调用参数化类型的getActualTypeArguments方法获取参数化类型的真实类型
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
System.out.println();
// 通过反射获取test02方法的Method对象
Method method02 = ReflectGeneric.class.getMethod("test02");
// 通过test02方法的Method对象的getGenericReturnType方法获取方法返回值类型,因为只有一个返回值所以不是数组
Type genericReturnType = method02.getGenericReturnType();
System.out.println(genericReturnType);
// 判断方法的返回值类型是不是参数化类型
if (genericReturnType instanceof ParameterizedType){
// 是参数化类型,调用参数化类型的getActualTypeArguments方法获取参数化类型的真实类型
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
}
(二)获取注解
ORM(对象关系映射,Object Relationship Mapping)的三个原则。
- 类和表结构对应。
- 属性和字段对应。
- 对象和记录对应。
只能通过被注解注释的类的反射来获取相应的注解。无法直接反射注解。使用在框架设计中。
示例:通过反射获取注解的属性值。
public class RefelctAnnotation {
public static void main(String[] args) throws Exception {
// 获取被注解的类的Class对象
Class<?> clsStudent = Class.forName("com.looper.springdemo.Student");
// 通过Class对象的getAnnotations方法获取全部注解,因为可有多个注解所以是数组
Annotation[] annoStudents = clsStudent.getAnnotations();
/*for (Annotation annoStudent : annoStudents) {
System.out.println(annoStudent);
}*/
// 获取Table注解的value的值
Table annoTable = clsStudent.getAnnotation(Table.class);
String value = annoTable.value();
// System.out.println(value);
// 获取指定字段的FieldTable注解的属性值
Field fieldName = clsStudent.getDeclaredField("name");
FieldTable annoField = fieldName.getAnnotation(FieldTable.class);
String columnName = annoField.columnName();
String type = annoField.type();
int length = annoField.length();
// System.out.println(columnName);
// System.out.println(type);
// System.out.println(length);
}
}
/**
* 创建一个简单的ORM类
*/
@Table("db_student")
class Student{
@FieldTable(columnName = "id", type = "int", length = 10)
private int id;
@FieldTable(columnName = "age", type = "int", length = 10)
private int age;
@FieldTable(columnName = "name", type = "varchar2", length = 255)
private String name;
public Student() {
}
public Student(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
/**
* @author looper
* 类名注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table {
String value();
}
/**
* @author looper
* 属性注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldTable {
String columnName();
String type();
int length();
}
七、使用反射生成动态代理
1、使用Proxy与InvocationHandler创建动态代理
在Java的java.lang.reflect 包下提供了一个Proxy类和一个InvocationHandler。使用这个类和接口生成JDK动态代理类或对象。
Proxy类提供了创建动态代理类和动态代理对象的静态方法。它也是所有动态代理类的父类。如果为若干个接口动态地生成实现类,可以使用Proxy来创建动态代理类;如果为若干个接口动态地创建实例,可以使用Proxy来创建动态代理实例。
Proxy创建动态代理类和动态代理实例的静态方法
方法 | 说明 |
---|---|
static Class<?> getProxyClass(ClassLoader loader, Class<?>… interfaces) | 创建一个动态代理类所对应的Class对象,该代理类将实现interfaces所指定的多个接口。ClassLoader参数指定生成动态代理类的类加载器。 |
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) | 创建一个动态代理对象,该代理对象的实现类实现了interfaces指定的系列接口,执行代理对象的每个方法时都会被替换执行InvocationHandler对象的invoke方法。 |
实际上,即使采用第一个方法生成动态代理类之后,如果程序需要通过该代理类创建对象,依然需要传入一个InvocationHandler对象。也就是说,系统生成的每个代理对象都有一个与之关联的InvocationHandler对象。
2、动态代理和AOP
以上图示展示了动态代理解耦的过程。
示例:为两个接口创建动态代理,以动态代理方式将相同的代码段与接口实现类解耦。
/**
* 定义接口
*/
interface Dog{
/**
* 接口方法1
*/
void walk();
/**
* 接口方法2
* @return 狗叫声
*/
String bark();
/**
* 接口方法3
* @param foodName 食物名
*/
void eat(String foodName);
}
interface Bird{
/**
* 接口方法1
*/
void fly();
/**
* 接口方法2
* @return 鸟叫声
*/
String chirp();
/**
* 接口方法3
* @param seedName 食物名
*/
void peck(String seedName);
}
/**
* 定义接口实现类,实现接口的特殊方法
*/
class ImpDog implements Dog{
/**
* 特用代码块1
*/
@Override
public void walk() {
System.out.println("狗跑步");
}
/**
* 特用代码块2
* @return 狗叫声
*/
@Override
public String bark() {
return "汪汪汪";
}
/**
* 特用代码块3
* @param foodName 食物名
*/
@Override
public void eat(String foodName) {
System.out.println("狗吃:" + foodName);
}
}
class ImpBird implements Bird{
/**
* 特用代码块1
*/
@Override
public void fly() {
System.out.println("鸟飞翔");
}
/**
* 特用代码块2
* @return 鸟叫声
*/
@Override
public String chirp() {
return "唧唧唧";
}
/**
* 特用代码块3
* @param seedName 食物名
*/
@Override
public void peck(String seedName) {
System.out.println("鸟啄:" + seedName);
}
}
/**
* 定义工具类,用于定义通用方法
*/
class AnimalUtil {
/**
* 通用方法1
*/
public void method1(){
System.out.println("模拟第一个通用方法");
}
/**
* 通用方法2
*/
public void method2(){
System.out.println("模拟第二个通用方法");
}
}
/**
* InvocationHandler接口的实现类
* 必须实现该接口才能为若干个其他接口动态地生成实现类
* 该实现类的invoke()方法将作为代理对象的方法实现
*/
class ImpInvocationHandler implements InvocationHandler{
/**
* 需要被代理的对象
*/
private Object target;
public void setTarget(Object target) {
this.target = target;
}
/**
* 执行动态代理对象的所有方法时,都会被替换为执行如下的invoke()方法
* @param proxy 动态代理对象,注意区别于被代理的对象target
* @param method 正在执行的Method对象
* @param args 代表调用目标方法Method对象时传入的参数
* @return 执行的Method对象的返回值
* @throws Throwable 异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
AnimalUtil animalUtil = new AnimalUtil();
// 执行DogUtil中的method1方法
animalUtil.method1();
// 以target作为调用对象,调用method方法
Object returnInvoke = method.invoke(target, args);
// 执行DogUtil中的method2方法
animalUtil.method2();
return returnInvoke;
}
}
/**
* 定义工厂类,该类静态方法为指定的target对象生成动态代理实例对象
*/
class ProxyFactory{
public static Object getProxy(Object target) {
ImpInvocationHandler impInvocationHandler = new ImpInvocationHandler();
impInvocationHandler.setTarget(target);
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), impInvocationHandler);
}
}
/**
* @author looper
*/
public class ProxyTest {
public static void main(String[] args) throws Exception{
Dog impDog = new ImpDog();
// 使用ProxyFactory工厂类为impDog对象生成动态代理对象
// 动态代理对象包含了impDog对象的所有方法
// 此动态代理对象根据InvocationHandler实现类的invoke方法又进一步修饰了impDog对象的所有方法
Dog proxyDog = (Dog)ProxyFactory.getProxy(impDog);
Bird impBird = new ImpBird();
Bird proxyBird = (Bird)ProxyFactory.getProxy(impBird);
// 动态代理ImpDog对象
proxyDog.walk();
System.out.println(proxyDog.bark());
proxyDog.eat("meat");
// 动态代理ImpBird对象
proxyBird.fly();
System.out.println(proxyBird.chirp());
proxyBird.peck("seed");
}
}
注解
一、简介
注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
注解本质上就是一个接口,该接口默认继承Annotation接口。
(一)作用分类
编写文档:通过代码里标识的注解生成文档javadoc文档【生成文档doc文档】。
代码分析:通过代码里标识的注解对代码进行分析【使用反射】。
编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override、SuppressWarnings、Deprecated等】。
(二)属性的赋值
- 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
- 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
- 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略。
二、基本注解
(一)@Override
只能修饰方法。限定重写父类方法。
(二)@Deprecated
修饰程序单元(方法、类、接口等)。表明该程序单元已过时。
(三)@SuppressWarnings
修饰程序单元(方法、类、接口等)。抑制编译器的警告。
(四)@SafeVarargs
修饰方法。抑制“堆污染”警告。
(五)@FunctionalInterface
只能修饰接口。表明该接口是函数式接口。
三、元注解
共有6个元注解,其中5个用于修饰其他的注解定义,剩余1个@Repeatable专门用于定义Java8新增的重复注解。
(一)@Retention
只能用于修饰注解定义,指定被修饰的注解可以保留多长时间。其包含一个RetentionPolicy类型的属性,使用时必须为其指定值。
RetentionPolicy类型的属性值有三个
1、RetentionPolicy.CLASS:编译器将把注解记录在class文件中。当运行Java程序时,JVM不可以获取注解信息,这是默认值。
2、RetentionPolicy.RUNTIME:编译器将把注解记录在class文件中。当运行Java程序时,JVM可以获取注解信息,程序可以通过反射获取该注解信息。
3、RetentionPolicy.SOURCE:注解只保留在源代码中,编译器直接丢弃这种注解。
以上常用的值为RetentionPolicy.RUNTIME,因为可以通过反射获取注解信息。
// 使用元注解自定义注解
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Testable{}
// 省略value的样式。只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可
@Retention(RetentionPolicy.RUNTIME)
public @interface Testable{}
(二)@Target
只能用于修饰注解定义,指定被修饰的注解能用于修饰那些程序单元。与@Retention类似,也是有且只有一个ElementType类型名为value的属性。
ElementType类型的属性值有九个
1、ElementType.ANNOTATION_TYPE:指定该策略的注解只能修饰注解。
2、ElementType.CONSTRUCTOR:指定该策略的注解只能修饰构造器。
3、ElementType.FIELD:指定该策略的注解只能修饰成员变量。
4、ElementType.LOCAL_VARIABLE:指定该策略的注解只能修饰局部变量。
5、ElementType.METHOD:指定该策略的注解只能修饰方法定义。
6、ElementType.PACKAGE:指定该策略的注解只能修饰包定义。
7、ElementType.PARAMETER:指定该策略的注解只能修饰参数。
8、ElementType.TYPE:指定该策略的注解只能修饰类、接口(注解)或枚举定义。
9、ElementType.TYPE_USE:指定该策略的注解被称为类型注解,类型注解可用于修饰在任何地方出现的类型。
// 省略value的样式。只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可
@Target(ElementType.TYPE)
public @interface Testable{}
(三)@Documented
指定被该元注解修饰的注解类将被javadoc工具提取成文档,如果定义注解类时使用了@Documented修饰,则所有使用该注解修饰的程序单元的API文档中将会包含该注解说明。
(四)@Inherited
指定被它修饰的注解将具有继承性。如果A类被@XX(这个注解在定义时被@Inherited修饰)注解修饰,则A类的子类也将自动被@XX注解修饰。
(五)@Native
?不常使用,指定被它修饰的成员变量可以被本地代码引用,常用于代码生成工具。?
(六)@Repeatable
指定被它修饰的注解可以重复使用在同一程序单元前面。注意重复注解只是一种简化写法,是一种假象,多个重复注解其实会被作为“容器”注解的属性的数组元素。
四、自定义注解
(一)格式
public @interface 注解名称{
属性列表;
}
(二)属性
属性本质是接口中的抽象方法。
属性的返回值类型有下列取值:基本数据类型、String、枚举、注解和以上类型的数组。
属性的赋值:如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值;如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可;数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略。
五、使用注解
使用注解的三种情况:
1.获取注解定义的位置的对象 (Class,Method,Field)
2.获取指定的注解:getAnnotation(Class)
//其实就是在内存中生成了一个该注解接口的子类实现对象
public class ProImpl implements Pro{
public String className(){
return "cn.itcast.annotation.Demo1";
}
public String methodName(){
return "show";
}
}
3.调用注解中的抽象方法获取配置的属性值