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代理;
总结:
通过简单学习,学会简单使用反射和代理,通常情况下,使用代理的效率会高于使用反射的效率,所以适合使用代理的情况就使用代理会更好!!!