JVM类加载器和反射机制及代理模式

JVM类加载器:

概述:

JVM加载器主要负责将外部文件系统或者网络中的class文件加载到JVM中,将加载到JVM的class的类信息、运行时常量池的常量、静态变量存放在JVM的方法区中,由于加载器使用的是双亲委派机制,所以JVM能确保一个相同的class只能被加载一次。JVM是以按需处理的原则去加载class,就是你什么时候需要,加载器才去把class加载到内存中生成类对象

JVM的加载大致可以分为三步:1、加载阶段;2、链接阶段;3、初始化阶段;
加载阶段:

工作过程:在加载阶段,加载器将class文件的二进制字节流读取,并通过class的全限定名来标识这个class,保证class只被加载过一次,读取二进制字节流后将文件的静态存储结构转化为方法区的数据存储结构,然后在堆内存中生成一个类对象作为方法区各种类信息类的运行时常量池常量的访问入口,java栈通过引用堆内存中生成的对象来访问对象
加载的class文件的途径:动态加载,本地class文件,jar包,zip包,war包,jsp文件等

链接阶段:

链接过程又包括了三个阶段:
验证:加载的二进制字节流符合JVM的要求,确保二进制字节流不会危害到JVM自身的安全,验证包括格式验证、元数据验证、二进制码验证、符号引用验证
准备:为class中的类变量分配内存,并且初始化类变量,注意这里的类变量不是成员变量,而是被static修饰的变量,准备阶段中不会给类的成员变量分配内存,而是将类变量初始化为数据类型的零值,而不是用户显示指定的值,这里还要注意的是内存分配不包括final修饰的staric变量,final修饰的staric变量在编译期就分配了内存,并且在准备期就能使用代码中的显示赋值了,而不是都赋值为0,准备阶段总而言之主要就是给static的变量赋值为0;
解析:解析虽然是在链接阶段,但是其工作却是在初始化之后执行,它的作用就是将常量池中的符号引用转化为直接引用

初始化阶段:

初始化阶段中的每个类都会调用类的构造器方法,这里需要注意这个构造器方法不是我们知道的那个类的构造器,构造器方法的过程就是将代码中所有的显示赋值操作揉合起来对类变量进行赋值操作,其中静态代码块的也是在这一阶段执行,总而言之就是对类的静态常量显示赋值

类加载器的分类:

类加载器大致分为四种:1、引导类加载器(BootstrapClassLoader)、2、扩展类加载器(ExtClassLoader)、应用类加载器(AppClassloader)、用户自定义加载器
启动加载器 BootstrapClassLoader:启动类加载器,是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar等,这个ClassLoader完全是JVM自己控制的,需要加载哪个类,怎么加载都是由JVM自己控制,别人也访问不到这个类,所以BootStrap ClassLoader不遵循委托机制,没有子加载器。
平台加载器(PlatformClassLoader)(jdk9后将ExtClassLoader替换成平台加载器):加载平台相关的模块,例如java.scripting,java.compiler,java.corba
扩展类加载器(ExtClassLoader)(jdk9后已经废除):称为扩展类加载器,负责加载Java的扩展类库,Java 虚拟机的实现会提供一个扩展库目录,该类加载器在此目录里面查找并加载 Java 类。默认加载JAVA_HOME/jre/lib/ext/目下的所有jar。也就是主要加载System.getProperty("java.ext.dirs")下的东西
应用类加载器(AppClassloader):称为系统类加载器,负责加载应用程序classpath目录下的所有jar和class文件。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。我们可以通过System.getProperty(“java.class.path”) 来查看 classpath。

  @org.junit.Test
    public void clazzloader(){
        //获取加载classpath的加载器AppClassloader
        ClassLoader classLoader = Test.class.getClassLoader();
        //输出加载名称
        System.out.println("加载classpath下的类的加载器"+classLoader.toString());
        //输出父加载名称ExtClassLoader
        System.out.println(classLoader.toString()+"的父类加载器"+classLoader.getParent().toString());
        //输出父加载名称的父加载器BootstrapClassLoader,注意这里是null,因为BootstrapClassLoader是C++编写的我们获取不到
        System.out.println(classLoader.getParent().toString()+"的父类加载器"+classLoader.getParent().getParent());
    }
