Java学习笔记(二十五):反射和注解
1.反射概述
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取类的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。
而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。
获取class文件对象的三种方式:
- Object类的getClass()方法;
Student student = new Student();
Class<? extends Student> aClass = student.getClass();
- 静态属性class;
Class<Student> studentClass = Student.class;
- Class类中静态方法forName();
Class<?> aClass2 = Class.forName("org.westos.demo.Student");
2. 通过反射获取构造方法
2.1 无参构造
获取所有构造方法:
public Constructor<?>[] getConstructors() 获取所有的构造方法不包含私有的
//获取该类中所有的构造方法对象,私有的构造方法对象获取不到
Constructor<?>[] constructors = aClass.getConstructors();
public Constructor<?>[] getDeclaredConstructors() 获取所有的构造方法 包括私有的
//获取该类中所有的构造方法对象,包括私有的构造方法
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
获取单个构造方法:
public Constructor<T> getConstructor(Class<?>... parameterTypes) 获取单个的构造方法 不包含私有的
//获取空参的构造方法对象
Constructor<?> constructor = aClass.getConstructor();
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 获取单个的构造方法包含私有的
//创建构造方法并创建对象
Constructor<?> declaredConstructor1 = aClass.getDeclaredConstructor();
//创建对象
Object obj2 = declaredConstructor1.newInstance();
2.2 有参构造
获取一个参数的构造方法对象 参数就是构造方法的参数类型的字节码类型:
Constructor<?> constructor1 = aClass.getConstructor(String.class);
获取私有的构造方法对象 :
Constructor<?> constructor2= aClass.getDeclaredConstructor(String.class,int.class);
//创建构造方法并创建对象
Constructor<?> declaredConstructor1 = aClass.getDeclaredConstructor(String.class, int.class);
//取消语法检查
declaredConstructor1.setAccessible(true);
//创建对象
Object obj2 = declaredConstructor1.newInstance("赵六", 26);
3. 通过反射获取成员变量并使用
获取所有成员变量:
public Field[] getFields() 获取所有的成员变量包含从父类继承过来的
//1.要使用反射,第一步先获取该类的字节码文件对象。
Class<?> aClass = Class.forName("org.westos.demo4.Dog");
//2.获取Dog类中的所有字段对象,但是私有的字段对象获取不到
Field[] fields = aClass.getFields();
public Field[] getDeclaredFields() 获取所有的成员变量 包含私有的 也包含从父类继承过来的成员变量
获取单个成员变量:
public Field getField(String name)
public Field getDeclaredField(String name)
通过反射的方式,给类中的成员变量设置值和获取值:
非私有成员变量:
//1.你要使用反射,第一步先获取该类的字节码文件对象。
Class<?> aClass = Class.forName("org.westos.demo4.Dog");
//2.给name字段设置,我们就要获取到name字段对象
Field fieldName = aClass.getField("name");
//调用Field类中的方法来设置值
//通过反射,创建一个类的对象
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Object obj = declaredConstructor.newInstance();
//set()方法要两个参数,第一个参数是该类的对象,第二个是要设置的具体的值
fieldName.set(obj,"小黑");
私有成员变量:
//给私有字段设置值
Field fieldSex = aClass.getDeclaredField("sex");
//对于私有的,我们可以取消语法检查
fieldSex.setAccessible(true);
fieldSex.setChar(obj,'女');
4.通过反射获取成员方法
4.1 无参无返回值
获取所有成员方法
public Method[] getMethods() //获取所有的公共的成员方法不包含私有的 包含从父类继承过来的过来的公共方法
public Method[] getDeclaredMethods()//获取自己的所有成员方法 包含私有的
获取单个成员方法
//参数1: 方法名称 参数2:方法行参的class 对象
public Method getMethod(String name,Class<?>... parameterTypes) //获取单个的方法 不包含私有的
public Method getDeclaredMethod(String name,Class<?>... parameterTypes) //获取单个方法,包括私有的
//获取类的字节码文件对象
Class<?> aClass = Class.forName("org.westos.demo.Student");
//获取hehe方法对象
Method heheMethod = aClass.getMethod("hehe");
//使用invoke方法调用Method对象代表的方法
//因为invoke方法的参数需要该类对象,因此先创建该类对象
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Object obj = declaredConstructor.newInstance();
//invoke(obj); //参数1,就是该类的对象
Object invoke = heheMethod.invoke(obj);
4.2 带参带返回值
//获取类的字节码文件对象
Class<?> aClass = Class.forName("org.westos.demo.Student");
//使用invoke方法调用Method对象代表的方法
//因为invoke方法的参数需要该类对象,因此先创建该类对象
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Object obj = declaredConstructor.newInstance();
Method test2Method = aClass.getMethod("test2", String.class, int.class);
//返回的就是test2()调用完毕之后的返回值
Object invoke1 = test2Method.invoke(obj, "赵六", 26);
5. 通过反射运行配置文件内容
假设现有两个类:猫类和狗类。最开始的需要是基于狗类开发,那么就需要在主方法中创建狗类对象,调用成员方法。假设一段时间之后改变需求,需要基于猫类开发,那么这时需要将原来的狗类对象和方法注释掉,重新创建猫类及其对象。这样的做法不管是时间还是精力成本都很高。
加入我们使用反射配合配置文件,那么就会大大减少工作量。
现存在一配置文件properties,内容如下:
className=org.westos.demo2.Dog
methodName=eat
主方法中的内容:
public class MyTest {
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
properties.load(new FileReader("MyConfig.properties"));
//通过键className定位到类的位置
//创建该类的字节码对象
Class<?> myClass = Class.forName(properties.getProperty("className"));
Object obj = myClass.getDeclaredConstructor().newInstance();
//调用猫类中的eat方法执行
Method methodName = myClass.getDeclaredMethod(properties.getProperty("methodName"));
Object invoke = methodName.invoke(obj);
}
}
这样每次基于新的类开发,就只要改变properties中类的位置className和方法methodName就可以了。
6. 通过反射越过泛型检查
我们知道,泛型只在编译期有效,运行期就擦除了。而反射机制正是运行状态下的一个机制,因此我们可以通过反射越过泛型检查。
public class MyTest {
public static void main(String[] args) throws Exception {
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
//list.add(200);
//我们通过反射机制,越过泛型检测
Class<? extends ArrayList> aClass = list.getClass();
//获取add方法对象
Method addMethod = aClass.getDeclaredMethod("add", Object.class);
//调用add方法执行。
addMethod.invoke(list,200);
addMethod.invoke(list, true);
System.out.println(list);
}
}
7.动态代理
动态代理的特点是随用随创建,随用随加载,在不修改源码的基础上就可以实现对方法增强。假如原本存在一个管理系统接口,具有增删改查功能:
ublic interface UserDao {
public abstract void add();
public abstract void delete();
public abstract void upate();
public abstract void query();
}
对其进行实现:
public class UserDaoImpl implements UserDao {
@Override
public void add() {
//System.out.println("权限校验");
System.out.println("增加一个用户");
//System.out.println("记录日志");
}
@Override
public void delete() {
// System.out.println("权限校验");
System.out.println("删除一个用户");
// System.out.println("记录日志");
}
@Override
public void upate() {
//System.out.println("权限校验");
System.out.println("修改一个用户");
//System.out.println("记录日志");
}
@Override
public void query() {
//System.out.println("权限校验");
System.out.println("查询一个用户");
// System.out.println("记录日志");
}
}
现要求对其进行功能增强,比如在每个方法中增加“权限校验”和“日志记录”功能,那么在原有代码中进行修改就很不方便。此时我们就可以使用动态代理。
在Java中java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象。注意JDK提供的代理只能针对接口做代理。
主函数中实现代理的过程如下:
public class MyTest2 {
public static void main(String[] args) {
//创建 userDao 接口的实例对象
UserDao userDao = new UserDaoImpl();
//通过ProxyUtils工具类获取代理对象
UserDao proxy = ProxyUtils.getProxy(userDao);
proxy.add();
System.out.println("=====================");
proxy.delete();
System.out.println("=====================");
proxy.upate();
System.out.println("=====================");
proxy.query();
}
}
工具类中的代码如下:
public class ProxyUtils {
//传过来的是被代理对象(目标对象)
public static UserDao getProxy(UserDao dao){
// Proxy 提供用于创建动态代理类和实例的静态方法
/* static Object newProxyInstance (ClassLoader loader, Class < ?>[]interfaces, InvocationHandler h)
返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。*/
//参数1:ClassLoader loader 类加载器。它用于加载代理对象字节码,和被代理对象使用相同的类加载器。具有如下代码中的固定写法;
//参数2:接口对应的一个Class数组。代理对象需要实现的接口,也具有固定写法;
//参数3:InvocationHandler 是代理对象的调用处理程序,实现的接口。需要我们重写invoke方法编写增强的代码
UserDao obj= (UserDao) Proxy.newProxyInstance(dao.getClass().getClassLoader(), dao.getClass().getInterfaces(), new InvocationHandler() {
//Object proxy 代理对象
//Method method 被代理接口中的方法对象
//Object[] args 被代理接口中方法对象的参数数组
//当我们调用接口中的方法的时候,都会经过invoke()方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如果没有使用if语句判断是哪个方法,那么所有方法都会执行invoke中的方法
//只对某个方法进行增强
Object result=null;
if (method.getName().equals("add")) {
System.out.println("权限校验");
result = method.invoke(dao);//调用目标对象的方法执行。
System.out.println("日志的记录");
}else{
result = method.invoke(dao);//调用目标对象的方法执行。
}
return result;
}
});
return obj;//返回代理对象
}
}
注解
注解是从JDK5.0开始引入的新技术。注解不是程序本身,可以对程序作出解释,可以被其他程序(如编译器)读取。如:@Override
与注释不同的是,注解会对程序产生影响。比如@Override修饰的方法必须是重写的方法,使用@Deperated修饰会使方法称为被抛弃的方法,不推荐使用,但可以运行。
1.元注解
负责注解其他注解的注解。Java定义了四个标准的meta-annotation类型,潭门用来提供对其他annotation类型作说明。
- @Target:用于描述注解的使用范围(被描述的注解用在什么地方);
//自定义一个注解
@Target(value = {ElementType.METHOD})
//Tatget元注解表明自定义注解MyAnnotation可以用在方法上
@interface MyAnnotation{
}
//测试元注解,注解报错。因为@Target规定其只能应用在方法上
@MyAnnotation
public class Test{
public void test(){};
}
- @Retention:表示需要在什么级别保存该注释信息,用于描述生命周期;(一般value 定义为 RUNTIME,不管是源码还是class中都有效)
@Retention(value = RetentionPolicy.RUNTIME)
//Tatget元注解表明自定义注解MyAnnotation可以用在所有情况上
@interface MyAnnotation{
}
- @Document:说明该注解将被包含在javadoc中;
- @Inherited:说明子类可以继承父类中的该注解。
2. 自定义注解
public class Test{
//注解可以显式赋值,如果没有默认值,就必须给注解赋值
@MyAnnotation2(value = "a")
public void test(){};
}
@Target(value = {ElementType.METHOD,ElementType.TYPE})})
//Tatget元注解表明自定义注解MyAnnotation可以用在方法和类上
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
//注解的参数:参数类型 + 参数名() ,不是方法
String value();
//可以赋默认值String name() default "";
}