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 ClassNotFoundExceptionClass<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.调用注解中的抽象方法获取配置的属性值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值