1. 认识反射机制
- 在传统的调用模式中,用户要调用某个类所提供的方法,首先要通过该类的构造方法获取该类的实例化对象。随后通过
实例.方法()
的形式实现调用。
在一个类产生实例化对象之后,实际上就可以通过该实例化对象获取所在类的相关信息
,这就是反射机制的作用
Object
类提供了一个getClass()
方法。由于Object
是所有类的父类,因此全部类都可以调用此方法,而此方法返回的是一个Class
类的实例化对象,该类就是反射操作的源头
任何类的实例化对象都可以通过getClass()
方法获取对象所在类的反射处理支持,而后利用Class
类提供的方法获取对象所在类的额名称信息
2. Class类对象实例化
Class
对象是反射处理操作的源头,所有与类反射相关的操作都需要通过Class
实例化对象来完成,Class
类对象的实例化有如下三种方式
- 对象.getClass()
Object
提供了getClass()
方法,所有类产生实例化对象之后都可以进行该方法的调用。该方法需要实例化对象存在
- 类名称.class
为了便于获取指定类Class的实例对象,这种操作机制不需要产生实例化对象,在使用时更加方便。
- Class.forName()
Class
类的内部并没有提供可用的构造方法的时候,但是提供了提个forName()
静态方法。
Date date = new Date();
System.out.println(date.getClass());
System.out.println(Date.class);
try {
System.out.println(Class.forName("java.util.Date"));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
3. 反射与对象实例化
Java原生语法提供了对象实例化处理的关键字new
,按照指定的语法通过构造方法
的形式即可获取指定类的实例化对象。但是有了反射机制之后,可以直接依靠Class
类所提供的方法进行对象实例化。
import domain.Book;
@Test
public void test(){
try {
Class<?> book = Class.forName("domain.Book");
Object o = book.getDeclaredConstructor().newInstance();
Book book1 = (Book) o;
book1.read();
} catch...
}
- 在JDK9以前的开发版本中,推荐的对象实例化的做法为
class.getDeclaredConstructor().newInstance()
,该操作实际上是通过Constructor
处理类实现无参构造方法调用的
3.1 反射与工厂设计模式
工厂模式可以解决类结构设计的耦合问题,通过反射可以进一步完善工厂的设计模式,使其拥有更好的接口适应性
。在使用反射机制进行对象的实例化的时候,传入指定的类名称即可获取该类的实例化对象。可以直接在项目中基于此种机制进行工厂设计模式的完善
- 下面是一个动态工厂类的实现
public class BookFactory {
//工厂类,设置为构造方法为私有
private BookFactory(){
}
public static IBook getInstance(String className){//传入子类的完整名称
try{
Object obj = Class.forName(className).getDeclaredConstructor().newInstance();
//防止转换失败
if(obj instanceof IBook){
return (IBook) obj;
}else{
return null;
}
} catch ...
return null;
}
}
- 本程序基于反射机制实现了工厂设计模式的改进,在
Factory.getInstance()
方法中不再绑定某一个具体的IBook
接口的子类,也没有使用关键字new
而是基于Class
类所提供的方法进行对象的实例化处理。 - 这样的工厂类适用于
IBook
接口的全部子类,子类可以根据需要任意扩充
3.2 反射与单例设计模式
单例模式可以保证在一个JVM进程中某一个类只有唯一的对象实例,从而保证一些核心对象的唯一性。而单例设计模式可以分为饿汉式
单例和饿汉式
单例
饿汉式单例在单例设计模式下不存在线程同步的处理问题,而懒汉式单例需要在每次返回对象前进行对象是否实例化的判断,就有可能出现线程处理不同步的问题,出现线程不安全情况如下,解决办法是给对象实例过程加锁,双重校验
首先我们来一步步实现单例模式
3.2.1 饿汉式单例模式
饿汉式单例模式是将对象的创建权交给了jvm,一旦进程开始运行,则该对象就被初始化,由于创建时机是在程序初始化时,所以一旦创建出来之后,就是单例的。但是注意,这种机制依然会被反射所破解,原单例模式以及破解方法如下
public class Hungry {
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
private Hungry(){//注意构造器私有
}
private final static Hungry HUNGRY = new Hungry();//一旦类被加载,就生成该单例对象
public static Hungry getInstance(){//提供对外访问的方法
return HUNGRY;
}
}
@org.junit.Test
public void HungryTest() throws Exception {
Constructor<?> declaredConstructor = Class.forName("Test.Hungry").getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance();
Hungry instance = Hungry.getInstance();
if(instance.equals(o)){
System.out.println("他们是一个对象");
}else{
System.out.println("不是同一个对象");
System.out.println(o.hashCode());
System.out.println(instance.hashCode());
}
System.out.println(o.getClass());
}
- 即使是使用反射来创建实例,那也是调用类中的构造器来实现的,所以可在构造器中做文章,在构造的时候做校验
private Hungry(){//注意构造器私有
if(HUNGRY!=null){
throw new RuntimeException("请不要尝试通过反射来破坏我的单例!");
}
}
Caused by: java.lang.RuntimeException: 请不要尝试通过反射来破坏我的单例!
at Test.Hungry.<init>(Hungry.java:10)
... 32 more
这种方式是的资源分配全部在进程初始化的时候就做完了,因此在后边即便有多线程,也不会导致资源的错误分配,因此是线程安全的哈
3.2.2 懒汉式单例模式
我们知道饿汉式单例是在进程一运行,在类被加载这个阶段的时候,就产生了这个单例,那么如果说,我整个进程的运行周期中,这个类都没有被用到,但是像上面这样这个单例,搞了很多个大数组来占空间但是又用不到,就很浪费空间。
所以的话,我们能不能想一个办法,使得就是说,我要用到这个单例类了,我才去构造,才去加载
,这种模式就是懒汉式单例了。
由于加载的时机是不确定的,因此类的资源分配的过程是在进程运行的任意时刻,因此在多线程环境下是不安全的,如果采用异步的方法来调用,那么很有可能导致资源的重复分配,因此的话要做同步线程保护,保证单例安全
public class Lazy {
private static volatile Lazy LAZY;
private Lazy(){
if(LAZY!=null){
throw new RuntimeException("请不要尝试通过反射来破坏我的单例!");
}
}
public static Lazy getInstance(){
if(LAZY == null){
synchronized (Lazy.class){
if(LAZY == null){
LAZY = new Lazy();
}
}
}
return LAZY;
}
}
- 来看一下几个比较关键的技术点吧
首先是volatile
,为啥实例对象要用volatitle
来修饰?
volatile
是JAVA提供的轻量级同步机制,它不会引起线程上下文的切换和调度。与之相对的synchronized
是重量级锁。先来了解一下volatile
这种锁机制为我们提供了什么
- 保证可见性
可见性:指当多个线程访问同一个变量的时候,一个线程修改了这个变量的值,其他线程能够立即
看到修改的值
在多线程环境下,一个线程对共享变量的操作对其他线程是不可见的。
这张图讲述了不可见
的原理,在多线程环境下,各个线程有自己的内存空间,对于普通的共享变量来讲,线程A将其修改为某个值发生在线程A
的本地内存中,此时还未同步到主内存中去,而线程B已经缓存了该变量的旧值,所以就导致了共享变量值的不一致。
这种问题的解决方法可以通过加锁来进行同步,一种想法就是在一个线程操作共享变量的时候,对这个共享变量加锁,然后其他线程就无法对这个共享变量进行读写了,但是这种方式的实现是通过synchronized
或者Lock
等重量级来实现的,比较合理的方式是volatile
其原理是:当写一个volatile
变量的时候,线程本地内存中的变量会被强制刷新到主内存中去,这个写操作会导致其他线程中的volatile
变量缓存无效,这实际上也是一种缓存一致性的实现方法
- 禁止指令重排
这个性质可以保证程序执行的顺序按照代码的先后顺序执行,重排序不会影响单线程的运行结果,但是对多线程会有相当大的影响,单例模式中的DCL(双重校验锁)
就是一个例子,其原理是生成一个内存屏障,这个屏障的作用是在编译器生成的指令流中确定一个点,使得此点之前的所有读写操作都执行后才可以执行此点之后的操作。
好了。言归正传,为什么我们的单例模式要用volatile
呢
public class TestInstance{
private volatile static TestInstance instance;
public static TestInstance getInstance(){ //1
if(instance == null){ //2
synchronized(TestInstance.class){ //3
if(instance == null){ //4
instance = new TestInstance(); //5
}
}
}
return instance; //6
}
}
如果没有volatile
关键字,在第五行会出现问题,instance = new TestInstance();
可以分级为三行代码
a. memory = allocate()
//分配内存
b. ctorInstanc(memory)
//初始化对象
c. instance = memory
//设置instance指向刚分配的地址
上面的代码在编译运行的时候,可能会出现排序a-b-c
排序为a-c-b
,这是因为我们发现b和c均依赖于a,但是b和c并不互相依赖,所以编译器可能会将bc语句进行重排序。
假设此时A执行的过程中发生指令重排序,也就是先执行了a和c,没有执行过b,那么a线程执行了c导致实例指向了一段地址,然后这时候发生了中断,然后B进程上CPU执行了,然后B上来发现,诶你这个instance不是null,指向了一个内存区域,那么应该就是被实例化了吧,然后这时候它就会把一个没有执行过b
的实例返回回去,这时候这个实例没有经过初始化,是危险的,所以的话,需要生成一个内存屏障,保证实例对象经历内存分配
->初始化
->指向内存空间
->完成
,才是合理的
然后或许你会有这种问题,如果在按照顺序执行的过程中,A线程执行了ab,然后时间片超时,B上了CPU,这时候B发现它是空,会不会去初始化一个新的实例呢?
,这个肯定是存在的,解决办法是给这个实例加锁,也就是说如果有线程在生成这个共享变量的时候,其他线程阻塞并等待,手段就是给这个实例的类模板加锁,这样就肯定能保证只生成一个对象
public static Lazy getInstance(){
if(LAZY == null){//如果没有这个对象
synchronized (Lazy.class){//给对象加锁,防止生成多个实例
if(LAZY == null){//加锁完毕后,第一个进来的肯定会检测到null,然后生成实例
LAZY = new Lazy();
}//一个线程结束之后,其他线程再进来,这时候就会检测到非null了直接返回
}
}
return LAZY;
}
好了,单例模式的核心就是上面的分析,但是现在还有一个问题,就是这个过程依然可以通过反射
来破解单例,而且几乎是无解的,破解代码如下
Class objectClass = Singleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton instance = Singleton.getInstance();
Field flag = objectClass.getDeclaredField("flag");
flag.setAccessible(true);
flag.set(instance , true);
Singleton newInstance = (Singleton) constructor.newInstance();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
这个方法把通过反射创建实例和调用静态方法getInstance()
获得实例的位置互换了,所以一开始通过反射创建实例调用构造器
,此时构造器中判断instance!=null
是无用的。这是为什么呢?如果反射先走的构造器创建实例方法,那么instance!=null
的异常就不会触发,无论后续如何完善,看了一些资料都是说加信号量等方法,但是均可以通过反射来修改其内部数据,都是没办法阻止反射的破解的。
唯一的办法是使用enum
类,enum类可以保证仅有实例存在于虚拟机中。
public enum Lazy {
INSTANCE;
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
private Lazy(){
System.out.println("构造器方法");
}
public static Lazy getInstance(){
return INSTANCE;
}
}
我们发现如果想要破解反射,无非就是从clone()
方法入手,construcotr.newInstance()
方法入手,从序列化反序列化入手,但是jvm为我们解决了这个问题。
具体原理见:https://blog.csdn.net/CS5686/article/details/123968025?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166324087716782395383287%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=166324087716782395383287&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-123968025-null-null.142^v47^control_1,201^v3^control_1&utm_term=%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%20enum&spm=1018.2226.3001.4187
4. 反射功能获取类结构操作
反射机制除了可以实现对象的实例化处理之外,还可以依据Class
类所提供的方法来进行类的结构解析处理,例如,指定类所实现的父接口,继承的父类以及所在包的信息
- public Package getPackage():获取类的所在包
- public String getPackageName():获取程序类所在包的名称
- public Class<? super T> getSuperClass:获取其实现的父类
- public Class<?>[] getInterfaces():获取所实现的全部接口
4.1 反射调用构造方法
类的对象实例化需要依靠构造方法进行属性的初始化处理,而在一个类中所有定义的构造方法也可以利用反射机制来获取
class Book { // Book类中的构造方法使用了不同的访问权限
public Book() {}
protected Book(String title) {}
Book(String title, String author) {}
private Book(String title, String author, double price) {}
}
public class YootkDemo {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("com.yootk.Book");
{
System.out.println("------------ getConstructors()获取构造方法 ---------------");
for (Constructor<?> constructor : clazz.getConstructors()) { // 迭代输出构造
System.out.println("【1 - 构造信息】" + constructor);
}
}
{
System.out.println("--------getDeclaredConstructors()获取构造方法 --------------");
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) { // 迭代输出构造
System.out.println("【2 - 构造信息】" + constructor);
}
}
}
}
构造类的方法
4.2 反射调用方法
在反射机制中,可以通过Class
类获取方法相关的定义内容
Method类的常用方法
class Book { // 自动生成无参构造
// 标准的简单Java类,通过属性的名称就可以得到其对应的setter与getter方法的名称
private String title; // 成员属性
// Setter方法定义时要以“set”单词开头,而后属性名称的首字母要求大写
public void setTitle(String title) {
this.title = title;
}
// Getter方法定义时要以“get”单词开头,而后属性名称的首字母要求大写
public String getTitle() {
return title;
}
}
public class YootkDemo {
public static void main(String[] args) throws Exception {
String fieldName = "title"; // 成员属性名称
String fieldValue = "Java就业编程实战"; // 成员属性内容
Class<?> clazz = Class.forName("com.yootk.Book"); // Class实例
// 1、如果要想通过Book类实现属性的操作,那么首先一定要获取Book类的对象
Object object = clazz.getDeclaredConstructor().newInstance(); // 反射对象实例化
// 2、要找到指定调用的setter,setTitle()方法的参数类型为String
Method setMethod = clazz.getMethod("set" + initcap(fieldName), String.class);
// 3、利用反射结合对象实例(不是具体的Book类,而是Object类型)同时传入所需要的参数
setMethod.invoke(object, fieldValue); // 等价于“book类对象.setTitle(fieldValue)”
// 4、找到getter方法,getter方法没有参数类型
Method getMethod = clazz.getMethod("get" + initcap(fieldName));
// 5、通过反射获取getter方法的返回值
Object value = getMethod.invoke(object); // 等价于“book类对象.getTitle()”
System.out.println(value);
}
public static String initcap(String str) { // 首字母大写
if (str == null || "".equals(str)) { // 内容为空
return str; // 直接返回
}
if (str.length() == 1) { // 字符串长度为1
return str.toUpperCase(); // 整体转大写
} else { // 首字母大写
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
}
}
4.3 反射调用成员属性
Fidld类的常用方法
4.4 Unsafe工具类
reflect
本身所描述的是一种反射的基本操作功能,还有一个比较特殊的反射类Unsafe
,该类最大的特点就是可以在没有实例化对象的情况下进行类中方法调用,记得invoke()
是需要传入对象实例的
由于Unsafe
类在使用的时候是直接与操作系统的底层进行交互的,所以该类并没有提供任何的对象实例化方式,但是在其内部却提供有一个theUnsafe
的实例化对象,此时就可以通过反射机制获取此成员属性,达到Unsafe
的对象实例化的目的
class Singleton {
private Singleton() { // 构造方法只进行输出
System.out.println("【Singleton】实例化类对象"); // 执行此语句表示对象实例化
}
public void print() {
System.out.println("沐言科技:www.yootk.com");
}
}
public class YootkDemo {
public static void main(String[] args) throws Exception {
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); // 通过反射获取成员
unsafeField.setAccessible(true); // 取消封装
Unsafe unsafe = (Unsafe) unsafeField.get(null); // 获取UnSafe对象
Singleton singleton = (Singleton) unsafe.allocateInstance(Singleton.class); // 获取实例
singleton.print(); // 方法调用
}
}
Unsafe
类定义的时候并没有提供构造方法或静态方法,所以在使用时必须通过反射
来获取该类中的theUnsafe
内置成员属性,该成员属性对应的类型为Unsafe
。获取Unsafe
类的对象后就可以利用allocateInstance()
方法获取指定类的对象(此时的对象没有经过正常的java对象实例化处理)
,所以不会调用类中的无参构造方法,这样就可以在没有实例化对象的情况下实现类中方法的调用
Unsafe
类绕过了JVM管理,从实际的开发来说,这种做法会完全绕过JVM对实例化对象的管理,使得对某些对象不便于及时进行回收。
5. ClassLoader类加载器
JVM进行程序类的加载时需要类加载器,JAVA内部提供类加载器的支持。程序要在JVM中执行,一定要通过JVM加载所需要的字节码文件,而字节码文件的加载需要通过ClassLoader
类加载器来完成,只有通过类加载器的程序类文件才可以被JVM所解析、执行。
public static void main(String[] args) throws Exception {
ClassLoader loader = Book.class.getClassLoader(); // 获取ClassLoader
while (loader != null) { // 存在ClassLoader
System.out.println(loader);
loader = loader.getParent(); // 获取父加载器
}
}
- JDK提供了如下三种ClassLoader
- Bootstrap:JVM系统内部的类加载器的处理机制,JVM原生类加载机制,专门用于加载系统类
(String,Date)
- PlatformClassLoader:平台类的加载器
(JDK8中是叫ExtClassLoader)
可以加载第三方扩展的jar包 - AppClassLoader:应用程序加载类,可以实现系统中类程序文件的加载
- Bootstrap:JVM系统内部的类加载器的处理机制,JVM原生类加载机制,专门用于加载系统类
在所有给定的类加载器中,由于Bootstrap
类加载器是JVM
自行管理的,所以用户无法获取该类加载器的信息(获取该类加载器时会返回null)
。
5.1 双亲委派机制
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
// -----??-----
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 首先,检查是否已经被类加载器加载过
Class<?> c = findLoadedClass(name)
if (c == null) {
try {
// 存在父加载器,递归的交由父加载器
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 直到最上面的Bootstrap类加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}
5.2 自定义文件类加载器
除了使用内置的类加载器的之外,也可以根据自身的需要来创建自己的类加载器。要通过类的文件路径非Classpath设置路径
进行加载,而后将指定字节码文件的类进行对象的实例化处理,这一功能就只能基于自定义类加载器实现
ClassLoader是一个抽象类,该类提供一个defineClass()
方法,该方法可以将所读取到的字节流内容转为Class
对象实例,这样就可以基于Class
类实现指定加载类的对象实例化处理。
public class Message {
public String echo(String msg) { // 字符串响应处理
return "【ECHO】" + msg;
}
}
// 如果要想进行加载器的创建则必须继承自“ClassLoader”父类,并且依据方法进行调用
class FileClassLoader extends ClassLoader { // 创建一个专属的文件加载器
// 定义要加载的字节码文件所处的绝对路径,必须通过File进行路径的拼凑
private static final String CLASS_FILE_PATH = "H:" + File.separator + "Message.class";
// 此时自定义了一个类加载的方法,这个类加载的方法一定不要重名
public Class<?> loadData(String className) throws Exception {
byte data[] = this.loadFileClassData(); // 加载要使用的类文件
if (data != null) { // 类数据加载成功
return super.defineClass(className, data, 0, data.length); // 创建Class实例
}
return null;
}
// 自定义一个新的方法,将根据给定的文件路径进行加载,为了简化设计故没有进行严格的异常控制
private byte[] loadFileClassData() throws Exception {
// 获取要加载文件的二进制字节输入流对象
InputStream input = new FileInputStream(new File(CLASS_FILE_PATH));
// 最终需要将所有的数据保存在内存之中,并且要利用字节数组返回
ByteArrayOutputStream bos = new ByteArrayOutputStream(); // 内存输出流
input.transferTo(bos); // 数据读取
byte data[] = bos.toByteArray(); // 获取全部Class文件数据
input.close(); // 关闭输入流
bos.close(); // 关闭输出流
return data; // 返回二进制数据
}
}
public class YootkDemo {
public static void main(String[] args) throws Exception {
FileClassLoader fileClassLoader = new FileClassLoader(); // 实例化自定义类加载器
Class<?> clazz = fileClassLoader.loadData("com.yootk.util.Message"); // 加载类
Object messageObject = clazz.getDeclaredConstructor().newInstance(); // 对象实例化
Method method = clazz.getMethod("echo", String.class); // 获取echo()方法对象
System.out.println(method.invoke(messageObject, "沐言科技:www.yootk.com")); // 方法调用
}
}
文件类加载器无法直接使用类的名称。上述程序的全部操作都是基于反射处理的,并且在获得Message类的实例化对象之后也没有进行强制cast,这是因为在当前项目中不存在Message类的定义,一旦转型在编译就会出错,因为项目都没有这个类嘛,当然无法编译链接到这个类
5.3 自定义网络类加载器
ClassLoader
的方便之处在于可以由用户任意定义加载程序文件的位置,除了在文件中加载之外,也可以通过网络服务器进行加载。
可以将类加载器延伸到分布式网络环境之中,例如,可以在网络服务器中提供一个Message.class
字节码文件,而后基于Socket
连接将此字节码文件发送到本地应用,这样本地应用就可以直接获取该类的Class
对象并进行方法调用。
下面基于上图的逻辑,实现一个简易的网络远程连接类传输使用
- Server创建服务端处理线程类,通过线程类实现字节码数据的响应
package server;
import java.io.*;
import java.net.Socket;
public class ClassDataLoadThread implements Runnable{
private Socket client;//客户端socket
public ClassDataLoadThread(Socket client){
this.client = client;
}
@Override
public void run() {//线程处理
try {
this.client.getOutputStream().write(this.loadClassData());
this.client.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public byte[] loadClassData() throws Exception {//读取class字节码文件
FileInputStream fileInputStream = new FileInputStream(new File("H:" + File.separator + "Message.class"));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
//1.首先利用文件输入流将文件存入
//然后利用拷贝方法,将文件输入流存储到字节流中
//再由字节流输出到数组
fileInputStream.transferTo(byteArrayOutputStream);
byte[] bytes = byteArrayOutputStream.toByteArray();
fileInputStream.close();
byteArrayOutputStream.close();
return bytes;
}
}
- 定义服务端请求类,该类中将每一个接收到的请求转为子线程进行数据响应
public class Application {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(9999);//绑定端口号
boolean flag = true;//持续交互标记
while (flag){
Socket client = serverSocket.accept();
new Thread(new ClassDataLoadThread(client)).start();
}
serverSocket.close();
}
}
- 定义网络类加载器,该类通过socket发起服务器连接并接收字节码数据
public class NetClassLoader extends ClassLoader{//网络类加载器
//此时需要通过特定的网络服务器进行类的加载,就必须明确定义出服务器的地址以及连接端口
public static final String SERVER_HOST = "localhost";//本地回环
public static final int SERVER_PORT = 9999;//交互端口
//此时自定义一个类加载的方法,这个类加载的方法不要重名
public Class<?> loadData(String className) throws Exception {
byte[] data = this.loadFileClassData();
if(data!=null){
return super.defineClass(className,data,0,data.length);
}
return null;
}
//自定义一个新的方法,将根据给定的文件路径进行加载,为了简化设计没有进行严格的异常控制
private byte[] loadFileClassData() throws Exception {
//获取要加载的二进制字节输入流对象
Socket client = new Socket(SERVER_HOST, SERVER_PORT);
//获取网络服务器响应给客户端的字节输入流
InputStream inputStream = client.getInputStream();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
inputStream.transferTo(byteArrayOutputStream);
byte[] data = byteArrayOutputStream.toByteArray();
inputStream.close();
client.close();
byteArrayOutputStream.close();
return data;
}
}
- 使用网络类加载器加载Message类并进行方法调用
public static void main(String[] args) throws Exception {
NetClassLoader netClassLoader = new NetClassLoader();
Class<?> aClass = netClassLoader.loadData("example.Message");
Object o = aClass.getDeclaredConstructor().newInstance();
Method echo = aClass.getMethod("echo", String.class);
echo.invoke(o,"原子弹!!!!");
}
6. 反射与代理设计模式
代理模式可以实现真实业务和辅助业务的有效拆分
如果要创建一个用于网络消息发送的业务接口,如果想要保证消息发送成功,那就必须在消息发送之前进行消息服务器的连接,在消息发送之后为了节约服务器资源也需要及时断开与消息服务器之间的连接。
此时的最佳实现方案就是基于代理设计模式,将真实业务包裹在代理主题之中,利用代理主题实现与业务有关的辅助性定义方法(网络连接、连接关闭)
,随后在核心的业务方法之中进行操作的整合,并通过真实业务主题提供的方法进行最终消息的发送与响应处理
6.1 静态代理设计模式实现
public class Test2 {
interface IMessage{ //定义核心业务接口
String echo(String str);//核心业务处理方法
}
class IMessageImpl implements IMessage{//真实业务的实现类
@Override
public String echo(String str) {
return "[echo]===>"+str;
}
}
class MessageProxy implements IMessage{//代理业务实现类
private IMessage target;//保存真实业务的实例
public MessageProxy(IMessage target) {
this.target = target;
}
//提供辅助方法
public boolean connect(){
System.out.println("[connecting]:正在建立连接");
return true;
}
public void close(){
System.out.println("[closing]:正在关闭连接");
}
@Override
public String echo(String str) {
try {
if(this.connect()){
return this.target.echo("str");//调用真实业务实例
}
}finally {
this.close();
}
return "[错误]:连接失败";
}
}
}
public class Factory {
public Factory() {}
public static IMessage getInstance(){
return new Test.Test2.MessageProxy(IMessageImpl());
}
}
使用代理模式虽然可以有效的解决类功能结构的设计问题,但是传统的静态代理模式之中一个代理类只能够为一个接口提供代理服务,而如果按照此类的设计方式设计编码,那么项目之中就会到处充满功能雷同的代理类。
假设要实现一个消息的发送,服务器可以接受的消息类型包括文本、图像以及视频,这样在设计的时候就需要设计出三个不同的功能接口,分别对应的是文本消息、图像消息以及视频消息,这三个接口最终都向同一台服务器发出消息请求,如果此时按照静态代理设计模式的设计来讲,每一个消息接口都需要提供一个代理类,利用代理类来实现服务器的连接与关闭
6.2 动态代理设计模式实现
静态代理设计模式仅仅只能够作为其基础的核心原理,而在实际的开发之中就需要通过动态代理设计模式来实现代理类的高可用
设计
动态代理设计模式是在JDK1.3
的时候引入的一项开发技术,由于该实现技术中要求若干个不同的功能接口使用同一个代理类
,所以提供了InvocationHandler
public interface InvocationHandler {
/**
* 该方法主要实现了动态代理方法的定义,并且通过该方法实现真实主题类方法的调用与结果返回
* @param proxy 调用该方法的代理类实例
* @param method 调用的接口方法对象(真实主题方法对象)
* @param args 接口方法调用时所传递的参数
* @return 接口方法调用的结果
* @throws 方法调用时所产生的异常
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
InvocationHandler
仅仅规定了动态代理色剂模式的代理方法定义,而真正在代理设计模式中最重要的就是要获取一个与核心业务接口有关的代理对象,而这一功能就需要通过Proxy
类所提供的newProxyInstance()
方法来实现。Proxy.newInstance()
方法将基于真实主题实现类的类加载器、实现接口类型来动态的创建一个与真实实现类父接口相关的对象实例,这样在通过接口进行业务调用的时候,调用的是动态代理对象对象实例,具体的代理操作是由invoke()
来完成的
package Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Test2 {
interface IMessage{ //定义核心业务接口
String echo(String str);//核心业务处理方法
}
class IMessageImpl implements IMessage{//真实业务的实现类
@Override
public String echo(String str) {
return "[echo]===>"+str;
}
}
class ServerProxy implements InvocationHandler{//此类为公共的代理类
private Object target;//核心业务对象
/**
* 动态代理设计模式需要保存真实业务实现对象,那么就需要将真实业务实现对象传递到代理类之中
* 同时基于Proxy系统类,动态地创建一个代理业务类的对象
* @param target 要绑定的真实业务实现对象
* @return 系统生成的代理类对象
*/
public Object bind(Object target){
this.target = target;
//获取真实业务实现对象的类加载器:这样可以解析.class文件的结构,从而实现接口代理度夏宁的定义
//所有的动态代理都是基于接口的设计应用,所以使用this描述的是本接口的实例化对象
//当前的类为InvocationHandler接口子类,所以要使用this描述的是本接口的实例化对象
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
//代理方法中需要进行方法的反射调用,反射调用需要实例化对象,method对象,方法参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if(this.connect()){
//连接成功则调用真实业务对象
return method.invoke(this.target,args);
}
}finally {
this.close();
}
return "error";
}
//提供辅助方法
public boolean connect(){
System.out.println("[connecting]:正在建立连接");
return true;
}
public void close(){
System.out.println("[closing]:正在关闭连接");
}
}
}
class Factory{
public Factory() {}
public static IMessage getInstance(){
return (IMessage) new ServerProxy().bind(new IMessageImpl());
}
}
public static void main(String[] args) {
IMessage message = Factory.getInstance();
System.out.println(message.echo("yes"));
}
本程序实现了动态代理设计模式, 所有的真实业务实现类需要绑定在ServerProxy
的动态代理类之中,这样就可以通过Proxy
的newProxyInstance()
方法创建代理对象,而代理对象再在进行方法调用的时候会调用InvocationHandler
所提供的invoke()
方法实现真实业务的调用
在JDK所提供的动态代理机制中可以发现,Proxy.newProxyInstance()
方法在调用的时候需要传入类实现的接口信息,如果在代理设计模式中没有使用到接口,那么是无法正常返回代理对象的,所以利用JDK实现代理设计模式的前提就是接口存在
6.3 CGLib动态代理机制
CGLib
可以实现基于类的代理结构,虽然JDK提供了良好的动态代理技术支持,但是其在实现过程中必须依赖于接口,在没有接口实现的情况下就无法使用Proxy.newProxyInstance()
进行代理对象的创建。为了解除这种限制,CGlib
可以在没有接口实现的情况下依然创造出代理对象
class IMessage{ //定义核心业务接口
String echo(String str){
return "[echo]===>"+str;
}
}
class ServerProxy implements MethodInterceptor{//此类为公共的代理类
private Object target;//核心业务对象
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
try{
if(this.connect()){
return method.invoke(this.target,objects);
}
}finally {
this.close();
}
return "error";
}
//代理方法中需要进行方法的反射调用,反射调用需要实例化对象,method对象,方法参数
//提供辅助方法
public boolean connect(){
System.out.println("[connecting]:正在建立连接");
return true;
}
public void close(){
System.out.println("[closing]:正在关闭连接");
}
}
public static void main(String[] args) {
IMessage iMessage = new IMessage();//核心业务对象
Enhancer enhancer = new Enhancer();//代理控制类
enhancer.setSuperclass(iMessage.getClass());
enhancer.setCallback(new ServerProxy(iMessage));
IMessage iMessage1 = (IMessage) enhancer.create();
System.out.println(iMessage1.echo("123"));
}
该程序基于CGlib实现了动态代理模式,可以发现CGlib所实现的动态代理设计模式是基于拦截的模式完成的,也就是在进行接口调用的时候首先定义一个方法拦截器,而后通过拦截器实现真实业务方法的调用。
7.反射与Annotation
7.1 注解
Annotation是JDK1.5及其之后的版本提供的重要的开发技术。
- AnnotatedElement接口
Class
类可以直接实现该接口,在AnnotatedElement接口中提供了获取注解的处理方法,在于Class的反射机制操作完成之后,就可以方便的通过类、方法或者属性来获取指定位置上所定义的注解
要注意:只有运行时注解才可以获取,在JAVA中得到所有注解都是有其运行范围的,而JDK默认提供的@Override
注解仅在JAVA源代码编译
的时候生效。
- 注解定义结构
除了使用默认的Annotation之外,也可以根据自身的需要创建与业务有关的自定义注解,在注解定义的时候需要使用@Rentention
定义注解的作用范围,并且通过@Target
定义该注解应用在何种结构之中类声明处、方法声明处或者类成员属性声明处
7.2 工厂模式的改进
如果想要最大能力的发挥出注解机制的操作特点,需要集合一些具体的实例来进行分析,本次可以对之前所学习的工厂设计模式进行Annotation注解话的设计改进
public class Test4 {
@Target({ ElementType.TYPE }) // 注解定义在类声明处
@Retention(RetentionPolicy.RUNTIME) // 运行时有效
@interface Action { // 自定义注解
public String value(); // 注解属性
}
@Target({ ElementType.CONSTRUCTOR }) // 注解在构造方法处定义
@Retention(RetentionPolicy.RUNTIME) // 运行时有效
@interface Instance { // 工厂类配置
public String value(); // 注解属性
}
interface IChannel extends AutoCloseable { // 传输通道
public boolean build(); // 建立通道
}
class InternetChannel implements IChannel { // 互联网通道
@Override
public boolean build() {
System.out.println("【InternetChannel】建立互联网通讯通道。");
return true;
}
@Override
public void close() throws Exception {
System.out.println("【InternetChannel】关闭互联网通讯通道。");
}
}
class RadioChannel implements IChannel { // 无线电通道
@Override
public boolean build() {
System.out.println("【RadioChannel】建立无线电通讯通道。");
return true;
}
@Override
public void close() throws Exception {
System.out.println("【RadioChannel】关闭无线电通讯通道。");
}
}
}
class Factory { // 编写工厂类
private Factory() {}
public static <T> T getInstance(String className) { // 泛型工厂类
try { // 在工厂类基于反射机制,实现指定类对象实例化处理
return (T) Class.forName(className).getDeclaredConstructor().newInstance();
} catch (Exception e) {
return null;
}
}
}
@Action("com.yootk.RadioChannel") // 通过注解配置连接通道
class Message { // 消息处理
private IChannel channel; // 连接通道
@Instance("com.yootk.Factory")
public Message() { // 构造方法
try { // 获取注解配置属性
Action actionAnnotation = super.getClass()
.getAnnotation(Action.class); // 获取类上的Annotation
Instance instanceAnnotation = super.getClass().getConstructor()
.getAnnotation(Instance.class); // 获取构造方法上的注解
String factoryClassName = instanceAnnotation.value(); // 获取子类名称
Class<?> factoryClazz = Class.forName(factoryClassName);
this.channel = (IChannel) factoryClazz.getMethod("getInstance", String.class)
.invoke(null, actionAnnotation.value()); // 获取工厂方法
} catch (Exception e) {}
}
public String echo(String msg) throws Exception { // 消息响应
String echoMessage = "〖ERROR〗消息发送失败!";
if (this.channel.build()) { // 通道创建成功
echoMessage = "【ECHO】" + msg; // 创建回应信息
this.channel.close();
}
return echoMessage;
}
}
public class YootkDemo { // 李兴华高薪就业编程训练营
public static void main(String[] args) throws Exception { // 沐言科技:www.yootk.com
System.out.println(new Message().echo("沐言科技:www.yootk.com")); // 消息响应
}
}
使用传统的工厂设计模式都要通过Factory.newInstacne()
方法进行对象实例化的处理,而寻找可以直接基于自定义的@Action
注解来配置要使用IChannel
接口子类,同时,通过@Instance
注解配置可以直接实例化IChannel
接口对象的工厂类,这样在获取Message
类对象的时候可以基于构造方法来获取IChannel
接口实例,从而实现消息的响应处理
8. 反射与简单JAVA类
简单JAVA类是项目开发中最基础的类结构,也拥有良好的设计规则,在传统的项目开发都需要通过提供的setter()
方法来实现属性内容的设置,当需要设置的属性过多的时候,必然会造成大量重复的代码结构出现。
public class Emp {
private String ename;
private String job;
public String getEname() { // 属性获取
return ename;
}
public void setEname(String ename) { // 属性设置
this.ename = ename;
}
public String getJob() { // 属性获取
return job;
}
public void setJob(String job) { // 属性设置
this.job = job;
}
@Override
public String toString() {
return "雇员姓名:" + this.ename + "、雇员职位:" + this.job;
}
}
假设此时的内容需要通过网络传输,而在网络传输的时候,所有的数据采用了属性名称:属性内容|属性名称:属性内容
的形式,那么传统的做法就是对当前的字符串进行拆分,而后根据属性名称找到匹配的setter
方法,将相关的数据保存在emp
对象的属性之中
8.1 属性的自动赋值
在当前的给定字符串之中实际上包含了属性名称与属性内容两个信息的组合,而属性名称是和类中的属性名是对应的,那么根据JAVA类的定义规则,只需要将属性名称的首字母大写同时为其追加set前缀即可获得设置属性名称的函数。
在反射机制的处理中,每一个类的结构可以通过Class
实例来获取,在本次操作中可以基于Class
实现简单类的的无参构造的调用并实例化对象,随后通过成员属性的名称来获取对于的Field
对象实例,这样就可以基于属性的类型获取相关的Method
对象实例,最终invoke
相关对象即可。
这种方法适用于所有的简单Java类赋值处理,而为了便于处理,最合理的设计就是创建一个工厂类,该工厂类可以依据简单Java类的Class实例与特定结构的字符串实现简单Java类的对象实例化与属性赋值
- 定义工厂结构模型
public class ObjectInstanceFactory {
private ObjectInstanceFactory(){}//禁止实例化本类对象
/**
* 实现简单java类的对象实例创建以及属性设置处理
* @param clazz 要进行简单操作的简单java类的class对象
* @param value 包含有属性内容的字符串"属性名称:属性内容|属性名称:属性内容"
* @param <T> 由于可以返回所有任意的java类对象,基于泛型配置
* @return 返回指定简单java类对象
*/
public static <T> T create(Class<?> clazz,String value){
return null;
}
}
- 应用类
public static final String DATA="ename:Lumxi|job:student";
public static void main(String[] args) {
//此时的应用主类不设计具体的反射操作细节,全部的由工厂来处理完成
Emp emp = ObjectInstanceFactory.create(Emp.class, DATA);
System.out.println(emp);
}
- 属性的自动赋值实现思路
- 成员拆分类
public class IFieldContentSplit {
public static final String SPLIT_VALUE = "|";
public static final String FIELD_SPLIT_VALUE = ":";
public static String[] splitAll(String value){
return value.split(SPLIT_VALUE);
}
public static String[] splitField(String value){
return value.split(FIELD_SPLIT_VALUE);
}
}
- 字符串工具类
public class StringUtil {
private StringUtil(){}
public static String initCap(String value){
if(value == null || value.length() == 0){
return value;
}
if(value.length() == 1){
return value.toUpperCase(Locale.ROOT);
}
return value.substring(0,1).toUpperCase(Locale.ROOT)+value.substring(1);//首字母大写
}
}
- BeanUtil工具类
private BeanUtil(){}
/**
* 实现对象反射属性赋值的处理操作
* @param object 要进行实例化处理的对象实例(不允许为空)
* @param value 满足与指定的格式要求的data
*/
public static void setObjectValue(Object object,String value){
String[] all = IFieldContentSplit.splitAll(value);
for (String content : all) {
try{
String[] fields = IFieldContentSplit.splitField(value);//获取属性名称和方法名
Field field = object.getClass().getDeclaredField(fields[0]);//找到属性
//找到属性所对应的setter方法,在获取setter方法的时候必须明确设置方法的设置类型
Method method = object.getClass().getMethod("set" + StringUtil.initCap(fields[0]), field.getType());
method.invoke(object,fields[1]);//通过反射调用
}catch (Exception e){
e.printStackTrace();
}
}
}
- 工厂方法的实现
private ObjectInstanceFactory(){}//禁止实例化本类对象
/**
* 实现简单java类的对象实例创建以及属性设置处理
* @param clazz 要进行简单操作的简单java类的class对象
* @param value 包含有属性内容的字符串"属性名称:属性内容|属性名称:属性内容"
* @param <T> 由于可以返回所有任意的java类对象,基于泛型配置
* @return 返回指定简单java类对象
*/
public static <T> T create(Class<?> clazz,String value){
try {
Object object = clazz.getDeclaredConstructor().newInstance();
BeanUtil.setObjectValue(object,value);
return (T)object;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return null;
}
此时需要对已有程序类的功能进行扩充,使其可以针对于不同的数据类型实现响应的setter
方法
简单java类属性实现自动赋值操作的核心关键在于使用了Method
类的invoke
方法,而该方法在进行方法反射调用的时候,所有设置的参数内容都是以Object动态参数的形式传递的,所以如果想要实现不同的数据类型的设置,就需要对属性的内容进行转型处理
- BeanUtil工具类的改进
package com.example.reflectDemo;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
public class BeanUtil {
//此时需要实现字符串转Date的处理,考虑到多线程的影响,需要通过LocalDateTime来实现
private static final DateTimeFormatter DATE_TIME_FORMATTER =DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final ZoneId ZONE_ID = ZoneId.systemDefault();
private BeanUtil(){}
/**
* 实现对象反射属性赋值的处理操作
* @param object 要进行实例化处理的对象实例(不允许为空)
* @param value 满足与指定的格式要求的data
*/
public static void setObjectValue(Object object,String value){
String[] all = IFieldContentSplit.splitAll(value);
for (String content : all) {
try{
String[] fields = IFieldContentSplit.splitField(content);//获取属性名称和内容
Field field = object.getClass().getDeclaredField(fields[0]);
Method method = object.getClass().getMethod("set" + StringUtil.initCap(fields[0]), field.getType());
method.invoke(object,convert(fields[1],field));
}catch (Exception e){
e.printStackTrace();
}
}
}
/**
* 根据属性的类型将字符串进行数据类型转换
* @param value 包含数据内容的字符串
* @param field 属性对应的类型
* @return 返回转换后的具体数据类型
*/
public static Object convert(String value, Field field) {
String fieldTypeName = field.getType().getName(); // 获取当前的属性类型名称
try {
switch (fieldTypeName) { // 通过switch进行处理
case "java.lang.String":
return value; // 不需要做出转换
case "int": // int数据类型
return Integer.parseInt(value); // 转为整型返回
case "java.lang.Integer": // Integer包装类型
return Integer.parseInt(value); // 转为整型返回
case "double": // double数据类型
return Double.parseDouble(value); // 转为double类型返回
case "java.lang.Double": // Double包装类型
return Double.parseDouble(value); // 转为double类型返回
case "java.math.BigDecimal": // 大浮点数包装类型
return new BigDecimal(value); // 返回BigDecimal类型对象
case "long": // long数据类型
return Long.parseLong(value); // 转为Long类型
case "java.lang.Long": // Long包装类型
return Long.parseLong(value); // 转为Long类型
case "java.util.Date": { // 日期时间
LocalDateTime localDateTime = LocalDateTime.parse(value,
DATE_TIME_FORMATTER); // 日期时间数据解析
Instant instant = localDateTime.atZone(ZONE_ID).toInstant();
return Date.from(instant); // 类型转换
}
}
} catch (Exception e) {}
return null;
}
}
8.2 对象的级联关系
如果考虑到实际的设计结构来讲,是有可能存在级联关系的。所谓级联关系就是一级一级从属于被从属之间的关系,各级之间存在着引用和被引用的依赖关系或者关联或者组合关系
/**
* 实现对象反射属性赋值的处理操作
* @param object 要进行实例化处理的对象实例(不允许为空)
* @param value 满足与指定的格式要求的data
*/
public static void setObjectValue(Object object,String value){
String[] all = IFieldContentSplit.splitAll(value);
for (String content : all) {
try{
String[] fields = IFieldContentSplit.splitField(content);//获取属性名称和内容
if(fields[0].contains(".")){//如果存在级联关系
String[] cascade = fields[0].split("\\.");//获取级联信息
instanceCascadeObject(object,cascade);//对象级联的处理
}else{
Field field = object.getClass().getDeclaredField(fields[0]);
Method method = object.getClass().getMethod("set" + StringUtil.initCap(fields[0]), field.getType());
method.invoke(object,convert(fields[1],field));
}
}catch (Exception e){
e.printStackTrace();
}
}
}
/**
* 级联对象实例化操作
* @param object 当前的操作的发起对象实例
* @param cascade 级联的配置关系数组
* @return 最后一个级联对象实例(当前操作属性的对应实例)
* @throws Exception 反射调用中可能产生的错误
*/
private static Object instanceCascadeObject(Object object, String cascade[]) throws Exception { // 实例化级联对象
for (int x = 0; x < cascade.length - 1; x++) { // 进行级联对象实例化
// 1、由于级联对象有可能被重复使用多次,所以必须防止有可能出现的重复对象实例化问题
Method getMethod = object.getClass().getMethod("get" + StringUtil.initCap(cascade[x]));
Object instance = getMethod.invoke(object); // 获取当前的实例化对象
if (instance == null) { // 2、当前对象还没有实例化
// 3、需要反射调用当前对象的setter方法进行对象的手工实例化,找到属性的成员类型
Field field = object.getClass().getDeclaredField(cascade[x]);
// 4、根据返回的对象类型来实现对象的实例化处理操作
Object obj = field.getType().getConstructor().newInstance();
// 5、调用类中的setter方法将对象的内容设置到类中
Method setMethod = object.getClass().getMethod("set" + StringUtil.initCap(cascade[x]), field.getType());
setMethod.invoke(object, obj); // 实现setter的调用
// 6、修改当前的object类型
object = obj; // 需要更换级联的下一步操作
} else { // 如果instance不为空
object = instance;
}
}
return object; // 返回最后一个级联对象
}
级联对象实例化完成之后,就需要进行级联对象属性的设置,由于在级联对象设置的时候需要通过具体的对象实例进行属性的设置,所以在每一次进行级联属性设置的时候,都需要确定当前要操作的对象实例是哪一个
/**
* 实现对象反射属性赋值的处理操作
* @param object 要进行实例化处理的对象实例(不允许为空)
* @param value 满足与指定的格式要求的data
*/
public static void setObjectValue(Object object,String value){
String[] all = IFieldContentSplit.splitAll(value);
for (String content : all) {
try{
String[] fields = IFieldContentSplit.splitField(content);//获取属性名称和内容
if(fields[0].contains(".")){//如果存在级联关系
String[] cascade = fields[0].split("\\.");//获取级联信息
Object o = instanceCascadeObject(object, cascade);//对象级联的处理
setFieldValue(o,cascade[cascade.length-1],fields[1]);
}else{
Field field = object.getClass().getDeclaredField(fields[0]);
Method method = object.getClass().getMethod("set" + StringUtil.initCap(fields[0]), field.getType());
method.invoke(object,convert(fields[1],field));
}
}catch (Exception e){
e.printStackTrace();
}
}
}
/**
* 实现指定对象实例属性内容的设置
* @param object 当前进行设置的实例化对象
* @param fieldName 属性的名称
* @param fieldValue 属性设置的内容
* @throws Exception 所有产生的异常全部抛出
*/
private static void setFieldValue(Object object, String fieldName,
String fieldValue) throws Exception {
Field field = object.getClass().getDeclaredField(fieldName); // 找到属性
// 找到属性对应的setter方法,在获取setter方法的时候必须明确的设置方法的参数类型(Class)
Method method = object.getClass().getMethod("set" +
StringUtil.initCap(fieldName), field.getType());
method.invoke(object, convert(fieldValue, field)); // 通过反射实现setter方法的调用
}