Java反射、动态代理详解,看这篇文章就够了

要了解Java反射机制,先来看看Java类的加载过程。

一、类的加载

类的加载过程

Java反射、动态代理详解,看这篇文章就够了_第1张图片

1.加载

加载过程主要完成三件事情:
(1)通过类的全限定名来获取定义此类的二进制字节流
(2)将这个类字节流代表的静态存储结构转为方法区的运行时数据结构
(3)在堆中生成一个代表此类的java.lang.Class对象,作为访问方法区这些数据结构的入口。

2.连接

(1)校验:其中一项看字节码的数据是否以“魔数cafe”以及当前的JVM的运行JDK版本是否可以运行。向下兼容,反过来不行。
(2)准备:给成员变量(类变量/静态变量给默认值),把常量(final)等值在方法区的常量池准备好
(3)解析:理解为把类中的对应类型名换成该类型的class对象的地址
String --》String类型对应的Class地址

3.初始化

包含以下两个部分
(1)静态变量的显式初始化代码,赋值代码
(2)静态代码块

哪些操作会导致类的初始化?
这句话的意思是,类的加载不一定会发生类初始化。虽然大多数时候都会初始化。
(1)main方法所在的类在加载时,直接初始化。
(2)new一个类的对象
(3)调用该类的静态变量(final常量除外)和静态方法
(4)使用java.lang.reflect包的方法对类进行反射调用
(5)初始化一个类,如果其父类没有被初始化,则会先初始化其父类

哪些操作不会导致类的初始化?
(1)引用静态常量时(final)不会触发此类的初始化
(2)当访问一个静态域时,只有真正声明这个域的类才会初始化,换句话说,通过子类访问父类的静态域时,只会初始化父类,不会初始化子类
(3)通过数组定义类引用,不会触发类初始化
无论如何,类的加载结果:在方法区有一个唯一的Class对象来代表一个类型

类加载器

1.类加载器是负责加载类的对象。
2.每个 Class 对象都包含一个对定义它的 ClassLoader 的引用。
共有四种类加载器:
(1)引导类加载器:用C++编写的,是JVM自带的类装载器,负责Java平台核心库,用来装载核心类库。该加载器无法直接获取
(2)扩展类加载器:负责jre/lib/ext目录下的jar包或 –D java.ext.dirs 指定目录下的jar包装入工作库
(3)应用程序类加载器:负责java –classpath 或 –D java.class.path所指的目录下的类与jar包装入工作 ,是最常用的加载器
(4)自定义类加载器(一般两种情况会用到):

  • 字节文件需要加密解密
  • 加载特定目录下的类
    关系:4,3,2,1 4认3为parent加载器,但不是继承关系
双亲委派机制

Java反射、动态代理详解,看这篇文章就够了_第2张图片

当“应用程序类加载器"接到一个加载任务时
(1)先搜素内存中是否已经加載过了,如果加载过了,就可以找到对应的C1ass对象,那么就不加载了
(2)如果没有找到,把这个任务先提交给 parent",父加载器接到任务时,也是重复(1)(2)
3)直到传给了根加载器,如果根加载器可以加载,就完成了,如果不能加戟,往回传,依次每个加載器尝试在自己员责的路径下搜素,如果找到就直接返回Class对象,如果一直回传到“应用程序类加载器”,还是没有找到就会报ClassnotFoundException
为什么要使用这种机制呢?

  • 为了安全,防止你写一个核心类。
  • 每一个加载器只负责自己路径下的东西。
    类加载器的作用
    1、最主要的作用:加载类
    2、辅助的作用:可以用它来加载“类路径下”的资源文件
    例如:bin中src下文件ー->bin目录下Properties.Properties类表示了一个持久的属性集, Properties可保存在流中或从中加载属性列表中每个键

二、Java反射

正射:

之前写代码都是正射,先写类–>用类创建对象–>通过对象操作

反射

原来是:类–>对象
现在是:从这个类的Class对象–>类的所有的操作
其中一种例如Tomcat,spring,mybatis等各种框架
(1)先创建对象
(2)通过对象操作
(3)再写类 通过XML等文件告知框架类名

