Class类、反射与代理

Class类、反射与代理:

RTTI:运行时类型识别,主要有两种方式:

  • 传统的RTTI,在编译的时候就已知所有类型,但是不是所有的class都能在编译时明确;
  • 反射机制,允许在运行市发现和使用类型的信息;

一、Class类:

1、概述:

java用Class类来表示运行时的类型信息,

  • Class类的作用是运行时提供或获得某个对象的类型信息;
  • Class类也是类的一种,只是名字和class关键字高度相似;
  • Class类的对象表示创建的类的类型信息,如果你创建一个a类,编译后就会创建一个包含a类型信息的Class对象;
  • Class类只有私有构造函数,无法通过new创建,只能通过JVM创建;
  • 一个class类只用一个相对应的Class对象,无论创建多少实例,JVM只有一个Class对象;

2、JVM加载类:

程序创建第一个对类的静态成员的引用时,JVM的类加载器子系统会将类对应的Class对象加载到JVM中,说明构造器也是类的静态方法,尽管构造器前面并没有static关键字修饰,如果使用new操作符操作一个类的实例对象时,会被当做静态成员的引用;

  • 1、加载:类加载器根据类名找到此类的.Class文件,将该文件包含的字节码加载到内存中,生成class对象;

  • 2、链接:

    • 验证:确保class文件的字节流中包含的信息符合当前虚拟机的要求,不会危害虚拟机自身的安全;

    • 准备:正式为类变量(static 成员变量)分配内存并设置初始值(零值)的阶段,这些变量所使用的内存都将在方法区中分配;

    • 解析:虚拟机将常量池中的符号引用替换为直接引用的过程;

  • 3、初始化:类在静态属性和初始化赋值,以及静态快的执行;

如下图所示:
请添加图片描述

3、Class对象的获取方式:

  • 1、Class.forName():
try {
    Class<?> user = Class.forName("com.test.User");//中间的字符串为类的路径
} catch (ClassNotFoundException e) {
    throw new RuntimeException(e);
}
  • 2、getClass():
    需要先new一个对象,通过该对象来获取Class的引用;
User user = new User();
Class<? extends com.test.User> aClass = user.getClass();
  • 3、类字面常量:
    通过类字面常量的方式获取Class对象的引用,在编译时会受到检查,相对于前面两种方式更加安全简单;
Class<User> userClass = User.class;

数据类型转换:
基本的转换思想:只有同类可转换、低级转高级可自动转换、高级转低级需强制转换;

instanceof:保持了类型的概念,而==或者equals比较的是实际的class对象,没有考虑继承

4、反射:(灵魂)

将类的各个组成部分封装成其他对象;

1、 优点:
  • 1、可以在程序运行中操作对象;
  • 2、解耦,提高程序的可扩展性;

同一个字节码文件在一次程序运行过程中,只会加载一次,不论通过哪一种方式获取的Class对象都是同一个;

5、Class类的理解:(三个阶段)

1、Source源代码阶段:

写好一个类的代码后,通过javac编译成一个class文件;通过类加载器进入第二阶段

2、类对象阶段:

有一个Class类对象,里面封装了成员变量类(数组)、构造方法类(数组)、成员方法类(数组)

  • 成员变量 Field【】fields
  • 构造方法 Constructor 【】 cons
  • 成员方法 Method 【】 methods
3、Runtime运行时阶段:

直接创建对象

6、Class对象功能:

1、获取成员变量们:
  • getFields():获取该类对象中所有public修饰的成员变量,用一个Field数组来存储;

  • getFields(String name):获取指定名称的成员变量;

  • Field[] getDeclaredFields():获取所有成员变量,不考虑修饰符;

  • field.setAccessible(true):忽略权限修饰符的安全检查;