结果

我们看到ExtClassLoader的父类得到的结果是null,在调用getParent()返回是null的话,就默认使用启动类加载器作为父加载器。

默认的ClassLoader 的 loadClass 方法中,当parent为null时,就会交给BootStrapClassLoader来处理的,而且ExtClassLoader 没有重写默认的loadClass方法,但是ExtClassLoader也会调用BootStrapLoader类加载器来加载,这就导致“BootStrapClassLoader具备了ExtClassLoader父类加载器的功能”。

用户自定义加载器:用户自定义的加载器

public class CustomLoader extends ClassLoader {//自定义加载器需要继承ClassLoader类

    private String name;

    public CustomLoader(String name) {
        this.name = name;
    }

    @Override
    protected Class findClass(String name) {//自定义类需要重写findClass方法
        byte[] xd = xd();
        return defineClass(name, xd, 0, xd.length);//获取了类的字节流,生成class对象
    }

    public byte[] xd() {
        String name = this.name;
        FileInputStream fileInputStream = null;
        ByteArrayOutputStream byteArrayOutputStream = null;
        byte[] bs=new byte[1024];
        try {
            fileInputStream = new FileInputStream(new File(name));//类的路径
            byteArrayOutputStream = new ByteArrayOutputStream();
            int len ;
            while ((len = fileInputStream.read(bs)) != -1) {//读取类的字节流
                byteArrayOutputStream.write(bs,0,len);//获取类的字节流
            }
            return byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                byteArrayOutputStream.close();
                fileInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    public static void main(String[] args) throws ClassNotFoundException {
        CustomLoader customLoader = new CustomLoader("E:\\javawork\\learn\\src\\main\\java\\com\\example\\reflectLearn\\clazzLoader\\Test.class");
        Class aClass = customLoader.loadClass("E:\\javawork\\learn\\src\\main\\java\\com\\example\\reflectLearn\\clazzLoader\\Test.class");
        System.out.println(aClass);
    }
}

双亲委派机制

概述:

JVM 使用双亲委派机制,确保类只被加载一次,这样可以保证核心的类不被篡改而避免了程序受到破坏,这也是JVM对自我的保护机制,重复加载也会造成数据的丢失,所以也保证了数据的安全性。

工作原理:

双亲委派机制还是比较容易理解的,在jdk8及之前的版本中当加载器要加载一个class时,自己不会先去加载,而是委托自己的父加载器去加载,父加载器也会继续向上委托它的父加载器去加载,当到达顶层(BootstrapClassLoader)时,如果顶层加载器也不想加载,然后才向下委托它的子类加载器们去加载,子加载器会核对自己是否合适加载,不合适就继续向下委托给自己的子加载器,期间只要有加载器决定自己要去加载这个class,这个任务才算成功完成


双亲委派机制图

jdk9之后由于增加了模块化所以流程如下图

java反射机制:

概述:
java反射机制是在JVM运行状态下,对于任意的类,都能够获取它所有的属性和方法,对于任意对象,都能调用它的任意属性和方法,反射机制是动态的获取类信息和动态调用对象方法的一种功能
在反射机制中能获取类对象的方法有三种,按照加载和初始化的完成程度排名为:
1.obj.getClass()
2.Class.forName()
3.Object.class
Object.class与其他两个不同,这个方法获取的类对象是没有经过加载器的链接过程和初始化过程,spring的懒加载中就是使用的这种反射延迟对类的初始化,真正使用的时候才会去初始化
不多bb我们直接上代码,下面是反射的基本使用
我们先创建一个实体类

public class ReflectBean {
    @MyTest(value = "我是成员变量上的注解",name = "name变量")
    private String name;
    private String nickname;

    public ReflectBean() {
    }

    public ReflectBean(String name, String nickname) {
        this.name = name;
        this.nickname = nickname;
    }
    @MyTest(value = "我是方法上的注解",name = "interfaceTest方法")
    public void interfaceTest(){
        System.out.println("我使用了自定义的注解");
    }

    private void say(String name){
        System.out.println(name);
    }
  private void says(String name,String nickname){
        System.out.println(name+" "+ nickname);
    }
    @Override
    public String toString() {
        return "ReflectBean{" +
                "name='" + name + '\'' +
                ", nickname='" + nickname + '\'' +
                '}';
    }
public class ReflectTest {
    @Test
    public void test() throws Exception {
        System.out.println("使用无参构造创建对象");
        Class clazz = ReflectBean.class;
        ReflectBean without = clazz.newInstance();//反射使用无参构造创建对象

        System.out.println("\n调用有参构造方法创建对象");
        Constructor dCr =
                clazz.getDeclaredConstructor(String.class, String.class);//获取有参构造器
        Object o = dCr.newInstance("我是name",
                "我是nickname");//创建有参构造方法创建对象
        ReflectBean reflectBean=(ReflectBean) o;
        System.out.println(reflectBean.toString());

        System.out.println("\n调用非私有方法");
        Method publicSay = clazz.getDeclaredMethod("toString");\\获取toString()方法
        publicSay.invoke(o);

        System.out.println("\n调用私有方法");
        Method privateSay = clazz.getDeclaredMethod("say", String.class);\\获取私有方法say()
        privateSay.setAccessible(true);//设置为true,代表可以访问私有
        privateSay.invoke(o, "我是反射调用的私有方法");

        System.out.println("\n调用私有方法2");
        Method privateSay = clazz.getDeclaredMethod("says", new Class[]{String.class,String.class});\\获取私有方法say()
        privateSay.setAccessible(true);//设置为true,代表可以访问私有
        privateSay.invoke(o, new Object [] {"name 名字","nikename 外号"});
      

        System.out.println("\n调用私有成员变量");
        Field name = clazz.getDeclaredField("name");\\获取私有成员变量name
        name.setAccessible(true);
        String getname = (String) name.get(o);
        System.out.println(getname);

        System.out.println("\n调用所有的方法和类成员变量");
        Method[] declaredMethods = clazz.getDeclaredMethods();//获取所有的方法
        for (Method declaredMethod : declaredMethods) {
            System.out.println(declaredMethod.getName());
        }
        Field[] declaredFields = clazz.getDeclaredFields();//获取所有的成员变量
        name.setAccessible(true);
        for (Field field : declaredFields) {
            System.out.println(field.getName());
        }
    }
}
还有一种是定义注解时去加载注解类类型使用的:

我们先定义一个简单的注解

@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest{
    String value() default "这个简单的注解";
    String name();
}

然后我们把这个注解使用上

@MyTest(value = "我是类上的注解",name = "ReflectBean类")
public class ReflectBean {
    @MyTest(value = "我是成员变量上的注解",name = "name变量")
    private String name;
    private String nickname;

    public ReflectBean() {
    }

    public ReflectBean(String name, String nickname) {
        this.name = name;
        this.nickname = nickname;
    }
    @MyTest(value = "我是方法上的注解",name = "interfaceTest方法")
    public void interfaceTest(){
        System.out.println("我使用了自定义的注解");
    }

我们开始使用反射获取注解

     @Test
    public void test2() throws Exception {
        Class clazz = ReflectBean.class;
        System.out.println("获取类上的注解");
        if (clazz.isAnnotationPresent(MyTest.class)){//判断类是否有注解标注
            MyTest annotation = clazz.getAnnotation(MyTest.class);//获取注解
            System.out.println(String.format("value:%s,name:%s",
                    annotation.value(),annotation.name()));
        }

        System.out.println("\n获取方法上的注解");
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            declaredMethod.setAccessible(true);//获取访问私有的权限
            if (declaredMethod.isAnnotationPresent(MyTest.class)){//判断方法是否有注解标注
                MyTest annotation = declaredMethod.getAnnotation(MyTest.class);//获取注解
                System.out.println(String.format("value:%s,name:%s",
                        annotation.value(),annotation.name()));
            }
        }

        System.out.println("\n获取成员变量上的注解");
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            declaredField.setAccessible(true);
            if (declaredField.isAnnotationPresent(MyTest.class)){//判断成员变量是否有注解标注
                MyTest annotation = declaredField.getAnnotation(MyTest.class);//获取注解
                System.out.println(String.format("value:%s,name:%s",
                        annotation.value(),annotation.name()));
            }
        }

    }

得出结果

代理模式

为其他对象提供一个代理以控制对某个对象的访问。代理类主要负责为被代理类预处理消息、过滤消息、传递消息给委托类,代理类不现实具体服务,而是利用委托类来完成服务,而完成这些流程不会对被代理类有代码侵入,代理类起到了中介的作用。
其实就是代理类为被代理类预处理消息、过滤消息并在此之后将消息转发给被代理类,之后还能进行消息的后置处理。代理类,代理类本身不实现服务,而是通过调用被代理类中的方法来提供服务。

静态代理

public interface IHello {
void sayHello(String str);
}
public class Hello implements IHello{
  @Override
   public void sayHello(String str) {
         System.out.println("hello "+str);
     }
}
public class ProxyHello implements IHello{    
    private IHello hello;    
    public ProxyHello(IHello hello) {
        super();
        this.hello = hello;
    }
//加强的方法
    @Override
    public void sayHello(String str) {
        Logger.start();
        hello.sayHello(str);
        Logger.end();
    }
}
public class Logger {
     public static void start(){
         System.out.println(new Date()+ " say hello start...");
     }
     public static void end(){
         System.out.println(new Date()+ " say hello end");
     }
 }
 public class Test {
     public static void main(String[] args) {
         IHello hello = new ProxyHello(new Hello());
         hello.sayHello("world");    
     }
 }

JDK动态代理

在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。

每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。

我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:

#  proxy:指代我们所代理的那个真实对象
#  method:指代的是我们所要调用真实对象的某个方法的Method对象
#  args:指代的是调用真实对象某个方法时接受的参数
Object invoke(Object proxy, Method method, Object[] args) throws Throwable

Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:

# loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
# interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,
# 如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
# h:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) throws IllegalArgumentException

实例1:简单实现

代理工厂

public class ProxyFatory {
    //obj是被代理的对象
    public static Object getProxyInstance(Object obj){
        //对人
        MyProxy myProxy=new MyProxy();
        myProxy.bind(obj);
        //返回的是代理人myProxy的invoke方法所返回的对象
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),myProxy);
    }
}

