注解与反射
一、注解
1.1 概念
Annotation(注解):是JDK5开始引入的新特性,可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,在框架中大量使用。
注解是一种能被添加到java代码中的元数据,包、类、字段、方法、局部变量、方法参数都可以用注解来修饰,注解对于它所修饰的代码并没有直接的影响。
注解可以通过反射获取标记的内容,编译器生成类字节码文件时,标记也可以嵌入到字节码中
1.2 应用
功能1:生成文档:生成文档是最常见的,也是java最早提供的注解
生成文档测试
package com.coder;
/**
* @author teacher_shi
* @version 1.0
* @since 1.8
* 这是一个学生类
*/
public class Student {
/**
* 计算成绩的方法
* @param x 成绩数组
* @return 总成绩
*/
public int calcScore(int[] x){
int sum=0;
for (int x1 : x) {
sum+=x1;
}
return sum;
}
}
功能2:在编译时进行格式检查,@Override:放在方法前面,如果这个方法并不是覆盖类方法,则编译时就会检查报错。
功能3:跟踪代码依赖性,实现替代配置文件功能。比较常见spring、mybatis开源框架,使用注解作用就是减少配置;在反射的Class,Method,Field这些方法中,有许多使用Annotation的相关处理。
1.3 注解分类
1.3.1 内置注解
@Override: 表示当前的方法定义将会覆盖父类中的方法,如果拼写错误或者方法签名不匹配,编译器就会提示出错。Retention级别RetentionPolicy.SOURCE
@Deprecated:作用是对不应该再使用的类、类成员、方法添加注解,标明已经废弃、过时了,不应该再使用。当编程人员在使用这些方法时,将会在编译器上显示过时信息提示。它和javadoc中的@deprecated标记具有相同的功能。
@SuppressWarnings:关闭对类、方法、成员编译时产生的特定警告
参考代码:
//压制静态访问警告
@SuppressWarnings("static-access")
public static void main(String[] args) {
Student student=new Student();
student.calcScore(new int[]{1,2,3});
student.calcScores(1,2,3);
student.test();
}
//压制多类型警告,使用数组
@SuppressWarnings({"deprecation","static-access"})
public static void main(String[] args) {
Student student=new Student();
student.calcScore(new int[]{1,2,3});
student.calcScores(1,2,3);
student.test();
}
//压制所有警告
@SuppressWarnings("all")
public static void main(String[] args) {
Student student=new Student();
student.calcScore(new int[]{1,2,3});
student.calcScores(1,2,3);
student.test();
}
//@Override-检查方法是否被重写
public class Test extends AnnotationDemo {
@Override
public void test() {
super.test();
}
}
//@Deprecated-标记过时
@Deprecated
public String toLocaleString() {
DateFormat formatter = DateFormat.getDateTimeInstance();
}
//@SuppressWarnings-忽略注解中声明的警告
@FunctionalInterface:用来定义一个函数式接口,如果在接口中有超过一个以上的抽象方法,则报错
1.3.2 元注解
@Target:用于定义注解修饰的目标,指定了目标后,自定义的注解就可以声明在目标上。标记注解的使用目标
public enum ElementType {
/** 标明该注解可以用于类、接口(包括注解类型)或enum声明 */
TYPE,
/** 标明该注解可以用于字段(属性)声明,包括enum实例 */
FIELD,
/** 标明该注解可以用于方法声明 */
METHOD,
/** 标明该注解可以用于参数声明 */
PARAMETER,
/** 标明注解可以用于构造方法声明 */
CONSTRUCTOR,
/** 标明注解可以用于局部变量声明 */
LOCAL_VARIABLE,
/** 标明注解可以用于另一个注解上声明 */
ANNOTATION_TYPE,
/** 标明注解可以用于包声明 */
PACKAGE,
/**
* 标明注解可以用于类型参数声明
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* 类型使用声明
*
* @since 1.8
*/
TYPE_USE,
/**
* 模块声明.
*
* @since 9
*/
MODULE
}
由上述源码可见,注解可以接收一个数组,因此可以接收多个常量值,已有的常量都是定义在ElementType中。
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.PARAMETER,ElementType.LOCAL_VARIABLE,ElementType.TYPE_PARAMETER,
ElementType.TYPE_USE})
public @interface Anno3 {
}
@Anno3//ElementType.TYPE
public class Player {
@Anno3 //ElementType.METHOD
public void test(@Anno3 int x){//ElementType.PARAMETER
@Anno3 int y;//ElementType.LOCAL_VARIABLE
}
}
//ElementType.TYPE_PARAMETER
class XX<@Anno3 T>{
}
//ElementType.TYPE_USE
class YY implements @Anno3 Serializable{
public void test(){
String s=new @Anno3 String();
}
}
@Retention:用于定义注解的生命周期,值只有三种类型 注解保存的时间
public enum RetentionPolicy {
/**
* 注解的信息只会记录在源文件中,编译时会被编译器丢弃,不会保存在编译好的类信息中
*/
SOURCE,
/**
* 编译器将注解记录在类文件中,但不会加载到虚拟机中,如果一个注解声明没有指定范围,则
系统默认值就是CLASS
*/
CLASS,
/**
* 注解信息会保留在源文件、类文件中,在执行时也加载到虚拟机中,可以通过反射机制进行读
取
*/
RUNTIME
}
通过反射机制获取Annotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Anno4 {
String value();
}
public class Team {
@Anno4("hello")
public void test(){
}
}
public class TestGetMethodAnnotation {
public static void main(String[] args) throws NoSuchMethodException {
Class<Team> teamClass = Team.class;
Method method=teamClass.getDeclaredMethod("test");
System.out.println("methodName:"+method.getName());
/*Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}*/
Anno4 anno4 = method.getDeclaredAnnotation(Anno4.class);
String value = anno4.value();
System.out.println(value);
}
}
@Documented:用来做标识,使用了该注解,在生成javaDoc文档的时候,就会把@Documented注解标识的显示出来。 标记这个注解是否包含在用户文档中
@Inherited:如果某个注解是被标注了Inherited,表明可以被继承。如果一个使用@Inherited修饰的annotation类型被用于一个类,则这个annotation将被用于该类的子类。 标记注解是继承哪个注解类(默认是没有继承的)
当父类中的注解被@Inherited标注,会有如下情况
- 如果父类的注解是定义在类上面的,子类是可以继承过来的。
- 如果父类的注解是定义在方法上面的,子类直接继承了父类的方法,则注解是可以继承过来的
- 如果父类的注解是定义在方法上面的,子类重写了父类定义了注解的方法,则子类将无法继承父类方法的注解,将方法连带上面的注解一并覆盖掉。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
@Inherited
public @interface AnnoDoc {
}
@AnnoDoc
public class DataBase {
@AnnoDoc
public void test(){
}
public static void main(String[] args) throws NoSuchMethodException {
/*Class<DataBase> dataBaseClass = DataBase.class;
Annotation[] annotations = dataBaseClass.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}*/
Class<Sub> subClass = Sub.class;
//Annotation[] annotations = subClass.getAnnotations();
// annotations = subClass.getDeclaredAnnotations();
/*for (Annotation annotation : annotations) {
System.out.println(annotation);
}*/
Method test = subClass.getMethod("test");
System.out.println("test = " + test);
Annotation[] annotations = test.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
}
}
class Sub extends DataBase{
public void test(){
}
}
1.3.3 自定义注解
格式
元注解
public @interface 注解名{ }
使用方式
@注解名称
本质:
注解本质就是一个接口,类似于新创建一个接口文件,但是为了和接口做区分,声明为@interface
public interface com.coder.Anno1 extends java.lang.annotation.Annotation {
}
在定义注解时,不能继承其他的注解或接口。
@interface用来声明一个注解,其中的每一个方法实际上声明了一个配置参数。方法的名称,就是参数的名称,方法的返回值类型,就是参数的类型 返回值类型只能是以下数据类型
- String
- 基本数据类型: byte short int long float double char boolean
- Class类型
- enum类型
- Annotation类型
- 以上所有类型的数组
不允许有void方法存在
定义了方法的类型,在使用时要给赋值,也可以在定义时使用default给赋默认值。方法不能有参数
如果只有一个参数成员,一般参数名为value,这时value可以不显式地写出来
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "MyAnnotation";
}
@MyAnnotation("eat1")
public void eat(){
//s
}
二、反射
- 问题:Object obj = new String(“abc”);
- 需求:如何调用 String 中的length 方法
- 使用强制类型转换
- 如果不知道真实的类型怎么办?
- 需求:如何调用 String 中的length 方法
- 问题:一切皆对象,**类这种事物是啥对象?**使用什么类来表示这种对象?
- Class :表示所有的类
- Constructor:表示所有的构造器
- Method :表示所有的方法
- Field:表示所有的字段
- 通过反射:得到类的元数据信息(构造器,方法,字段,内部类)
- 类加载进内存,变成 Class 对象,也叫字节码对象
2.1 反射机制简介
被视为动态语言的关键,允许程序在执行期间,借助于ReflectionAPI取得任何类的内部信息。在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个类对象所属的类,可以了解任意一个类的 成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为java反射机制。
java反射机制提供的功能 :
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时查看任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的方法
- 在运行时给任意一个对象的属性赋值
- 生成动态代理
2.2 动态语言和静态语言
动态语言:在运行时可以改变其结构的语言,比如新的函数、对象、甚至代码可以被引进,已有的函数 可以被删除或是其他结构上的变化。也就是说在运行时代码可以根据某些条件改变自身结构。主要的动 态语言:C#、javaScript、PHP、Python等
静态语言:运行时结构不可变的语言,就是静态语言。包括Java、C、C++等。Java有一定的动态性,可 以利用反射机制、字节码操作获得类似动态语言的特性。
2.3 Class类
java在将.class字节码文件载入时,JVM会产生一个java.lang.Class对象代表该.class字节码文件。Class 是一个比较特殊的类,是java反射机制的基础。Class类的对象表示正在运行的java程序中的类或接口。 也就是任何一个类被加载时,即将类的.class文件读入内存的同时,都自动为其创建一个java.lang.Class 对象。Class类没有公共构造方法,其对象是JVM在加载类时通过调用类加载器中的defineClass()方法创建的,因此不能显式地创建一个Class对象。通过Class对象,才可以获取这个类对象的其他信息。
每个类被加载之后,系统都会为该类生成一个对应的Class对象,一旦类被加载到JVM中,同一个类将不 会被再次载入。
2.4 如何获得Class对象
方式一:使用Class类的静态方法 forName(String className),参数className表示所需类的全路径, 如果给的参数类找不到,会抛出ClassNotFoundException异常。
Class<?> clazz=Class.forName("com.coder.Student");
方式二:用类名调用class属性来获取该类对应的Class对象,“类名.class"
Class<?> clazz2=Student.class
方式三:使用该类的对象调用getClass()方法,来获取该类对应的Class对象。
Student student=new Student();
Class<?> clazz3=student.getClass();
方式四:使用类的装载器
ClassLoader loader=Student.class.getClassLoader();
Class<?> clazz4=loader.loadClass("com.coder.Student");
2.5 那些类型可以有Class对象
- class :外部类、成员内部类、静态内部类、局部内部类、匿名内部类
- interface:接口
- 数组
- enum:枚举
- annotation:注解
- 基本数据类型
- void
Class 类和 Class 实例
-
Class 表示所有的类
-
Class 实例表示:一份份的字节码(类,接口,注解)
-
各种类如何表示?
- String Class<java.lang.String>
- HashMap Class<java.util.HashMap>
-
如何获取Class 的实例
//1:使用类名.class Class<String> stringClass = String.class; //2:通过对象调用 getClass(); String str = "abc"; Class<? extends String> aClass = str.getClass(); //3:使用类的全限定类名 java.lang.String Class<?> aClass1 = Class.forName("java.lang.String"); Class<?> stuClass = Class.forName("cn.sycoder.ClassObjectDemo.Student"); System.out.println(stuClass);
- 注意:同一个JVM中,只存在一个类的一份字节码和一份字节码对象
- 使用最多的 Class.forName(类的全限定类名);
-
基本数据类型的字节码如何表示:
-
jvm 已经提前内置好基本数据类型字节码实例
Class<Integer> aClass2 = int.class;
-
包装类型提供了一个 TYPE 常量,也是可以去获取基本类型的类实例
Class<Integer> type = Integer.TYPE;
-
如何证明 Integer 和 int 不是同一种数据类型
Integer.class == int.class
-
类加载
-
直接使用类.class
Class<String> stringClass = String.class;
-
使用对象调用getClass()
String str = "abc"; Class<? extends String> aClass = str.getClass();
-
使用类全限定类名
Class<?> aClass1 = Class.forName("java.lang.String"); Class<?> stuClass = Class.forName("cn.sycoder.ClassObjectDemo.Student");
反射操作构造器
-
Class 去操作 Constructor 类
-
常用的方法
//获取构造器对象 public Constructor<T> getConstructor(Class<?>... parameterTypes) //获取全部构造器对象 public Constructor<?>[] getConstructors() //获取全部构造器包括私有 public Constructor<?>[] getDeclaredConstructors()
-
常用获取构造器方法
//获取单个 Class<?> aClass = Class.forName("cn.sycoder.ConstructorDemo.User"); Constructor<?> constructor = aClass.getConstructor(); //获取非私有全部 Constructor<?>[] constructors = aClass.getConstructors(); //获取私有的单个 Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(); //获取带私有的全部 Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors(); //带参数的 Constructor<?> constructor1 = aClass.getConstructor(String.class);
反射创建对象
-
问题:为啥使用反射创建对象,不直接使用 new 创建对象
- 框架中,多半使用反射创建对象,使用类全限定类名操作
-
如何使用反射创建对象
-
获取类字节码对象
-
获取构造器对象
-
通过反射获取的构造器创建对象
Class<?> aClass = Class.forName("cn.sycoder.ConstructorNewObject.Person"); Constructor<?> constructor = aClass.getConstructor(); //通过构造器创建对象 Object o = constructor.newInstance();
-
如果构造器是私有的,还需要去设置可以访问
constructor.setAccessible(true);
-
-
常见错误:
-
cn.sycoder.ConstructorNewObject.Person with modifiers “private”
constructor.setAccessible(true);
-
Exception in thread “main” java.lang.IllegalArgumentException: wrong number of arguments
Constructor<?> constructor1 = aClass.getConstructor(String.class, int.class); Object o1 = constructor1.newInstance("上云",18);
-
反射操作方法
-
实例方法
-
获取字节码对象
-
使用构造器获取对象
-
获取方法对象
-
反射调用方法
-
调用方法
- Object invoke(Object obj,Object…args)
Class<?> aClass = Class.forName("cn.sycoder.MethedDemo.MethodDemo"); Constructor<?> constructor = aClass.getConstructor(); Object o = constructor.newInstance(); Method eat = aClass.getMethod("eat"); Object invoke = eat.invoke(o); Method eat1 = aClass.getMethod("eat", String.class); Object obj = eat1.invoke(o, "鱼");
-
-
-
静态方法
-
静态方法不属于对象,属于类本身
-
如何执行:
-
获取类字节码对象
-
获取方法对象
-
执行(invoke)不需要传对象
Method getName = aClass.getMethod("getName", String.class); Object object = getName.invoke(null, "上云"); System.out.println(object);
-
-
反射操作字段
-
获取类字节码对象
-
获取对象
-
操作字段
Class<?> aClass = Class.forName("cn.sycoder.FiledDemo.FiledDemo"); Constructor<?> constructor = aClass.getConstructor(); Object o = constructor.newInstance(); Field age = aClass.getDeclaredField("age"); age.setAccessible(true); age.set(o,19); Object o1 = age.get(o); System.out.println(o1);
类加载器加载 Properties
-
使用绝对路径
//使用绝对路径 Properties properties = new Properties(); FileInputStream in = new FileInputStream(path); properties.load(in); System.out.println(properties);
-
使用相对路径从根目录开始找
Properties properties = new Properties(); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); InputStream resourceAsStream = classLoader.getResourceAsStream(path); properties.load(resourceAsStream); System.out.println(properties);
-
使用相对路径(相对于我们使用的类的文件夹开始)
Properties properties = new Properties(); InputStream inputStream = Test.class.getResourceAsStream(path); properties.load(inputStream); System.out.println(properties);
2.6 ClassLoader类加载器
2.6.1 简介
一个用来加载类文件的类 java源代码通过javac编译器编译成类文件,然后Jvm通过类文件中的字节码来执行程序,类加载器负责加载文件系统、网络或其他来源的类文件
jvm 和类的关系
- 运行带有main方法的类(主方法),启动 jvm 进程,并加载字节码,同一个jvm 所有线程已经变量都处于同一个进程中
- jvm 何时退出?
- 程序正常运行结束
- 出现异常,没有捕获异常时
- System.exit() 方法
- 强制杀进程的时候
- 重点:jvm 进程一旦结束,进程中的内存中的数据会丢失
2.6.2 机制
Java类装载器的作用就是在运行时加载类。基于三个机制:
-
委托机制:将加载一个类的请求交给父类加载器,如果这个父类加载器找不到要加载的类,那么子 类再加载它。比如加载一个String类
-
可见性机制:子类的加载器可以看见所有的父类加载器加载的类,但是父类加载器看不到子类加载 器加载的类
-
单一性机制:加载一个类,仅加载一次,可以确保在委托机制中,如果父类加载器已经加载过这个 类了,子类加载器不会再次加载
2.6.3 加载的两种方式
隐式加载:程序在运行过程中,通过new等方式生成对象时,隐式调用类加载器加载对应的类进入到 JVM中
显式加载:通过Class.forName()等方法,显式地加载需要的类
2.6.4 类加载初始化具体步骤(加载,连接,初始化)
- 类的加载
- 类加载器(ClassLoader),将字节码文件(class 文件)加载进内存中,并且创建一个字节码对象(java.lang.Class),类加载器由JVM提供,或者自定义
- 类的连接(类加载进内存之后,生成 Class 对象,把类的二进制数据合并到 JRE 中)
- 验证:检测被加载的类是否有正确的内部结构
- 准备:负责为类的 static 变量分配内存,设置默认值(并不是初始化操作)
- 解析:把类的二进制数据中的符号引用替换为直接引用(深入分析 jvm)
- 类的初始化(jvm 负责对类进行初始化,主要是对 static 变量进行初始化)
- 如果该类未被加载和连接,则程序先加载并连接该类(类还可以动态加载)groovy
- 如果该类的父类未被初始化,则优先初始化父类
- 如果该类有初始化语句(静态代码块),系统依次执行这些初始化语句
2.6.5 类加载器的分类
JDK默认提供三种ClassLoader
BootStrapClassLoader:根装载器,它使用C++编写,所以在Java中看不到它,负责装载核心类库
ExtClassLoader:(PlatformClassLoader(JDK9版本))扩展类装载器,装载扩展目录ext中的jar类
AppClassLoader:系统类装载器(应用类装载器),负责classpath类的加载
public class TestClassLoader {
public static void main(String[] args) {
//jdk.internal.loader.ClassLoaders$AppClassLoader@2437c6dc
ClassLoader classLoader = TestClassLoader.class.getClassLoader();
System.out.println(classLoader);
//jdk.internal.loader.ClassLoaders$PlatformClassLoader@5594a1b5
ClassLoader parent = classLoader.getParent();
System.out.println(parent);
//null
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);
}
JVM装载类时使用“全盘负责委托机制",当一个ClassLoader一个类的时候,除非显式地使用另一个ClassLoader,不然该类所依赖及引用的类也是由这个ClassLoader载入。
一个应用程序总是由很多个类组成,java程序启动时,并不是一次把所有的类加载再运行。总是先把保 证程序运行的基础类一次性加载到JVM中,其他类等到JVM用到的时候再加载,这样可以节省内存的开销。
2.7 Constructor类
public class TestConstructor {
public static void main(String[] args) throws Exception {
Class<?> aClass = Class.forName("com.coder.Student");
//调用无参构造方法
Constructor<?> constructor = aClass.getConstructor();
// Object o = constructor.newInstance();
//调用有参数的构造方法
/*Constructor<?> constructor = aClass.getConstructor(String.class,
int.class);
Object o = constructor.newInstance("李白", 20);
Student student= (Student) o;
System.out.println(student);*/
//访问私有构造方法
/*Constructor<?> constructor = aClass.getDeclaredConstructor();
//setAccessible(true) 取消java语言对访问的检查
//Student student=new Student();
constructor.setAccessible(true);
Object o = constructor.newInstance();
Student student= (Student) o;
student.setName("杜甫");
student.setAge(22);
System.out.println(student);*/
//获取构造方法数组
/* Constructor<?>[] declaredConstructors =
aClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor.getName());
System.out.println(declaredConstructor.getParameterCount());
Class<?>[] parameterTypes =
declaredConstructor.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println("\t"+parameterType.getName());
}
}*/
//以下调用方式,从jdk9版本后被弃用
aClass.newInstance();
}
}
2.8 Field类
Class<?> aClass = Class.forName("com.coder.Student");
//获取public修饰的属性
//如果父类有公有的属性,可以读取
Field[] fields = aClass.getFields();
//获取所有private default protected public修饰的属性
//不读取父类的属性
fields=aClass.getDeclaredFields();
获取访问修饰符
for (Field field : fields) {
System.out.print(field.getModifiers());//访问修饰符
System.out.println(" "+field.getName());
}
public class TestField {
public static void main(String[] args) throws Exception {
Class<?> aClass = Class.forName("com.coder.Student");
//获取public修饰的属性
//如果父类有公有的属性,可以读取
Field[] fields = aClass.getFields();
//获取所有private default protected public修饰的属性
//不读取父类的属性
/*fields=aClass.getDeclaredFields();
for (Field field : fields) {
System.out.print(getModifiers(field.getModifiers()));
Class<?> type = field.getType();
System.out.print(" "+type.getSimpleName());
System.out.println(" "+field.getName());
}*/
//只获取一个属性
Field name = aClass.getDeclaredField("name");
System.out.println(name.getName()+"\t"+name.getType().getSimpleName());
//通过反射机制为对象的属性赋值
Object obj = aClass.getDeclaredConstructor().newInstance();
//取消java语言访问检查
name.setAccessible(true);
//为obj对象的属性赋值
name.set(obj,"李白");
//获取属性值
//Object value = name.get(obj);
//System.out.println(value);
Student student = (Student) obj;
System.out.println(student);
}
public static String getModifiers(int modifiers){
switch (modifiers){
case 1:
return "public";
case 2:
return "private";
case 4:
return "protected";
case 10:
return "private static";
default:
return "";
}
}
}
2.9 Method类
public class TestMethod {
public static void main(String[] args) throws Exception {
Class<?> aClass = Class.forName("com.coder.Student");
//获取本类的所有公共方法和父类的公共方法
Method[] methods = aClass.getMethods();
//获取本类的所有方法
/* methods=aClass.getDeclaredMethods();
for (Method method : methods) {
System.out.print(method.getModifiers()+" ");
System.out.print(method.getName()+" ");
//获取方法的返回值类型
Class<?> returnType = method.getReturnType();
System.out.println(returnType.getSimpleName());
//方法的参数
Class<?>[] parameterTypes = method.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println("\t"+parameterType.getSimpleName());
}
//获取方法异常列表
Class<?>[] exceptionTypes = method.getExceptionTypes();
for (Class<?> exceptionType : exceptionTypes) {
System.out.println("\t\t"+exceptionType.getSimpleName());
}
//获取注解
//获取@Retention(RetentionPolicy.RUNTIME)的注解
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("\t\t\t"+annotation);
}
}*/
Object obj = aClass.getConstructor().newInstance();
//获取一个方法,并调用
Method addMethod = aClass.getDeclaredMethod("add", int.class,
int.class);
Object result = addMethod.invoke(obj, 10, 20);
System.out.println(result);
Method keep2 = aClass.getDeclaredMethod("keep2", double.class);
keep2.invoke(obj, 3.1455);
//System.out.println(invoke);
}
}
2.10 反射获得泛型
可以通过反射获取泛型的场景
- 成员变量的泛型
- 方法参数的泛型
- 方法返回值的泛型
- 获取带有泛型的超类或带有泛型的实现的接口
不可以通过反射获取泛型的场景
- 局部变量的泛型
参考代码
成员变量泛型
public class GenericDemo {
public static void main(String[] args) throws Exception {
getField();
}
public static void getField() throws Exception {
Class<MyDemo> myDemoClass = MyDemo.class;
Field listField=myDemoClass.getDeclaredField("list");
//Class<?> type = listField.getType();
//System.out.println(type.getName());
//获取带有泛型的类型
Type genericType = listField.getGenericType();
System.out.println(genericType.getTypeName());
//判断获取到的Type是不是参数化类型(泛型)
if (genericType instanceof ParameterizedType){
ParameterizedType parameterizedType= (ParameterizedType)genericType;
//获取泛型真实参数类型
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
//System.out.println(actualTypeArgument.getTypeName());
Class clazz= (Class) actualTypeArgument;
System.out.println(clazz.getSimpleName());
}
}
}
}
class MyDemo{
//成员变量带有泛型
private List<String> list=new ArrayList<>();
private int x;
//方法参数带有泛型
public void filter(List<String> list){
}
//方法返回值带有泛型
public Map<String,Double> getScore(){
return null;
}
}
方法参数泛型
public static void getMethodParameter() throws Exception{
Class<MyDemo> myDemoClass = MyDemo.class;
Method
method=myDemoClass.getDeclaredMethod("filter",List.class,Map.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
if (genericParameterType instanceof ParameterizedType){
ParameterizedType parameterizedType= (ParameterizedType)
genericParameterType;
Type[] actualTypeArguments =
parameterizedType.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument.getTypeName());
}
}
}
}
class MyDemo{
//成员变量带有泛型
private List<String> list=new ArrayList<>();
private int x;
//方法参数带有泛型
public void filter(List<Double> list, Map<String,Student> map){
}
//方法返回值带有泛型
public Map<String,Double> getScore(){
return null;
}
}
方法返回值的泛型
public static void getMethodReturn() throws Exception {
Class<MyDemo> myDemoClass = MyDemo.class;
Method getScore = myDemoClass.getDeclaredMethod("getScore");
// getScore.getReturnType();
//获取带有泛型的返回值类型
Type genericReturnType = getScore.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType){
ParameterizedType parameterizedType= (ParameterizedType)genericReturnType;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument.getTypeName());
}
}
}
2.11 获取接口和超类
public class TestInterface {
public static void main(String[] args) throws Exception {
Class<?> aClass = Class.forName("com.coder.UserServiceImpl");
//获取当前类实现的带有泛型的接口的泛型类型
/* Type[] genericInterfaces = aClass.getGenericInterfaces();
for (Type genericInterface : genericInterfaces) {
if (genericInterface instanceof ParameterizedType){
ParameterizedType parameterizedType= (ParameterizedType)
genericInterface;
Type[] actualTypeArguments =
parameterizedType.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument.getTypeName());
}
}
}*/
//获取当前类的所有接口
/*Class<?>[] interfaces = aClass.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println(anInterface.getName());
}*/
//获取父类
Class<?> superclass = aClass.getSuperclass();
System.out.println(superclass.getName());
//获取带有泛型的父类
Type genericSuperclass = aClass.getGenericSuperclass();
if (genericSuperclass instanceof ParameterizedType){
ParameterizedType parameterizedType= (ParameterizedType)
genericSuperclass;
Type[] actualTypeArguments =
parameterizedType.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument.getTypeName());
}
}
}
}
三、基于JDK动态代理
3.1 简介
动态代理是通过创建代理对象,可以实现在不修改原有代码的情况下,为程序增加新的功能,实现程序 功能的增加。
动态代理的实现分成两种
- JDK动态代理
- CGLIB动态代理
3.2 JDK动态代理API
InvocationHandler接口
Method类
Proxy类
JDK的动态代理目标类必须要有接口。
实现动态代理的步骤
-
创建接口,定义目标类要完成的功能
public interface UserService { void saveUser(); void deleteUser(); }
-
创建目标类实现接口,编写对应方法
public class UserServiceImpl implements UserService{ @Override public void saveUser() { System.out.println("保存用户"); } @Override public void deleteUser() { System.out.println("删除用户"); } }
-
创建InvocationHandler接口的实现类,在invoke方法中完成代理的功能
-
调用目标类的方法
-
加入增强功能
//表示程序的代理要做什么,怎么做 public class MyHandler implements InvocationHandler { //被代理的目标对象 private Object object; public MyHandler(Object object) { this.object = object; } /** * * @param proxy jdk创建的代理对象,无需赋值 * @param method 目标类中的方法,jdk提供拦截的目标类正在调用的方法 * @param args 目标类中方法的参数 * @return 调用方法后的返回的结果 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { boolean b = checkPermission(); if (b){ Object result = method.invoke(object, args); log(); return result; } return null; } private boolean checkPermission(){ System.out.println("校验用户权限"); return true; } private void log(){ System.out.println("处理日志"); } }
-
-
使用Proxy类的静态方法newProxyInstance,创建代理对象,并把返回值转为接口类型
-
调用接口的相关方法
public class Test { public static void main(String[] args) { /* UserService service=new UserServiceImpl(); service.saveUser(); service.deleteUser();*/ UserServiceImpl userService = new UserServiceImpl(); //创建了一个代理对象 //ClassLoader 类装载器 //Class[] 获取目标对象的所有接口 //InvocationHandler Object o = Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), new MyHandler(userService)); UserService service= (UserService) o; service.saveUser(); System.out.println("___________________"); service.deleteUser(); } }