@Data
class User {
    public String name;
    Integer no;
    private String szf;
}
public class ClassTest {
    public static void main(String[] args) {
public class ClassTest {
    public static void main(String[] args) {
        Class<User> userClass = User.class;
        Field[] fields = userClass.getFields();
        for (Field field : fields) {
            System.out.println("getFields 方法 --->"+field);
        }
        try {
            //Field szf = userClass.getField("szf");//会抛出异常
            //System.out.println("getField(\"szf\") 获取 private 修饰的字段 --->"+szf);
            Field szf = userClass.getDeclaredField("szf");
            szf.setAccessible(false);
            System.out.println("getDeclaredField(\"szf\") 获取 private 修饰的字段 忽略检查  --->"+szf);
            Field name = userClass.getField("name");
            System.out.println("getField(\"name\") 获取 public 修饰的字段 --->"+name);

            //Field no = userClass.getField("no");//会抛出异常
            //System.out.println("getField(\"no\") 获取 缺省  的字段 --->"+no);
            Field no = userClass.getDeclaredField("no");
            no.setAccessible(false);
            System.out.println("getField(\"no\") 获取 缺省  的字段  忽略检查 --->"+no);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
        Field[] declaredFields = userClass.getDeclaredFields();//强制获取所有字段,无视修饰符
        for (Field declaredField : declaredFields) {
            System.out.println("getDeclaredFields 方法 --->"+declaredField);
        }
    }
}

    }
}

输出结果:
getFields 方法 --->public java.lang.String com.test1.User.name
getDeclaredField("szf") 获取 private 修饰的字段 忽略检查  --->private java.lang.String com.test1.User.szf
getField("name") 获取 public 修饰的字段 --->public java.lang.String com.test1.User.name
getField("no") 获取 缺省  的字段  忽略检查 --->java.lang.Integer com.test1.User.no
getDeclaredFields 方法 --->public java.lang.String com.test1.User.name
getDeclaredFields 方法 --->java.lang.Integer com.test1.User.no
getDeclaredFields 方法 --->private java.lang.String com.test1.User.szf

获取到成员变量类之后,需要让它和实例化原类绑定,才能回去值和修改值:

Field a = class.getField("b");
Person p = new Person();//得到实例化对象
Object value = a.get(p);//a得到实例化对象,将实例化的p传进去,用Object接受
a.set(b,"eeee");
2、获取构造方法:
  • getConstructor():获取构造器
Constructor constructor1 = personClass.getConstructor();//有参数构造方法需要获取是添加具体的类对象至参数上,如:personClass.getConstructor(String.class);
System.out.println(constructor1);
//创建对象
Object person1 = constructor1.newInstance();
System.out.println(person1);
Object o = personClass.newInstance();
System.out.println(o);
3、获取成员方法们:
  • getMethod():获取方法,也可以根据名称获取;
//执行方法,需要传递一个实例化类:
Method amethod = class.getMethod("a");
User u = new User();
amethod.invoke(u);//有参方法添加相关的参数类型

二、代理模式:(Proxy、Surrogate)

1、概述:

一个人或者一个机构代表另一个人或者另一个机构采取行动,在一些情况下,一个客户不想或者不能直接引用一个对象,而代理对象可以在客户端与目标对象之间起到中介的作用。代理就是为其它对象提供一个代理以控制对某个对象的访问;

代理模式:代理类和被代理类实现共同的接口,代理类中存在指向被代理类的索引,实际执行中通过调用代理类的方法,实际执行的是被代理类的方法;

2、优点:

  • 1、隐藏真实目标类的实现;
  • 2、实现客户与真实目标间的解耦,在不修改真实目标类代码的情况下能做一些额外处理;

3、分类:

  • 静态代理:创建或特定工具自动生成源代码,在进行编译,在程序运行前代理类的class文件已经存在;
  • 动态代理:在程序运行时,运用反射机制动态创建而成,动态代理类的字节码程序运行时由java反射机制动态生成,无需编写源代码;

4、动态代理分类:

基于接口的代理( JDK代理 )和基于继承的代理(CGlib代理 );

一般的动态代理示例:

//接口
public interface FontProvider {
    Font getFont(String name);
}
//真正提供的类
public class FontProviderFromDisk implements FontProvider{
    @Override
    public Font getFont(String name) {
        System.out.println("磁盘上的文字");
        return null;
    }
}
//代理类
public class FontProviderProxy implements FontProvider{

    private FontProviderFromDisk fontProviderFromDisk;

    FontProviderProxy(FontProviderFromDisk frontProviderFromDisk){
        this.fontProviderFromDisk = frontProviderFromDisk;
    }

    @Override
    public Font getFont(String name) {
        System.out.println("调用前");
        Font font = fontProviderFromDisk.getFont(name);
        System.out.println("调用后");
        return null;
    }
}
//测试类
public class MyFont {
    public static void main(String[] args) {
        FontProvider provider = new FontProviderProxy(new FontProviderFromDisk());
        provider.getFont("字体");
    }
}
测试结果:
调用前
磁盘上的文字
调用后
1、JDK代理:

JDK动态代理主要涉及java.lang.reflect包下的Proxy类和InvocationHandler接口:

  • 1、通过java.lang.reflect.Proxy类来动态生成代理类;

  • 2、代理类要实现InvocationHandler接口;

  • 3、JDK代理只能基于接口进行动态代理;

//Proxy类的作用就是用来创建代理对象的类,提供了很多方法,常用的就是newProxyInstance方法,这个方法是用来得到动态的代理对象,并接受三个参数:
public static Object newProxyInstance(ClassLoader loader,Class interfaces,InvocationHandler h) throws IllegalArgumentException;
loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来生成代理对象进行加载;
interfaces:一个interface对象的数组,表示将要给我需要代理的对象提供一组什么接口,如果我提供一组接口给他,就会调用这组接口中的方法;
h:一个InvocationHandler对象,表示的是动态代理在调用方法的时候,会关联哪一个InvocationHandler对象上;

示例:

//接口,动态代理的前提
public interface JDKSubject {
    void request();
    void hello();
}
//目标对象
public class JDKSubjectImpl implements JDKSubject{
    @Override
    public void request() {
        System.out.println("JDKSubjectImpl ------->  request");
    }

    @Override
    public void hello() {
        System.out.println("JDKSubjectImpl ------->  hello");
    }
}
//代理类
public class ProxyJDKSubject implements InvocationHandler {

    private Object subject;

    ProxyJDKSubject(Object subject){
        this.subject = subject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("start");
        Object result = null;
        try{
            result = method.invoke(subject, args);
        }catch (Exception e){
            System.out.println(e.getMessage());
            e.printStackTrace();
        } finally {
            System.out.println("end");
        }
        return result;
    }
}
//测试类
public class Client {
    public static void main(String[] args) {
        JDKSubjectImpl impl = new JDKSubjectImpl();
        InvocationHandler handler = new ProxyJDKSubject(impl);
        JDKSubject o = (JDKSubject) Proxy.newProxyInstance(Client.class.getClassLoader(), new Class[]{JDKSubject.class}, handler);
        o.hello();
        o.request();
    }
}

测试结果:
start
JDKSubjectImpl ------->  hello
end
start
JDKSubjectImpl ------->  request
end
2、CGlib代理模式:
  • CGlib采用ASM字节码生成框架,使用字节码技术生成代理类,为一个类创建子类,并在子类中采用方法拦截的技术拦截所有对父类方法的调用,并顺势加入横切逻辑。CGlib是针对类来实现代理的,原理是对指定的业务类生成一个子类,覆盖其中的业务方法实现代理,**因为采用的是继承,所以不能对final修饰的类进行代理 **,也是通过方法反射调用目标对象的方法;
  • CGlib无需通过接口实现,是通过实现子类的方式完成调用的,Enhance对象把代理对象设置为被代理类的子类来实现动态代理;
    比java反射效率要高;

示例:

//目标类
public class RealSubject {
    public void request(){
        System.out.println("RealSubject is ----> request");
    }
    public void hello(){
        System.out.println("RealSubject is ----> hello");
    }
}
//代理类
public class CGlibProxy implements MethodInterceptor {

    private Object target;

    public Object getInstance(Object target){
        this.target = target;
        Enhancer enhancer = new Enhancer();//创建加强器,用来创建动态代理类
        enhancer.setSuperclass(this.target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("cglib start--");
        Object result = null;
        try {
            result = methodProxy.invokeSuper(o,objects);
        }catch (Exception e){
            System.out.println(e);
            throw e;
        }finally {
            System.out.println("cglib end--");
        }
        return result;
    }
}
//测试类
public class Client {
    public static void main(String[] args) {
        RealSubject instance = (RealSubject) new CGlibProxy().getInstance(new RealSubject());
        instance.request();
        instance.hello();
    }
}
测试结果:
cglib start--
RealSubject is ----> request
cglib end--
cglib start--
RealSubject is ----> hello
cglib end--

5、Spring如何选择动态代理:

  • 1、当bean实现接口时,Spring就会使用JDK动态代理;
  • 2、当bean没有实现接口时,Spring使用CGlib的代理实现;
  • 3、可以通过修改配置文件强制使用CGlib代理;

总结:

通过简单学习,学会简单使用反射和代理,通常情况下,使用代理的效率会高于使用反射的效率,所以适合使用代理的情况就使用代理会更好!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值