第二种:
(1)先写类
(2)通过4种方式获取到该类的Class对象
(3)用Class对象创建这个类的对象
(4)再通过Class对象操作

4种获取Class对象的方法:
1)前提:若已知具体的类,通过类的class属性获取,该方法
最为安全可靠,程序性能最高
实例:

Class clazz = String.class;

2)前提:已知某个类的实例,调用该实例的getClass()方法获
取Class对象
实例:

Class clazz = “www.atguigu.com”.getClass();

3)前提:已知一个类的全类名,且该类在类路径下,可通过
Class类的静态方法forName()获取,可能抛ClassNotFoundException
实例:

Class clazz = Class.forName(“java.lang.String”);

4)其他方式

ClassLoader cl = this.getClass().getClassLoader();
Class clazz4 = cl.loadClass(“类的全类名”);

反射在开发的应用有如下几个方面:

一、在运行期间:动态得获取各个类的详细信息

1.获取某个类型的Class对象
2.使用Class和java.lang.reflect包下面的其他类型的API

Class cls = Class.forName("com.jdk"); 
//返回true 
System.out.print(cls.isInstance(new jdk())); 
二、在运行期间:动态创建任意类型的对象

1.Class对象.newInstance()
前提:这个类必须有无参构造
步骤:(1)获取Class对象
(2)直接调用Class对象.newInstance()

Class cls = Class.forName("com.jdk"); 
jdk jdkobj = cls.newInstance(); 

2.构造器来创建对象
步骤:(1)获取Class对象
(2)获取构造器对象
(3)用构造器创建对象

Constructor<T> getDeclaredConstructors(Class<?>... parameterTypes)
//构造器可以重载有很多个,如何确定需要哪一个?
通过形参列表
三、在运行期间:动态的为对象的属性赋值

步骤:
1.获取Class对象
2.获取Field属性对象
3.创建实例对象,Class代表的类型的实例对象
4.调用Field对象.set(实例对象,属性值)
调用Field对象. get(实例对象
如果属性是私有的,可以用
Field对象.setAccessible(true)

Class cls = Class.forName("com.jdk"); Methods methods[]= cls.getDecliedMethod(); Fields  fields[] = cls.getDeclieredFields(); 
四、在运行期间:动态的调用任意对象的任意方法

步骤:
1.获取Class对象
2.获取Method方法
3.创建实例对象
4.调用方法

Class cls = Class.forName("com.jdk"); 
Methods methods[]= cls.getDecliedMethod(); 
jdk jdkobj = new jdk(); 
String returnvalue = methods.invoke(jdkobj,null) 

获取泛型

Type type = cla.getGenericSuperclass();

获取注释

cla.getAnnotation(annotationClass);

动态代理底层也是用的反射
我们之前用的都是静态代理

// 委托接口
public interface IHelloService {

    /**
     * 定义接口方法
     * @param userName
     * @return
     */
    String sayHello(String userName);

}
// 委托类实现
public class HelloService implements IHelloService {

    @Override
    public String sayHello(String userName) {
        System.out.println("helloService" + userName);
        return "HelloService" + userName;
    }
}

// 代理类
public class StaticProxyHello implements IHelloService {

    private IHelloService helloService = new HelloService();

    @Override
    public String sayHello(String userName) {
        /** 代理对象可以在此处包装一下*/
        System.out.println("代理对象包装礼盒...");
        return helloService.sayHello(userName);
    }
}
// 测试静态代理类
public class MainStatic {
    public static void main(String[] args) {
        StaticProxyHello staticProxyHello = new StaticProxyHello();
        staticProxyHello.sayHello(" ttt");
    }
}
//静态代理比较容易理解, 需要被代理的类和代理类实现自同一个接口, 然后在代理类中调用真正实现类, 
//并且静态代理的关系在编译期间就已经确定了。而动态代理的关系是在运行期间确定的。静态代理实现简单,
//适合于代理类较少且确定的情况,而动态代理则给我们提供了更大的灵活性。

代理对象和被代理对象都要实现同一个接口,代码冗余

动态代理工作处理器必须实现接口:InvocationHandler
这个方法
(1)它不是由程序员手动调用的,这个方法的代码会被编译器自动生成到代理类的对应方法中,当你调用代理类的方法时,自动执行这个方法的代码

(2)参数列表
第一个参数: proxy代理类对象
第二个参数: method代理类要执行的真正的方法,例如: insert, update…
第三个参数:给 method方法的实参列表,如果有的返回值: method方法的返回值,就是 invoke的返回值
(3)编写代理类要被代理者完成的工作,例如:给所有方法都增加一个功能,统计该方法的运行时间

2、动态的生成代理类及其它的对象
这个时候就需要借助java.lang. reflect. Proxy
Proxy提供用于创建动态代理英和实例的态方法

static Object newproxy Instance(Classloader loader, Class<?>0) interfaces, Invocationhandler h)