代理人必须是InvocationHandler 方法的子类

public class MyProxy implements InvocationHandler {
    private Object obj;

    public void bind(Object obj){
        this.obj=obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        proxy = obj;
        System.out.println("增强方法是:"+method);
        System.out.println("前置增强");
        Object invoke = method.invoke(proxy, args);
        System.out.println("后置增强");
        return invoke;
    }
}

被代理的对象

public class IPhone implements Apple {
    private int id;
    private String name;
    public IPhone() {
    }
    @Override
    public String getIdAndName(int id, String name) {
        return name+id;
    }
}

测试

public class Test {
    @org.junit.Test
    public void test1(){
        Apple proxyInstance = (Apple) ProxyFatory.getProxyInstance(new IPhone());
        String iPhone11 = proxyInstance.getIdAndName(1, "IPhone1");
        System.out.println(iPhone11);
        System.out.println("==========");
        proxyInstance.printName("IPhone12");
    }
}

实例2:方法加强

需要被加强的接口类

public interface OrderService {
    int insertData(Integer i);
}

需要被加强的接口实现类

public class OrderServiceImpl implements OrderService {
    @Override
    public int insertData(Integer i) {
        System.out.println("执行插入业务");
        return i;
    }
}

代理类实现:

public class OrderProxy implements InvocationHandler {
    private Object target;
    public OrderProxy(Object target) {
        this.target = target;
    }
    //前置增强
    private void before(Object o){
        if (o instanceof Integer) {
            System.out.println("OrderService的前置增强....");
        }
    }
    //后置增强
    private void after(Object o){
        if (o instanceof Integer) {
            System.out.println("OrderService后置增强......");
        }
    }
    public Object bind(){
        Class aClass = target.getClass();
        return Proxy.newProxyInstance(aClass.getClassLoader(),aClass.getInterfaces(),this);
    }
//proxy代理类(这个很少用的),method被代理对象需要增强的方法,被代理对象执行方法的参数
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //增强方法的参数,0代表第一个参数
        Object arg = args[0];
        before(arg);
        //需要增强的方法
        Object invoke = method.invoke(target, args);
        after(arg);
        return invoke;
    }
}

