简单了解java中的反射、注解、动态代理相关知识

类加载器

类加载器的作用

加载器是java运行时环境的一部分,负责加载字节码文件,即将磁盘上的某个class文件读取到内存并生成Class的对象

类加载器的分类

启动类加载器

用于加载系统类库**<JAVA_HOME>\bin**目录下的calss

扩展类加载器

用于加载扩展类库**<JAVA_HOME>\lib\ext**目录下的class

应用程序类加载器

用于加载我们自定义的加载器

获取加载类的方式

来自Class类型获取类加载器的方式

java 复制代码
public ClassLoader getClassLoader()//返回类的加载器

示例:

java 复制代码
public class TestDemo1 {
    @Test
    public void testClassLoader(){
        //获取TestDemo1的Class对象
        Class<TestDemo1> demo = TestDemo1.class;

        //获取AppClassLoader类加载器对象
        ClassLoader cl1 = demo.getClassLoader();
        System.out.println(cl1);

        //获取AppClassLoader类加载器的父类扩展类加载器ExtClassLoader
        ClassLoader cl2 = cl1.getParent();
        System.out.println(cl2);

        //获取ExtClassLoader类加载器的父类启动类加载器Bootstrap
        ClassLoader cl3 = cl2.getParent();//null
        System.out.println(cl3);
    }
}

双亲委派机制

工作机制

某个"类加载器"收到类加载的请求,它首先不会尝试自己取加载这个类,而是把请求交给父级类加载

因此,所有的类加载的请求最终**都会被传送到顶层的"启动类加载器"**中。

如果父级类加载器无法加载这个类,然后子级类加载器再去加载。

使用类加载器加载配置文件的方式

示例步骤:

src 下新建一个user.properties文件,并输入:

properties 复制代码
userName=TG
age=21

创建properties类的集合对象p

使用当前类TestDemo2获取Class对象并调用Class类中的getClassLoader函数

使用类加载器对象调用ClassLoader类中的**InputStream getResourceAsStream(String name)**返回读取指定资源的输入流

java 复制代码
public class TestDemo2 {
    @Test
    public void testGetResourceAsStream() throws IOException {
        //获取类加载器对象
        Class<TestDemo2> cla = TestDemo2.class;
        ClassLoader classLoader = cla.getClassLoader();

        //使用类加载器中的方法,获取src目录下的配置文件
        InputStream is = classLoader.getResourceAsStream("user.properties");

        //创建Properties对象
        Properties prop = new Properties();
        prop.load(is);

        System.out.println(prop);
    }
}

反射

概述

是针对Class对象进行操作,是一种类的解剖技术(.class文件中有:构造方法、成员方法、成员变量 ),反射就可以获取.class文件中的构造方法、成员方法和成员变量。获取到这些东西有啥用嘞?当我们获取到构造方法时,我们可以创建对象,当我们获取到成员变量时,我们可以进行取值和赋值,当我们获取到成员方法时,我们可以进行方法调用执行。上述这种在运行过程中动态的获取类的信息以及调用类中成分的能力称为java语言的反射机制

反射的关键

反射的第一步都是先得到编译后的Class类对象,然后就可以得到Class的全部成分。

反射获取类对象

  1. 当方法区中创建了.class文件的Class对象后,就可以使用类名.class
java 复制代码
Class cls = 类名.class
  1. Class cls = Class.forName("类的全限定名")
java 复制代码
Class cls = Class.forName("com.mysql.cj.jdbc.Driver");//编写代码时不要求Driver必须存在
  1. 在创建具体对象后,用对象名获取Class对象
java 复制代码
Class cls = 对象名.getClass();

示例:

java 复制代码
public class TestDemo3 {
    //类名.class
    @Test
    public void classTest() {
        System.out.println(User.class);
    }

    //Class.forName:读取配置文件
    @Test
    public void forNameTest() throws IOException, ClassNotFoundException {
        //读取配置文件
        Class cla = TestDemo3.class;
        ClassLoader classLoader = cla.getClassLoader();
        InputStream ras = classLoader.getResourceAsStream("user.properties");
        Properties prop = new Properties();
        prop.load(ras);


        String className = prop.getProperty("className");

        Class cls = Class.forName(className);

        System.out.println(cls);
    }

    //对象名.getClass()
    @Test
    public void getClassTest() {
        demo(new User());
    }

    public void demo(User user) {
        Class cls = user.getClass();
        System.out.println(cls);
    }
}

使用反射技术获取构造器对象并使用

Class类中用于获取构造器的方法

方法 说明
Constructor<?>[] getConstructors() 返回所有构造对象的数组
Constructor<?>[] getDeclaredConstructors() 返回所有构造器数组,存在就能拿到
Constructor< T> getConstructor(Class<?>... parameterTypes) 返回单个构造器对象(只能拿到public的)
Constructor< T> getDeclaredConstructor(Class<?>... parameterTypes) 返回单个构造器存在就能拿到

