1. 反射
1.1. 反射概述
问题引入:
- 提供一个类的路径(例如:
com.softeem.entity.Student
),不清楚类的结构,如果获取该类的对象以及调用类中的方法? - 有一个已知的java对象,如何在不使用
new
关键字的前提对该对象完成一个拷贝? - 如果有一个Map集合,以及一个具体类的Class对象如何将该Map集合转换为一个javabean(实体类的对象)?
Java语言本身是一门静态语言,所有的代码执行都需要先经过编译过程,然后再解释,所有的数据在声明时都会直接指定数据类型,并且运行期间是不能够被修改的(区别JavaScript);但是java中提供了一种称之为反射的机制。
在程序运行期间,通过反射机制可以获取当前对象所属类的具体信息:属性,构造器,方法,注解等;同时,通过反射可以在程序运行期间动态的执行某个对象的方法。反射即:类照镜子的过程,将一个类中的所有成分反射成为对应的java类。
反射是JavaEE框架技术底层核心。
1.2. Class类
任何一个java类被装在到JVM过程中都会在堆区生成一个用于表示该类的原始类型:java.lang.Class
.如何获取一个类的class对象呢:
-
方法一:直接调用对象的
getClass()
方法Dog d = new Dog(); Class clzz1 = d.getClass();
-
方法二
Class clzz2 = Dog.class;
-
方法三
Class clzz3 = Class.forName("com.softeem.j2106.reflect.Dog");
1.3. 使用反射创建对象
一旦获取一个类的Class对象,则可以通过Class中提供的一个实例方法newInstance()
获取当前类的实例:
//获取Class时指定泛型
Class<Dog> clz = Dog.class;
Dog dog = clz.newInstance();
//获取Class时不指定泛型
Class clz2 = Dog.class;
Object obj = clz2.newInstance();
1.4. 反射获取类中成分
1.4.1. 获取类中的属性
Class clz = Dog.class;
//1. 获取指定名称的属性对象(public)
Field f1 = clz.getField("name");
//2. 获取指定名称的属性对象(任何访问修饰符)
Field f2 = clz.getDeclaredField("age");
//3. 获取所有的公共属性(public)对象
Field[] fields1 = clz.getFields();
//4. 获取所有的属性对象(任何访问修饰符)
Field[] fields2 = clz.getDeclaredFields();
Field
类中常见方法:
String getName()
:获取属性的名称Class<?> getType()
:获取属性的类型int getModifirs()
:获取属性的修饰符setAccessable(boolean f)
:设置属性的可访问性(实现暴力破解)set(Object obj,Object value)
:为属性设置值(不是调用对象的setter方法)
1.4.2. 获取类中的构造方法
Class clz = Dog.class;
//1. 获取所有的公共(public)构造器
Constructor<?>[] c1 = clz.getConstructors();
//2. 获取所有构造器
Constructor<?>[] c2 = clz.getDeclaredConstructors();
//3. 获取指定参数的public构造器(无参构造器)
Constructor c2 = clz.getConstructor(null);
//4. 获取指定参数的构造器
Constructor c2 = clz.getDeclaredConstructor(int.class, String.class);
Constractor
类中的常见方法
newInstance(Object... param)
根据体提的参数实例化对象setAccessable(boolean f)
:设置构造器的可访问性
1.4.3. 获取类中的方法(重点)
Class clz = Dog.class;
//获取类中的所有public方法
Method[] methods1 = clz.getMethods();
//获取类中的所有方法
Method[] methods2 = clz.getDeclaredMethods();
//获取类中指定名称的公共方法
Method setName = clz.getMethod("setName");
//获取类中指定名称的方法
Method eat = clz.getDeclaredMethod("eat");
Method
类中的常见方法
Object invoke(Object obj, Object... args)
:通过提供的参数执行指定对象的方法(返回值为实例方法的返回值,null对应void
)setAccessable(boolean f)
:设置方法的可访问性
1.5. 反射执行方法(重点)
使用反射机制,可以获取到一个类中的方法,然后使用反射的方式执行方法:
//获取指定类型的class对象
Class clz = Dog.class;
//实例化对象
Object obj = clz.newInstance();
//获取方法对象
Method eat = clz.getDeclaredMethod("eat");
//若方法为私有的,则可通过以下方式修改其可访问性
eat.setAccessible(true);
//执行方法,并获取方法返回值
Object returnVal = eat.invoke(obj);//等同 int i = dog.eat()
System.out.println(returnVal);
1.6. 案例:反射实现对象拷贝
思考:将一个java对象使用反射实现克隆
/**
* @param obj
* @param t
* @param <T>
* @return
*/
public static <T> T clone(Object obj,Class<T> t){
T newObj = null;
try {
//基于传递的Class对象实例化一个空对象(数据为空)
newObj = t.newInstance();
//获取Class中的所有属性
Field[] fields = t.getDeclaredFields();
for (Field f : fields) {
//获取属性名
String fname = f.getName();
//获取属性类型
Class<?> ftype = f.getType();
//获取属性对应的setter/getter方法名称
String setMethodName = "set"+fname.substring(0,1).toUpperCase()+fname.substring(1);
String getMethodName = "get"+fname.substring(0,1).toUpperCase()+fname.substring(1);
//对于布尔类型的get方法,前缀以is开头
if(Objects.equals("boolean",ftype.getName())){
getMethodName = "is"+fname.substring(0,1).toUpperCase()+fname.substring(1);
}
//根据提供的方法名称和类型获取方法对象
Method methodSet = t.getMethod(setMethodName,ftype);
Method methodGet = t.getMethod(getMethodName);
//执行参数对象的get方法取出属性值,作为目标对象的set方法参数值
Object returnVal = methodGet.invoke(obj);
methodSet.invoke(newObj,returnVal);
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return newObj;
}
测试方法:
Dog d = new Dog();
d.setId(10);
d.setAge(5);
d.setName("旺财");
d.setSex("雄");
d.setType(1);
//无论哪种类型对象,都能实现克隆功能
Dog dog = clone(d, Dog.class);
System.out.println(dog);
1.7. 案例:反射实现Map转换为JavaBean
思考:将一个Map<String,Object>集合转换为一个JavaBean
/**
* 将一个Map集合转换为JavaBean
* @param map 目标集合
* @param t 目标对象的Class对象
* @param <T>
* @return
*/
public static <T> T mapToBean(Map<String,Object> map, Class<T> t){
T target = null;
try {
//1. 实例化一个空对象
target = t.newInstance();
//2.获取Class中的所有属性名称
Field[] fields = t.getDeclaredFields();
for (Field f : fields) {
//获取属性名称(map集合的键)
String fieldName = f.getName();
//设置属性的可访问性为true(暴力反射)
f.setAccessible(true);
//设置属性的值 sname=XXX
f.set(target,map.get(fieldName));
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
//返回目标对象
return target;
}
2. 注解
2.1. 注解概述
从JDK5开始,Java增加对元数据的支持,也就是注解,注解与注释是有一定区别的,可以把注解理解为代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息!
注释和注解的区别:
- 注释通常认为是对代码的一些解释,这些解释信息通常不会被编译器进行编译
- 注解用于在代码内部,对于类,构造器,方法,属性等添加标注,这些标注可以被编译器所识别,并在类加载时,运行时进行读取,用于执行一些逻辑处理
应用场景
- 编译检查
- 框架配置
2.2. 内置注解
JDK已经存在一些内置的注解,主要包含:
@Override
:方法重写时进行编译检查(检查该方法是否属于重写方法)@Deprecated
:标注指定的方法,属性,构造器,类是过时的(不建议使用)@SuppressWarnings
:抑制警告(抑制编译器的警告信息)@FunctionalInterface
:在JDK8新增用于检查函数式接口定义的注解
抑制警告对照表:
关键词 | 用途 |
---|---|
all | to suppress all warnings(抑制所有警告) |
boxing | to suppress warnings relative to boxing/unboxing operations(抑制装箱、拆箱操作时候的警告) |
cast | to suppress warnings relative to cast operations(抑制映射相关的警告) |
dep-ann | to suppress warnings relative to deprecated annotation(抑制启用注释的警告) |
deprecation | to suppress warnings relative to deprecation(抑制过期方法警告) |
fallthrough | to suppress warnings relative to missing breaks in switch statements(抑制确在switch中缺失breaks的警告) |
finally | to suppress warnings relative to finally block that don’t return(抑制finally模块没有返回的警告) |
hiding | to suppress warnings relative to locals that hide variable(抑制与隐藏变量的局部变量相关的警告) |
incomplete-switch | to suppress warnings relative to missing entries in a switch statement (enum case)(忽略没有完整的switch语句) |
nls | to suppress warnings relative to non-nls string literals(忽略非nls格式的字符) |
null | to suppress warnings relative to null analysis(忽略对null的操作) |
rawtypes | to suppress warnings relative to un-specific types when using generics on class params(使用generics时忽略没有指定相应的类型) |
restriction | to suppress warnings relative to usage of discouraged or forbidden references(禁止使用与劝阻或禁止使用的引用有关的警告的限制) |
serial | to suppress warnings relative to missing serialVersionUID field for a serializable class(忽略在serializable类中没有声明serialVersionUID变量) |
static-access | to suppress warnings relative to incorrect static access(抑制不正确的静态访问方式警告) |
synthetic-access | to suppress warnings relative to unoptimized access from inner classes(抑制子类没有按最优方法访问内部类的警告) |
unchecked | to suppress warnings relative to unchecked operations(抑制没有进行类型检查操作的警告) |
unqualified-field-access | to suppress warnings relative to field access unqualified(抑制没有权限访问的域的警告) |
unused | to suppress warnings relative to unused code(抑制没被使用过的代码的警告) |
2.3. 自定义注解
在实际开发中除了使用内置注解进行相关检查外,经常会使用注解对一些传统复杂的xml文件配置进行简化处理(使用反射机制
+注解
),通过注解的方式可以极大减少编码量;
自定义注解通过@interface
创建自定的注解类型,这些注解类型实际都是从java.lang.annotation.Annotation
,语法规范:
public @interface <注解名>{
<属性定义>
}
案例代码
public @interface RequestMapping {
String name() default "";
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}
自定义注解支持以下属性数据类型:
- 整数类型
- 浮点数
- 字符串类型
- 布尔类型
- 枚举类型
- 注解类型
- 以上所有的数组类型
2.4. 元注解
-
Java中除了提供内置的注解和自定义注解之外,另外还提供了一些元注解:
- @Retention 描述注解被保留的阶段(多数时候设置RUNTIME)
- @Target 描述注解的作用范围
- @Documented 用于描述注解是否能被保留到API文档
- @Inherited 用于描述该注解是否被子类继承
3.4.1.
@Retention
(保留范围)用于色湖之注解的被保留范围,包含三个枚举值:
- Retention.SOURCE:仅在编译期间,注解将被编译器丢弃
- Retention.CLASS:运行期间字节码文件中(默认)但会被VM丢弃
- Retention.RUNTIME:运行时(常用选择) VM将在运行期也保留,因此可以通过反射机制读取注解的信息
2.4.2.
@Target
(使用范围)@Target注解用于限定注解的使用范围(在什么地方可以使用该注解)
源码如下:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { /** * Returns an array of the kinds of elements an annotation type * can be applied to. * @return an array of the kinds of elements an annotation type * can be applied to */ ElementType[] value(); }
其中ElementType(是一个枚举类)用于限定范围,源码如下:
public enum ElementType { /**类,接口,枚举*/ TYPE, /**字段上 */ FIELD, /** 方法*/ METHOD, /** 参数*/ PARAMETER, /** 构造器*/ CONSTRUCTOR, /** 局部变量 */ LOCAL_VARIABLE, /** 注解 */ ANNOTATION_TYPE, /** 包 */ PACKAGE, /** * 泛型 * @since 1.8 */ TYPE_PARAMETER, /** * 使用泛型时 * @since 1.8 */ TYPE_USE }
应用如下:
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE, ElementType.CONSTRUCTOR}) public @interface MyAnno { String value(); }
以上的代码表示,该注解可以用于:
- 类型上(类,接口,枚举)
- 字段上(全局变量)
- 方法上
- 方法的参数上
- 局部变量上
- 构造器上
如果不限定范围,默认全局(任何地方可以加)
2.4.3.
@Documented
(文档化)用于设置该注解是否能够被保留到文档中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SZdhMR0j-1632645950985)(assets/1595646837029.png)]
@FuntionalInterface中有如下声明
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface FunctionalInterface {}
2.4.4.
@Inherited
(继承)用于标记当前注解在使用到某个类上时,如果有子类继承该类,则注解也会默认作用子类上
2.5. 使用反射读取注解
Class<User> clz = User.class;
//获取Class类上的所有注解对象
Annotation[] as = clz.getAnnotations();
for (Annotation a : as) {
System.out.println(a);
}
//ORM框架
//判断当前Class对象上是否存在指定的注解
if(clz.isAnnotationPresent(Table.class)){
//获取指定注解
Table t = clz.getAnnotation(Table.class);
System.out.println("表名"+t.name());
System.out.println("别名"+t.alias());
}
//获取Class中指定名称的字段
Field f = clz.getDeclaredField("name");
if(f.isAnnotationPresent(Column.class)){
Column c = f.getAnnotation(Column.class);
System.out.println("字段名:"+c.name());
System.out.println("字段类型:"+c.type());
}
2.6. 使用注解模拟Junit测试框架
编写一个自定义注解并且模拟Junit测试框架自动测试能力。
2.6.1. 定义实体类
public class User {
private int id;
private String name;
private String pwd;
//setter/getter
}
2.6.2. 定义操作类
public class UserManage {
public boolean add(){
System.out.println("添加数据");
return true;
}
public boolean del(){
try {
System.out.println("删除数据" + (10 / 0));
}catch (Exception e){
e.printStackTrace();
}
return false;
}
public boolean upate(){
System.out.println("修改数据");
return true;
}
public List<User> select(){
System.out.println("查询数据");
return null;
}
}
2.6.3. 定义自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Test {
}
2.6.4. 定义测试类
public class TestUser {
UserManage um = new UserManage();
@Test
public void testAdd() {
long start = System.currentTimeMillis();
System.out.println("开始执行时间:"+ LocalDateTime.now());
um.add();
long end = System.currentTimeMillis();
System.out.println("结束执行时间:"+LocalDateTime.now());
System.out.println("耗时:"+(end-start));
}
@Test
public void testDel(){
um.del();
}
@Test
public void testUpdate(){
um.upate();
}
@Test
public void testSelect(){
um.select();
}
@Test
public void testFindById(){
System.out.println("测试根据id查询");
}
}
2.6.6. 实现自动测试类
public class MyJunit {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException {
Class clz = TestUser.class;
//获取测试类实例
Object obj = clz.newInstance();
//获取所有需要被测试的方法
Method[] methods = clz.getMethods();
for (Method m : methods) {
//判断方法是否以“test”开头,并且是否包含@Test注解
if(m.getName().startsWith("test") && m.isAnnotationPresent(Test.class)){
//执行方法
m.invoke(obj);
}
}
}
}