Java学习笔记(二十五):反射与注解

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 "";
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值