示例:

java 复制代码
/**
 * 利用反射获取构造器,并为对象属性赋值
 */
public class ReflectDemo1 {
    @Test
    public void demo() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //获取Class对象
        Class<User> userClass = User.class;

        //获取构造器(要是你想获取所有(包括私有)的构造器可以使用getDeclaredConstructor)
        //获取public无参构造
        Constructor<User> constructor = userClass.getConstructor();
        //获取public有参构造
        Constructor<User> constructor1 = userClass.getConstructor(String.class, String.class);

        //利用构造器创建对象
        User user = constructor.newInstance();//无参构造方法中无需传递参数
        //利用有参构造创建对象
        User user1 = constructor1.newInstance("糖魅", "8849");

        //为对象属性赋值
        user.setName("糖锅");
        user.setPassword("8848");

        System.out.println(user);
        System.out.println(user1);
    }

    @Test
    public void demo1() {
        //获取Class对象
        Class<User> userClass = User.class;

        //获取构造器(若是你想获取所有(包括私有)的构造器你可以使用getDeclaredConstructors)
        Constructor<?>[] constructors = userClass.getConstructors();

        //查看获取到的构造器
        for (Constructor<?> cons : constructors) { //可以获取所有public构造器
            System.out.println(cons);
        }
    }
}

Constructor类中用于创建对象的方法

方法 说明
T newInstance(Object... initargs) 根据指定构造器创建对象
public void setAccessible(boolean flag) 设置为true表示取消访问检查,进行暴力反射

**注意:**这里解释一下第二个方法

继承中,父类的私有内容是可以被继承的,但是由于java语言有权限检查过滤,所以才导致私有内容子类无法访问;Class对象中存储的private构造器,我们虽然可以得到私有构造器,但是我们无法通过一般手段来用它创建对象,而反射技术的强大之处在于可以暴力破解权限限制(setAccessible方法),可以设置本次访问时暂时取消权限检查。

示例:

java 复制代码
@Test
public void demo2() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        /*
         反射技术:获取私有构造器(如果有私有属性也是一样的可以获取)
        */
        //获取Class类
        Class<User> userClass = User.class;

        //获取私有构造器
        Constructor<User> c = userClass.getDeclaredConstructor(String.class);
        c.setAccessible(true);//取消权限检查

        //用私有构造器创建对象
        User user = c.newInstance("糖解");

        System.out.println(user);
    }

使用反射技术获取成员方法变量

Class类中用于获取成员方法的方法

方法 说明
Method[] getMethods() 返回所有成员方法对象的数组(只能拿public)
Method[] getDeclaredMethods() 返回所有成员方法对象的数组,只要存在就能拿到
Method getMethod(String name, Class< ?>... parameterType) 返回单个成员方法对象,只能拿public
Method getDeclaredMethod(String name, Class< ?>... parameterType) 返回单个成员方法,只要存在就能拿到

Method类中用于除法执行的方法

方法 说明
Object invoke(Object obj, Object... args) 参数一:用于obj对象调用该方法;参数二:调用方法的传递参数;返回值:方法的返回值(如果没有就不写)

示例:

Demo类

java 复制代码
public class Demo {
    int id;

    public Demo() {
    }

    public Demo(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Demo{" +
                "id=" + id +
                '}';
    }

    public String haha() {
        return "hahahaha";
    }
}

测试方法

java 复制代码
public class MethodDemo {
    @Test
    public void getMethodTest() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //获取Class对象
        Class<Demo> demoClass = Demo.class;

        //获取对象中的方法
        Method haha = demoClass.getMethod("haha");
        Constructor<Demo> cons = demoClass.getConstructor();

        //调用方法
        Demo demo = cons.newInstance();
        System.out.println(haha.invoke(demo));
    }
}

使用反射技术获取成员变量

Class类中用于获取成员变量的方法

方法 说明
Field[] getFields() 返回所有(public)成员变量对象数组
Field[] getDeclaredFields () 返回所有成员变量对象的数组
Field[] getField(String name) 返回单个成员变量(public)
Field[] getDeclaredField () 返回单个成员变量对象存在就能获得

Field类中用于取值赋值的方法

方法 说明
void set(Object obj, Object value) 赋值
Object get(Object obj) 获取值

注解

单独使用没有意义,通常配合反射技术使用

概述

java注解又称java标注,时jdk5引入的一种注释机制,java语言中类、构造方法、方法、成员变量、参数等都可以被注解进行标注

作用

对java中类、方法、成员变量做标记,然后进行特殊处理,至于到底作何处理由业务需求决定,比如:JUnit框架中,标记了注解@Test的方法就可以当作测试方法执行,而没有标记的就不能作为测试方法执行。

自定义注解

格式
java 复制代码
public @interface 注解名称 {
    public 属性类型 属性名() default 默认值;
}

上述注解中,属性类型可以是:八种基本数据类型、String、Class、注解类型、枚举类型