测试

 @org.junit.Test
    public void test12() {
        OrderService orderService = (OrderService) new OrderProxy(new OrderServiceImpl()).bind();
        orderService.insertData(1);
    }

CGLIB

jdk的动态代理需要实现接口,CGLIB不需要实现接口
引入依赖

  
            cglib
            cglib
            3.3.0
        

被代理对象

public class CustomerServiceImpl {
    public int insertData(Integer i) {
        System.out.println("执行插入业务");
        return i;
    }
}

代理类

public class CustomerProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("Customer的前置增强....");
        Object o1 = methodProxy.invokeSuper(o, objects);
        System.out.println("Customer后置增强......");
        return o1;
    }
}

测试

@org.junit.Test
    public void test112() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(CustomerServiceImpl.class);
        enhancer.setCallback(new CustomerProxy());
        CustomerServiceImpl customerService = (CustomerServiceImpl) enhancer.create();
        customerService.insertData(1);
    }

Cglib和jdk动态代理的区别?

  • 1、Jdk动态代理:利用拦截器(必须实现InvocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
  • 2、Cglib动态代理:利用ASM框架,对代理对象类生成的class文件加载进来,通过修改其字节码生成子类来处理

什么时候用cglib什么时候用jdk动态代理?

  • 1、目标对象生成了接口 默认用JDK动态代理
  • 2、如果目标对象使用了接口,可以强制使用cglib
  • 3、如果目标对象没有实现接口,必须采用cglib库,Spring会自动在JDK动态代理和cglib之间转换

JDK动态代理和cglib字节码生成的区别?

  • 1、JDK动态代理只能对实现了接口的类生成代理,而不能针对类
  • 2、Cglib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法的增强,但是因为采用的是继承,所以该类或方法最好不要生成final,对于final类或方法,是无法继承的

Cglib比JDK快?

  • 1、cglib底层是ASM字节码生成框架,但是字节码技术生成代理类,在JDL1.6之前比使用java反射的效率要高
  • 2、在jdk6之后逐步对JDK动态代理进行了优化,在调用次数比较少时效率高于cglib代理效率
  • 3、只有在大量调用的时候cglib的效率高,但是在1.8的时候JDK的效率已高于cglib
  • 4、Cglib不能对声明final的方法进行代理,因为cglib是动态生成代理对象,final关键字修饰的类不可变只能被引用不能被修改

Spring如何选择是用JDK还是cglib?(Mybatis使用的是jdk的动态代理)

  • 1、当bean实现接口时,会用JDK代理模式
  • 2、当bean没有实现接口,用cglib实现
  • 3、可以强制使用cglib(在spring配置中加入

SpringAOP日志管理实战

https://www.cnblogs.com/jianjianyang/p/4910851.html

装饰器和代理模式的区别

对装饰器模式来说,装饰者和被装饰者都实现一个接口。对代理模式来说,代理类和真实处理的类都实现同一个接口。此外,不论我们使用哪一个模式,都可以很容易地在真实对象的方法前面或者后面加上自定义的方法。

在装饰模式调用者只想要你把他给你的对象装饰一下。而代理模式使用的是代理对象在自己的构造方法里面new的一个被代理的对象,不是调用者传入的。调用者不知道你找了其他人,他也不关心这些事,只要你把事情做对了即可。

  • 1、装饰器模式强调的是增强自身,在被装饰之后你能够在被增强的类上使用增强后的功能。增强后你还是你,只不过能力更强了而已;代理模式强调要让别人帮你去做一些本身与你业务没有太多关系的职责(记录日志、设置缓存)。代理模式是为了实现对象的控制,因为被代理的对象往往难以直接获得或者是其内部不想暴露出来。

  • 2、装饰模式是以对客户端透明的方式扩展对象的功能,是继承方案的一个替代方案;代理模式则是给一个对象提供一个代理对象,并由代理对象来控制对原有对象的引用;

  • 3、装饰模式是为装饰的对象增强功能;而代理模式对代理的对象施加控制,但不对对象本身的功能进行增强

你可能感兴趣的:(JVM类加载器和反射机制及代理模式)