第一个数:传入被代理者的类加载器
第二个数:传入被代理者实现的接口们
第三个参数:传入代理者要替被代理者完成的工作的处理对象

// 委托类接口
public interface IHelloService {

    /**
     * 方法1
     * @param userName
     * @return
     */
    String sayHello(String userName);

    /**
     * 方法2
     * @param userName
     * @return
     */
    String sayByeBye(String userName);

}
// 委托类
public class HelloService implements IHelloService {

    @Override
    public String sayHello(String userName) {
        System.out.println(userName + " hello");
        return userName + " hello";
    }

    @Override
    public String sayByeBye(String userName) {
        System.out.println(userName + " ByeBye");
        return userName + " ByeBye";
    }
}
// 中间类
public class JavaProxyInvocationHandler implements InvocationHandler {

    /**
     * 中间类持有委托类对象的引用,这里会构成一种静态代理关系
     */
    private Object obj ;

    /**
     * 有参构造器,传入委托类的对象
     * @param obj 委托类的对象
     */
    public JavaProxyInvocationHandler(Object obj){
        this.obj = obj;

    }

    /**
     * 动态生成代理类对象,Proxy.newProxyInstance
     * @return 返回代理类的实例
     */
    public Object newProxyInstance() {
        return Proxy.newProxyInstance(
                //指定代理对象的类加载器
                obj.getClass().getClassLoader(),
                //代理对象需要实现的接口,可以同时指定多个接口
                obj.getClass().getInterfaces(),
                //方法调用的实际处理者,代理对象的方法调用都会转发到这里
                this);
    }


    /**
     *
     * @param proxy 代理对象
     * @param method 代理方法
     * @param args 方法的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("invoke before");
        Object result = method.invoke(obj, args);
        System.out.println("invoke after");
        return result;
    }
}
// 测试动态代理类
public class MainJavaProxy {
    public static void main(String[] args) {
        JavaProxyInvocationHandler proxyInvocationHandler = new JavaProxyInvocationHandler(new HelloService());
        IHelloService helloService = (IHelloService) proxyInvocationHandler.newProxyInstance();
        helloService.sayByeBye("ttt");
        helloService.sayHello("www");
    }

}
//JDK 动态代理所用到的代理类在程序调用到代理类对象时才由 JVM 真正创建,JVM 根据传进来的 
//业务实现类对象 以及 方法名 ,动态地创建了一个代理类的 class 文件并被字节码引擎执行,然后
//通过该代理类对象进行方法调用。我们需要做的,只需指定代理类的预处理、调用后操作即可。

总结

Java反射机制让身为静态语言的Java有一定动态性,增加程序的灵活性,避免将程序写死到代码里。目前框架大部分都是用了反射机制。
反射也会带来性能问题,使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此Java反射机制主要应用在对灵活性和扩展性要求很高的系统框架上,普通程序不建议使用。

参考:
类加载机制-深入理解jvm
Java 静态代理、Java动态代理、CGLIB动态代理

你可能感兴趣的:(Java)