特殊属性

value属性,如果只有一个value属性的情况下,使用value属性的时候可以省略value名称不写,但是如果有多个属性,且多个属性没有默认值,那么value名称是不能省略的。

自定义注解书写示例:

自定义注解书写示例:

java 复制代码
public @interface Student {
    public String value();
    public String[] teacherName();
    public String schoolName() default "TG大学";
}

元注解

概述

就是修饰自定义注解的注解

常见的元注解有两个

@Target :约束自定义注解只能在哪些地方使用,其中可以使用的值定义在ElementType枚举类中,常用值如下:

  • TYPE:类、接口
  • FIELD:成员变量
  • METHOD:成员方法
  • PARAMELER:方法参数
  • CONSTRUCTOR:构造器
  • LOCAL_VARIABLE:局部变量

示例:

java 复制代码
@Target(ElementType.TYPE)//限定当前注解只能在类上使用
public @interface Student {
    
}

@Retention :声明注解生命周期,其可以使用RetentionPolicy枚举类中,常用值如下:

  • SOURCE:注解之作用在源码阶段,生成的字节码文件中不存在
  • CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值
  • RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段(开发常用 )

注解解析

概述

注解的操作中经常要进行解析,注解的解析就是判断是否存在注解,存在注解就解析出内容。

相关接口

AnnotatedElement:该接口定义了与注解解析相关的方法

方法 说明
Annotation[] getDeclaredAnnotations() 获得当前对象上使用的所有注解,返回注解数组
T getDeclaredAnnotation(Class< T> annotationClass) 根据注解类型获得对应注解对象
boolean isAnnotationPresent(Class < Annotation> annotationClass) 判断当前对象是否使用了指定注解,如果使用了则返回true,否则false

示例:获取注解中的value并为使用该注解的对象成员变量赋值为value

自定义一个注解SetStringValue

java 复制代码
@Target(ElementType.FIELD)//限定该注解就只能在成员变量上使用
@Retention(RetentionPolicy.RUNTIME)//该注解生命周期为源码、字节码、运行阶段
public @interface SetStringValue {
    public String value();
}

自定义了一个Person类,其中name属性使用@SetStringValue注解

java 复制代码
public class Person {
    @SetStringValue("糖锅")
    String name;

    public Person() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

定义测试类TestPerson

java 复制代码
public class TestPerson {
    @Test
    public void testPerson() throws NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //获取Class对象
        Class<Person> cls = Person.class;

        //根据Class对象获取成员变量对象和构造器对象
        Field field = cls.getDeclaredField("name");
        Constructor<Person> cons = cls.getDeclaredConstructor();
        
        //如果对象是私有的,用setAccessible(true)方法消除权限判断

        //创建Person对象
        Person person = cons.newInstance();

        //根据成员变量对象判断是否使用@SetStringValue注解
        if (field.isAnnotationPresent(SetStringValue.class)) {
            //如果使用了该注解,则对该注解进行解析
            SetStringValue anno = field.getDeclaredAnnotation(SetStringValue.class);

            //根据获取的注解对象,获取注解中的value值
            String value = anno.value();

            //赋值
            field.set(person, value);
        }
        System.out.println(person.getName());
    }
}

测试运行结果:

txt 复制代码
糖锅

动态代理

概述

动态代理是被代理者没有能力或者不愿意取完成某件事情,需要找个人代替自己去完成这件事情,动态代理就是用来对业务功能进行代理的

步骤

必须有接口,实现类要实现接口(代理通常是基于接口实现的)

创建一个实现类对象,该对象为业务对象,紧接着为业务对象做一个代理对象

实现

java 复制代码
代理对象 = Proxy.newProxyInstance(类加载器, 父接口, 处理器);

参数解释:

  • 类加载器:动态加载.class文件
  • 父接口:代理类和被代理类需要拥有共同的父接口
  • 处理器:代理对象拦截了方法后,对方法进行前增强,后增强,是由处理器来书写逻辑
java 复制代码
代理对象 = Proxy.newProxyInstance(
	类.class.getClassLoader(),//类加载器
    被代理类.class.getInterfaces(),//父接口
    new InvocationHandler(){
        public Object invoke(Object 代理对象,Method 被拦截的方法对象,Object[] 方法中的实参){
            //业务逻辑
        }
    }
)
相关推荐
哎呦没3 分钟前
SpringBoot框架下的资产管理自动化
java·spring boot·后端
m0_571957582 小时前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
一点媛艺3 小时前
Kotlin函数由易到难
开发语言·python·kotlin
姑苏风3 小时前
《Kotlin实战》-附录
android·开发语言·kotlin
奋斗的小花生4 小时前
c++ 多态性
开发语言·c++
魔道不误砍柴功4 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2344 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨4 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程5 小时前
一个例子来说明Ada语言的实时性支持
开发语言·ada
Chrikk6 小时前
Go-性能调优实战案例
开发语言·后端·golang