Java反射机制


1 获取Class对象

  • 通过Object类支持:对象.getClass()方法
    • 缺点:必须要先有实例化对象,存在资源浪费。
  • 类.class 直接访问
    • 需要先导入类所在的开发包。
  • Class.forName(String name)方法

2 通过Class对象实例化对象

  • 在JDK1.9之前,通过class对象.newInstance()方法。
  • 在JDK1.9之后,通过class对象.getDeclaredConstructor().newInstance()获得。

范例:

public class Demo1 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<?> c = Class.forName("com.kkb.xzk.Person");
        Object obj = c.getDeclaredConstructor().newInstance();
        System.out.println(obj);
    }
}

class Person{
    public Person() {
    }
    public String toString(){
        return "我是一个脱离了低级趣味的人";
    }
}

通过这种方法实例化,本质上还是调用了类中的无参构造方法:new 类()。这里等于隐含了new关键字,而直接通过字符串实例化了一个对象。

JDK1.9之前的方法(class对象.newInstance)只能调用类的无参构造方法,很多开发者认为其描述的不准确,于是将其变换了形式。

3 反射与工厂设计模式

首先回顾一下工厂设计模式。如果没有工厂设计模式会怎么样?观察下面一段代码。

public class JavaAPIDemo {
	public static void main(String[] args) {
		IMessage msg  = new NetMessage();
		msg.send();
	}
}

interface IMessage{
	public void send(); //消息发送
}

class NetMessage implements IMessage{
	public void send() {
		System.out.println("网络消息发送");
	};
}

以上程序最大的一个问题是耦合。接口IMessage不可能只有一个子类,而主类里如果后面想实例化IMessage的其他子类则必须修改主类代码才能实现。因此考虑工厂模式,代码如下:


public class JavaAPIDemo {
	public static void main(String[] args) {
		IMessage msg  = Factory.getInstance("netmessage");
		msg.send();
	}
}

interface IMessage{
	public void send(); //消息发送
}

class NetMessage implements IMessage{
	public void send() {
		System.out.println("网络消息发送");
	};
}

class Factory{
	private Factory() {} // 私有化构造方法
	public static IMessage getInstance(String name) {
		if("netmessage".equals(name)) {
			return new NetMessage();
		}
		return null;
	}
}

利用工厂模式,只要主类给一个字符串名字,工厂就能返回相应的子类实例化对象。

此种工厂设计模式属于静态工厂设计模式,针对于每一个子类都需要一个if条件语句判断。一旦增加一个子类,则必须修改工厂类的代码以识别这个子类(增加else if判断)。这种情况的发生主要是因为new关键字。该关键字的使用要求必须知道具体类名称(不是字符串,而是类本身)。

采用以下的代码即可解决上述问题:
改进后的工厂模式


public class JavaAPIDemo {
	public static void main(String[] args) {
		IMessage msg  = Factory.getInstance("CloudMessage");
		msg.send();
		msg  = Factory.getInstance("NetMessage");
		msg.send();
	}
}

interface IMessage{
	public void send(); //消息发送
}

class NetMessage implements IMessage{
	public void send() {
		System.out.println("网络消息发送");
	};
}

class CloudMessage implements IMessage{
	@Override
	public void send() {
		System.out.println("【云消息】发送");
	}
}

class Factory{
	private Factory() {} // 私有化构造方法
	public static IMessage getInstance(String name) {
		IMessage msg = null;
		try {
			msg = (IMessage) Class.forName(name).getDeclaredConstructor().newInstance();
		}catch (Exception e) {
			e.printStackTrace();
		}
		return msg;
	}
}

上述改动最大的优势在于,增加子类之后不需要再改动工厂类。
上述代码存在一个新的问题是,它只能为IMessage接口服务,最好的做法是能为所有接口服务。此时需要利用泛型,将泛型定义到方法的参数里。


public class JavaAPIDemo {
	public static void main(String[] args) {
		IMessage msg  = Factory.getInstance("CloudMessage", IMessage.class);
		msg.send();
		IService house  = Factory.getInstance("HouseService", IService.class);
		house.service();
	}
}

interface IMessage{
	public void send(); //消息发送
}

class NetMessage implements IMessage{
	public void send() {
		System.out.println("网络消息发送");
	};
}

class CloudMessage implements IMessage{
	@Override
	public void send() {
		System.out.println("【云消息】发送");
	}
}

interface IService{
	void service();
}

class HouseService implements IService{
	@Override
	public void service() {
		System.out.println("提供住宿服务");
	}
}


class Factory{
	private Factory() {} // 私有化构造方法
	@SuppressWarnings("unchecked")
	public static <T> T getInstance(String name, Class<T> clazz) {
		T msg = null;
		try {
			msg = (T) Class.forName(name).getDeclaredConstructor().newInstance();
		}catch (Exception e) {
			e.printStackTrace();
		}
		return msg;
	}
}

注意:实际测试的时候发现,不加Class<T> clazz参数也能够顺利执行,这是为什么?
可能是因为当在主方法里声明IMessage … = … 的时候,根据声明类型自动判断方法的返回值类型,从而确定了T是什么。

此时的工厂模式才是一种高可用的工厂设计模式。

4 反射与单例设计模式

传统的单例设计模式存在一个缺陷,就是面对多线程操作的时候会出现多次实例化对象的情况。

范例:懒汉式单例模式:

public class JavaAPIDemo {
	public static void main(String[] args) {
		new Thread(() -> {
			Singleton a = Singleton.getInstance();
		}, "线程A").start();
		new Thread(() -> {
			Singleton a = Singleton.getInstance();
		}, "线程B").start();
		new Thread(() -> {
			Singleton a = Singleton.getInstance();
		}, "线程C").start();
	}
}

class Singleton {
	private static Singleton instance = null;
	private Singleton() {
		System.out.println("***** 【" + Thread.currentThread().getName() + "】 实例化了对象 *****");
	}
	public static Singleton getInstance() {
		if (instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
}

输出结果:

***** 【线程A】 实例化了对象 *****
***** 【线程C】 实例化了对象 *****
***** 【线程B】 实例化了对象 *****

出现上述情况的原因很好理解,当多个线程进入到getInstance方法时,在instance还未实例化之前,很多线程已经进入到if条件中,这样就造成了instance被多次实例化。

我一上来的解决思路是,把getInstance方法使用synchronized关键字修饰。但这种方法有一个弊端,严重降低了性能,导致任意时刻只能由一个线程获取单例。实际上只是在单例构造时需要控制同步问题

所以更好的做法是,对new Singleton这一句进行同步控制。

  • 由于instance是static的,所以不能用synchronized(this)控制,需要变为synchronized(Singleton.class)控制。
  • 此外,加锁之后,里面仍然需要增加判断if(instance == null)。因为可能有很多线程进入了外层的If判断,在synchronized之前等待,当一个线程实例化instance之后,其他线程就不应该进入同步块继续实例化了,所以在同步块内还需要增加判断。
  • 最后,考虑到instance一旦实例化了就应该和主内存进行同步,不应该保存副本,所以给Instance增加volatile关键字修饰。

最后的实现效果:

public class JavaAPIDemo {
	public static void main(String[] args) {
		new Thread(() -> {
			Singleton a = Singleton.getInstance();
		}, "线程A").start();
		new Thread(() -> {
			Singleton a = Singleton.getInstance();
		}, "线程B").start();
		new Thread(() -> {
			Singleton a = Singleton.getInstance();
		}, "线程C").start();
	}
}

class Singleton {
	private static volatile Singleton instance = null;
	private Singleton() {
		System.out.println("***** 【" + Thread.currentThread().getName() + "】 实例化了对象 *****");
	}
	public static Singleton getInstance() {
		if (instance == null) {
			synchronized(Singleton.class) {
				if (instance == null) {
					instance = new Singleton();
				}
			}
		}
		
		return instance;
	}
}

面试题:请写出单例模式

  • 【100%】直接编写一个饿汉式单例设计模式,并且将构造方法私有化。
  • 【120%】Java中哪里出现过单例?Runtime类,Pattern类,Spring框架。
  • 【200%】懒汉式单例设计模式的问题?(上述解决代码)

5 反射获取类结构信息

反射不仅仅是构造对象,更多的是可以通过反射获取到类结构的信息。类结构包含:

  • 父类(父接口)
  • 属性
  • 方法(构造方法、普通方法)

5.1 获取类的基本信息

一个类的基本信息主要包括:类所在的包名称、父类的定义、父接口的定义。

  • 获取包名称:
    • pubic Package getPackage();
    • package.getName();
  • 获取继承父类:public Class<? super T> getSuperClass();
  • 获取实现父接口:public Class<? super T>[] getInterfaces();

范例:
基本类和接口的定义:
在这里插入图片描述
测试类:

public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<?> c = Person2.class;
        // 获取类所在的包名
        Package p = c.getPackage();
        System.out.println(p.getName());
        // 获取类的继承父类
        System.out.println(c.getSuperclass().getName());
        // 获取类的实现父接口
        Class<?>[] clazzs = c.getInterfaces();
        for(Class<?> clazz: clazzs){
            System.out.println(clazz.getName());
        }
    }
}

class Person2 extends AbstractBase implements IChannelService, IMessageService {
    @Override
    public void send() {
        if(this.connect()){
            System.out.println("【信息发送】aaa");
        }
    }

    @Override
    public boolean connect() {
        return true;
    }
}

输出结果:

com.kkb.xzk.test
com.kkb.xzk.abs.AbstractBase
com.kkb.xzk.service.IChannelService
com.kkb.xzk.service.IMessageService

5.2 反射调用构造方法

  • 获取所有的构造器:
    public Constructor<?>[] getDeclaredConstructors() throws SecurityException
  • 获取指定的构造方法:根据参数类型获取构造方法
    public Constructor<T> getDeclaredConstructor​(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException

  • 获取所有的构造方法:
    public Constructor<?>[] getConstructors() throws SecurityException
  • 获取指定的构造方法:
    public Constructor<T> getConstructor​(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException

上面两种好像没什么区别?

获得Constructor<?>之后,可以通过它实例化类对象,类似于之前的newInstance()方法:

  • public T newInstance​(Object... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException

范例:得到所有的Constructor

public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<?> c = Person2.class;
        Constructor<?>[] cons = c.getDeclaredConstructors();
        for(Constructor<?> con: cons){
            System.out.println(con);
        }
    }
}

class Person2 extends AbstractBase implements IChannelService, IMessageService {
    private int age;
    private String name;
    public Person2() {
    }

    public Person2(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

输出结果:

public com.kkb.xzk.test.Person2()
public com.kkb.xzk.test.Person2(java.lang.String,int)

范例:得到指定的Constructor

public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<?> c = Person2.class;
        Constructor<?> con = c.getDeclaredConstructor(String.class, int.class);
        Object obj = con.newInstance("王二",18);
        System.out.println(obj);
    }
}

class Person2 extends AbstractBase implements IChannelService, IMessageService {
    private int age;
    private String name;
    public Person2() {
    }

    public Person2(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "姓名:" + name + "、年龄:" + age;
    }
}

输出结果:

姓名:王二、年龄:18

虽然支持得到指定参数的构造方法,但是通常情况下还是建议在反射会用到的类中定义无参构造方法。

5.3 反射调用方法

  • 反射取得所有方法(包括父类中的所有方法)
    public Methos[] getMethods() throws SecurityException
  • 反射取得指定方法
    public Method getMethod​(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
  • 反射取得本类定义的所有方法
    public Methos[] getDeclaredMethods() throws SecurityException
  • 反射取得本类定义的指定方法
    public Method getMethod​(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException

范例:

public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<?> c = Person2.class;
        Method[] mets = c.getMethods();
        for(Method met: mets){
            System.out.println(met);
        }
    }
}

class Person2 extends AbstractBase implements IChannelService, IMessageService {
    private int age;
    private String name;
    public Person2() {
    }

    public Person2(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "姓名:" + name + "、年龄:" + age;
    }

    @Override
    public void send() {
        if(this.connect()){
            System.out.println("【信息发送】aaa");
        }
    }

    @Override
    public boolean connect() {
        return true;
    }
}

输出结果:注意所有的父类父接口的方法都被获得了、

public java.lang.String com.kkb.xzk.test.Person2.toString() 【覆写的方法算作子类的方法】
public boolean com.kkb.xzk.test.Person2.connect()  【父接口的方法】
public void com.kkb.xzk.test.Person2.send()  【父接口的方法】
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

范例2:修改为getDeclaredMethods之后的输出结果:

public java.lang.String com.kkb.xzk.test.Person2.toString()
public boolean com.kkb.xzk.test.Person2.connect()
public void com.kkb.xzk.test.Person2.send()

上面打印到的结果都是由Method对象的toString得到的。实际开发中也可以根据需求得到相应的方法属性然后自由拼接。方法的相关属性获得(在Method类及其父类中定义):

  • public String getName() 得到名字

  • public int getModifiers() 得到修饰符(public、static、abstract等)

    注意:得到的修饰符是用int表示的。要想转化成可读的字符串,可以通过Modifier类的静态方法toString(int mod)实现。Modifier将修饰符定义为不同的整型,比如public=1,abstract=5,final=10。这样对于一个整数6就可以自动解析为public abstract了。

  • public Class<?> getReturnType() 得到返回值

  • public Class<?>[] getParameterTypes 得到参数的类型

  • public Class<?>[] getExceptionTypes() 得到异常的类型

范例:

public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<?> c = Person2.class;
        Method[] mets = c.getDeclaredMethods();
        for(Method met: mets){
            System.out.print(Modifier.toString(met.getModifiers()) + " ");
            System.out.print(met.getReturnType().getName() + " ");
            System.out.print(met.getName() + "(");
            Class<?>[] paramTypes = met.getParameterTypes();
            for(int x = 0; x < paramTypes.length; x++){
                System.out.print(paramTypes[x].getName());
                if(x < paramTypes.length - 1){
                    System.out.print(", ");
                }
            }
            System.out.print(") ");
            Class<?>[] exceptionTypes = met.getExceptionTypes();
            if(exceptionTypes.length > 0){
                System.out.print("throws ");
                for(int x = 0; x < exceptionTypes.length; x++){
                    System.out.print(exceptionTypes[x]);
                    if(x < exceptionTypes.length - 1){
                        System.out.print(", ");
                    }
                }
            }
            System.out.println();

        }
    }
}

class Person2 extends AbstractBase implements IChannelService, IMessageService {
    private int age;
    private String name;
    public Person2() {
    }

    public Person2(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "姓名:" + name + "、年龄:" + age;
    }

    @Override
    public void send() {
        if(this.connect()){
            System.out.println("【信息发送】aaa");
        }
    }

    @Override
    public boolean connect() {
        return true;
    }
}

上面的方法仅了解即可,知道有这么个东西可以得到方法的结构。下面的方法比较重要:通过反射来调用对象里的方法。

  • public Object invoke​(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException

这里的返回值Object即为方法的返回值。invoke的第一个参数表示调用哪个对象的方法,后边的可变长度参数表示方法的实参。

范例:在不导包的情况下,通过反射调用Person对象的setter和getter方法。(Person类的定义在上段代码中)

public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<?> c = Class.forName("com.kkb.xzk.test.Person2");
        // 通过反射调用对象中的方法
        Object obj = c.getDeclaredConstructor().newInstance(); //调用无参构造方法实例化对象
        c.getMethod("setName", String.class).invoke(obj, "李四");
        c.getMethod("setAge", int.class).invoke(obj, 28);
        System.out.println("姓名:" + c.getMethod("getName").invoke(obj));
        System.out.println("年龄:" + c.getMethod("getAge").invoke(obj));
    }
}

输出结果:

姓名:李四
年龄:28

以上的程序没有明确的类对象的产生(产生的实际是Object对象),一切都是依靠反射机制处理的。这种方式可以解决高耦合的问题。

5.4 反射调用成员属性

  • public Field getDeclaredField​(String name) throws NoSuchFieldException, SecurityException 获得子类中的指定成员
  • public Field[] getDeclaredFields() throws SecurityException 获得子类中的全部成员
  • public Field[] getFields() throws SecurityException 获得本类和父类中的全部public成员
  • public Field getField​(String name) throws NoSuchFieldException, SecurityException 获得子类和父类中指定public成员

上述对属性的操作不是最重要的。最重要的是Field类里的如下三个方法:

  • 设置属性内容:
    public void set​(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException
  • 获得属性内容:
    public Object get​(Object obj) throws IllegalArgumentException, IllegalAccessException

上述方法都需要传入实例化对象参数。

范例:直接设置类的私有属性:报错

public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        Class<?> c = Class.forName("com.kkb.xzk.test.Person2"); //可以在不导包的情况下获得Class对象
        Object obj = c.getDeclaredConstructor().newInstance(); //调用无参构造函数实例化对象
        Field name = c.getDeclaredField("name");   // 获得指定的属性
        Field age = c.getDeclaredField("age");     // 获得指定属性
        name.set(obj, "张三");  // 设置类的私有属性
        age.set(obj, 18);

        System.out.println("姓名:" + c.getDeclaredMethod("getName").invoke(obj));
        System.out.println("年龄:" + c.getDeclaredMethod("getAge").invoke(obj));
    }
}

程序结果:

Exception in thread "main" java.lang.IllegalAccessException: class com.kkb.xzk.test.Demo2 cannot access a member of class com.kkb.xzk.test.Person2 with modifiers "private"
	at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361)

既然在操作私有属性时除了问题,于是有了第三个重要方法:解除封装

  • 解除封装:
    public void setAccessible(boolean flag)

注意这个方法定义在AccessibleObject里。因此可以预见,其所有的子类,包括Constructor,Method等等,都可以设置封装性。

范例:将封装的属性解除封装然后设置其值。

public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        Class<?> c = Class.forName("com.kkb.xzk.test.Person2"); //可以在不导包的情况下获得Class对象
        Object obj = c.getDeclaredConstructor().newInstance(); //调用无参构造函数实例化对象
        Field name = c.getDeclaredField("name");   // 获得指定的属性
        Field age = c.getDeclaredField("age");     // 获得指定属性
        name.setAccessible(true);
        age.setAccessible(true);
        name.set(obj, "张三");  // 设置类的私有属性
        age.set(obj, 18);

        System.out.println("姓名:" + c.getDeclaredMethod("getName").invoke(obj));
        System.out.println("年龄:" + c.getDeclaredMethod("getAge").invoke(obj));
    }
}

运行结果:

姓名:张三
年龄:18

上述设置成员属性的方法并不常用,还是应该通过setter来进行设置。在开发中,Field类最重要的一个方法是,获得成员属性的类型:

  • public Class<?> getType()

范例:

public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        Class<?> c = Class.forName("com.kkb.xzk.test.Person2"); //可以在不导包的情况下获得Class对象
        Object obj = c.getDeclaredConstructor().newInstance(); //调用无参构造函数实例化对象
        Field name = c.getDeclaredField("name");   // 获得指定的属性
        System.out.println(name.getType().getName());
        System.out.println(name.getType().getSimpleName());
    }
}

运行结果:

java.lang.String
String

在以后开发进行反射处理时,往往会利用Field和Method进行setter方法的调用。

5.5 相关类结构

在这里插入图片描述

6 Unsafe工具类(了解)

在Java里面提供有一个Unsafe类(不安全的操作),这个类的特点是可以利用反射来获取对象,直接使用底层的C++来代替JVM执行,绕过JVM的对象管理机制(内存管理机制和垃圾回收处理)。

Unsafe类的构造方法是私有的,在里面有一个Unsafe theUnsafe的私有静态常量。但是在这个类中并没有一个公共的静态方法来获取此对象。因此该对象只能通过反射来获得。(Field类)。

范例:获得Unsafe对象

public class Demo3 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
        Class<?> c = Unsafe.class;
        Field f = c.getDeclaredField("theUnsafe"); //得到属性
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null); // 静态对象不需要传入实例化对象参数
    }
}

通过Unsafe可以快速的实现单例设计模式。在类中不需要定义私有静态变量。
范例:Unsafe实现的单例模式

public class Demo3 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
        Class<?> c = Unsafe.class;
        Field f = c.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null); // 静态对象不需要传入实例化对象参数
        Singleton singleton = (Singleton) unsafe.allocateInstance(Singleton.class); // 绕过JVM
    }
}

class Singleton{ //单例设计模式,没有私有静态变量和getInstance方法。
    private Singleton(){
        System.out.println("**** 调用Singleton构造方法 *****");
    }
}

注意:上述程序不会输出“**** 调用Singleton构造方法 *****”

Unsafe因为绕过了JVM,因此除非必须,一般不使用。Unsafe可以在单例模式笔试面试的时候做补充。

7 反射与简单Java类

传统的简单Java类开发会面临一些困难。假如一个类中有50个属性需要赋值,则程序中会出现大量的setter方法。当很多简单java类都需要赋值的时候,setter会大量重复。

7.1 属性自动设置解决方案

  • 需要通过反射进行指定类对象的实例化
  • 通过属性名称fieldName得到Field对象进行内容的设置
    • Field对象: 获得属性的类型(getType()方法得到Class对象),方法的名称(set+fieldName首字母大写),从而可以得到Method对象
    • Method对象:invoke设置内容。

7.2 单级属性赋值

范例1:单极属性赋值(单级属性是没有引用关联的属性,即属性都是基本类型)
目标:通过属性名:值|属性名:值...这样的字符串,构造一个实例化对象并且按照字符串的内容对其属性进行赋值。

注意:本程序只能实现String类型属性的设置,对于非String类型的数据,将不进行操作。

  • Person类定义
class Person3 {
    private int age;
    private String name;
    public Person3() {
    }

    public Person3(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "姓名:" + name + "、年龄:" + age;
    }
}
  • 定义ClassInstanceFactory
class ClassInstanceFactory{
    private ClassInstanceFactory(){
    }

    public static <T> T getInstance(Class<?> clazz, String value) throws NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //先获得实例化对象
        T instance = (T) clazz.getDeclaredConstructor().newInstance();
        BeanUtil.setValue(instance, value);
        return instance;
    }
}
  • 定义StringUtil:实现字符串首字母大写功能
class StringUtil{
    private StringUtil(){
    }

    public static String title(String s){
        if(s == null || "".equals(s)){
            return s;
        }
        if(s.length() == 1){
            return s.toUpperCase();
        }
        return s.substring(0, 1).toUpperCase() + s.substring(1);
    }
}
  • 定义BeanUtil:实现对实例化对象的赋值操作
class BeanUtil{
    private BeanUtil(){}
    public static void setValue(Object obj, String value){
        String[] results = value.split("\\|");
        for(String result : results){
            String fieldName = result.split(":")[0];
            String fieldValue = result.split(":")[1];
            String methodName = "set" + StringUtil.title(fieldName);
            Method method = null;
            try {
                method = obj.getClass().getDeclaredMethod(methodName, obj.getClass().getDeclaredField(fieldName).getType());
                method.invoke(obj, fieldValue);
            } catch (Exception e) { //即使某一个属性设置错误,也不要影响其他属性的设置
            }
        }
    }
}
  • 测试类
public class Demo4 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        Person3 p = ClassInstanceFactory.getInstance(Class.forName("com.kkb.xzk.test.Person3"), "name:张三|age:18");
        System.out.println(p);
    }
}

输出结果:

姓名:张三、年龄:0

注意:年龄18没有设置上,因为age是int类型,在调用method.invoke时出现异常从而没有设置这个属性。

7.3 设置多种数据类型

对于简单Java类,常见的单级属性类型包括:int, double, long, String, Date等。我们考虑对这些类型的属性进行赋值。主要修改上述的BeanUtil类。

  • 修改之后的BeanUtil类:
class BeanUtil {
    private BeanUtil() {
    }

    public static void setValue(Object obj, String value) {
        String[] results = value.split("\\|");
        for (String result : results) {
            String fieldName = result.split(":")[0];
            String fieldValue = result.split(":")[1];
            String methodName = "set" + StringUtil.title(fieldName);
            Method method = null;
            try {
                String fieldType = obj.getClass().getDeclaredField(fieldName).getType().getName();
                method = obj.getClass().getDeclaredMethod(methodName, obj.getClass().getDeclaredField(fieldName).getType());
                method.invoke(obj, convertValue(fieldValue, fieldType));
            } catch (Exception e) { //即使某一个属性设置错误,也不要影响其他属性的设置
            }
        }
    }

    /**
     * 将字符串的value转化为指定类型的value
     *
     * @param value 字符串的value值
     * @param type  要转换的属性
     * @return 转换后的属性对象
     * @author Han Ding
     * @date 2021/7/2
     **/
    public static Object convertValue(String value, String type) {
        if (value == null || type == null || "".equals(value) || "".equals(type)) {
            return null;
        }

        if ("int".equalsIgnoreCase(type) || "java.lang.int".equalsIgnoreCase(type)) { // int类型数据
            return Integer.parseInt(value);
        } else if ("double".equalsIgnoreCase(type) || "java.lang.double".equalsIgnoreCase(type)) {
            return Double.parseDouble(value);
        } else if ("long".equalsIgnoreCase(type) || "java.lang.long".equalsIgnoreCase(type)) {
            return Long.parseLong(value);
        } else if ("Date".equalsIgnoreCase(type) || "java.util.Date".equalsIgnoreCase(type)) {
            SimpleDateFormat sdf = null;
            try {
                if (value.matches("\\d{4}-\\d{1,2}-\\d{1,2}")) { //日期格式:yyyy-MM-dd
                    sdf = new SimpleDateFormat("yyyy-MM-dd");
                    return sdf.parse(value);
                } else if (value.matches("\\d{4}-\\d{1,2}-\\d{1,2} \\d{2}:\\d{2}:\\d{2}")) { //日期时间格式:yyyy-MM-dd HH:mm:ss
                    sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    return sdf.parse(value);
                } else{
                    return new Date();
                }
            } catch (ParseException e) {
                return new Date(); //报错的话就默认返回当前日期
            }
        }else{ //字符串类型
            return value;
        }
    }
}
  • Person类修改:增加一些属性以更好地测试
class Person3 {
    private int age;
    private String name;
    private double salary;
    private Date hireDate;

    public Person3() {
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public Date getHireDate() {
        return hireDate;
    }

    public void setHireDate(Date hireDate) {
        this.hireDate = hireDate;
    }

    @Override
    public String toString() {
        return "Person3{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", salary=" + salary +
                ", hireDate=" + new SimpleDateFormat("yyyy-MM-dd").format(hireDate) +
                '}';
    }
}
  • 测试类:
public class Demo4 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        String personInfo = "name:张三|age:19|hireDate:2013-9-15|salary:8695.33";
        Person3 p = ClassInstanceFactory.getInstance(Class.forName("com.kkb.xzk.test.Person3"), personInfo);
        System.out.println(p);
    }
}

输出结果:

Person3{age=19, name='张三', salary=8695.33, hireDate=2013-09-15}

7.4 级联对象实例化

假设有如下的结构:

  • 雇员:姓名,id,薪水,部门
  • 部门:部门名称,公司
  • 公司:公司名称,公司地址

这个时候如果要对雇员对象设置属性,则必须设置其部门属性。设置的时候可以用.表示级联关系。例如:

department.dname:开发组|
department.company.name:腾讯|
department.company.location:深圳

这样就完成了雇员的部门属性的设置。

  • 进行级联对象实例化的第一步是判断是否是级联实例化,判断依据即属性名称中是否含有.
    fieldName.contains(".")

注意:contains函数的参数是一个CharSequence,因此直接写".“即可,不要按照正则表达式的写法(”\."),这样识别不到。

  • 假设需要设置级联对象department。它的类型是Department。实例化字符串为department.company.name:腾讯。我们需要遵循以下步骤逐级设置。
    1. 设置当前对象为雇员对象。找到当前雇员对象的department属性。如果该属性不存在,则实例化。
    2. 然后设置当前对象为department,继续向后查找(相当于为department对象设置company属性)
    3. 到倒数第二级的时候停止。此时的当前对象就是要设置value的对象:即当前对象是employee.getDepartment().getCompany(),要执行setName(“腾讯”)这一操作。

相关Bean类:
Employee类:

public class Employee {
    private long id;
    private String name;
    private double salary;
    private Date hireDate;
    private Department department;

    public Employee() {
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public Date getHireDate() {
        return hireDate;
    }

    public void setHireDate(Date hireDate) {
        this.hireDate = hireDate;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", salary=" + salary +
                ", hireDate=" + hireDate +
                ", department=" + department +
                '}';
    }
}

Department类:

public class Department {
    private String dname; //部门名称
    private Company company; //部门属于哪个公司

    public Department() {
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    public Company getCompany() {
        return company;
    }

    public void setCompany(Company company) {
        this.company = company;
    }

    @Override
    public String toString() {
        return "【部门名称:" + dname + "、来自公司" + company + "】";
    }

}

Company类:

public class Company {
    private String name;
    private String location;

    public Company(String name, String location) {
        this.name = name;
        this.location = location;
    }

    public Company() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    @Override
    public String toString() {
        return "【公司名:" + name + "、公司地址:" + location + "】";
    }
}

BeanUtil类修改(修改setValue):

public class BeanUtil {
    private BeanUtil() {
    }

    public static void setValue(Object obj, String value) {
        String[] results = value.split("\\|");
        for (String result : results) {
            try {
                String fieldName = result.split(":")[0];
                String fieldValue = result.split(":")[1];
                if (!fieldName.contains(".")) { //常规单级属性的设置
                    String methodName = "set" + StringUtil.title(fieldName);
                    Method method = null;
                    String fieldType = obj.getClass().getDeclaredField(fieldName).getType().getName();
                    method = obj.getClass().getDeclaredMethod(methodName, obj.getClass().getDeclaredField(fieldName).getType());
                    method.invoke(obj, convertValue(fieldValue, fieldType));
                }else{ // 级联属性设置
                    String[] objectNames = fieldName.split("\\.");
                    // department.company.name
                    // department.getCompany().setName()
                    Object currentObject = obj;
                    Class<?> currentClass = obj.getClass();
                    for(int x = 0; x < objectNames.length - 1; x++){ // 把除了最后一级之前的对象实例化。因为最后一级就是属性本身了
                        Field f = currentClass.getDeclaredField(objectNames[x]);
                        Method getMethod = currentClass.getDeclaredMethod("get" + StringUtil.title(objectNames[x]));
                        Object temp = getMethod.invoke(currentObject);
                        if(temp == null){ //说明当前属性对象还未实例化
                            temp = f.getType().getDeclaredConstructor().newInstance(); //实例化属性对象
                            Method setMethod = currentClass.getDeclaredMethod("set" + StringUtil.title(objectNames[x]), f.getType());
                            setMethod.invoke(currentObject, temp);
                        }
                        currentObject = temp;
                        currentClass = currentObject.getClass();
                    }
                    //设置最后一级对象的属性
                    String theFieldName = objectNames[objectNames.length - 1];
                    Field f = currentClass.getDeclaredField(theFieldName);
                    Method setMethod = currentClass.getDeclaredMethod("set" + StringUtil.title(theFieldName), f.getType());
                    String fieldType = f.getType().getName();
                    setMethod.invoke(currentObject, convertValue(fieldValue, fieldType));
                }
            } catch (Exception e) { //即使某一个属性设置错误,也不要影响其他属性的设置
            }
        }
    }

    /**
     * 将字符串的value转化为指定类型的value
     *
     * @param value 字符串的value值
     * @param type  要转换的属性
     * @return 转换后的属性对象
     * @author Han Ding
     * @date 2021/7/2
     **/
    public static Object convertValue(String value, String type) {
        if (value == null || type == null || "".equals(value) || "".equals(type)) {
            return null;
        }

        if ("int".equalsIgnoreCase(type) || "java.lang.int".equalsIgnoreCase(type)) { // int类型数据
            return Integer.parseInt(value);
        } else if ("double".equalsIgnoreCase(type) || "java.lang.double".equalsIgnoreCase(type)) {
            return Double.parseDouble(value);
        } else if ("long".equalsIgnoreCase(type) || "java.lang.long".equalsIgnoreCase(type)) {
            return Long.parseLong(value);
        } else if ("Date".equalsIgnoreCase(type) || "java.util.Date".equalsIgnoreCase(type)) {
            SimpleDateFormat sdf = null;
            try {
                if (value.matches("\\d{4}-\\d{1,2}-\\d{1,2}")) { //日期格式:yyyy-MM-dd
                    sdf = new SimpleDateFormat("yyyy-MM-dd");
                    return sdf.parse(value);
                } else if (value.matches("\\d{4}-\\d{1,2}-\\d{1,2} \\d{2}:\\d{2}:\\d{2}")) { //日期时间格式:yyyy-MM-dd HH:mm:ss
                    sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    return sdf.parse(value);
                } else {
                    return new Date();
                }
            } catch (ParseException e) {
                return new Date(); //报错的话就默认返回当前日期
            }
        } else { //字符串类型
            return value;
        }
    }
}

测试类:

public class Demo5 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        String employeeInfo = "id:100001|name:张三|age:19|hireDate:2013-9-15|salary:8695.33|" +
                "department.dname:财务部|department.company.name:腾讯|" +
                "department.company.location:深圳";
        Employee p = ClassInstanceFactory.getInstance(Class.forName("com.kkb.xzk.bean.Employee"), employeeInfo);
        System.out.println(p);
    }
}

输出结果:

Employee{id=100001, name='张三', salary=8695.33, hireDate=Sun Sep 15 00:00:00 CST 2013, department=【部门名称:财务部、来自公司【公司名:腾讯、公司地址:深圳】】}

8 类加载器

Java程序的执行需要依靠JVM,JVM在进行类执行时会通过设置的CLASSPATH环境属性进行指定路径的字节码文件加载。JVM加载字节码文件的操作就需要使用类加载器(ClassLoader)。

Java提供3中类加载器:

  • Bootstrap:根加载器,又称为系统加载器,由C++编写的类加载器,是在Java虚拟机启动后进行初始化操作。主要目的是加载Java底层系统提供的核心类库
  • PlatformCLassLoader:平台类加载器。JDK 1.8之前是ExtClassLoader外部类加载器。使用Java编写的类加载器,主要功能是进行模块的加载
  • AppClassLoader:应用程序类加载器。加载ClassPath所指定的类文件或者jar文件

在这里插入图片描述

范例:获取自定义类的加载器

public class Demo6 {
    public static void main(String[] args) {
        ClassLoader classLoader = Person3.class.getClassLoader();
        System.out.println(classLoader);
        System.out.println(classLoader.getParent());
        System.out.println(classLoader.getParent().getParent());
    }
}

输出结果:

jdk.internal.loader.ClassLoaders$AppClassLoader@1f89ab83
jdk.internal.loader.ClassLoaders$PlatformClassLoader@58ceff1
null

注意:并不是说加载器只有两个,而是第三个BootStrap不可见。

当你获得了ClassLoader,就可以调用它的findClass方法加载*.class字节码文件加载类。name是类的完整名称(包.类)。

  • protected Class<?> findClass​(String name) throws ClassNotFoundException

这个方法本质上还是通过CLASSPATH找到相应的类。

8.1 通过ClassLoader加载资源文件

通过ClassLoader可以方便的找到我们定义的资源文件。通过ClassLoader.getSystemResourceAsStream(资源文件路径)方法可以获得一个资源文件读取流,通过此流读取资源文件。例如:

public class Demo10 {
    public static void main(String[] args) throws IOException {
        InputStream in = ClassLoader.getSystemResourceAsStream("config.txt");
        //InputStream in = Demo10.class.getClassLoader().getResourceAsStream("config.txt"); //找的是Resource Root下的config.txt
        BufferedReader br = new BufferedReader((new InputStreamReader(in)));
        System.out.println(br.readLine());
    }
}

注意:默认读取的是src目录下的资源文件。如果指定了某一个目录为ResourceRoot,则变为在该目录下找资源文件。
在这里插入图片描述
设置某个目录为ResourceRoot的方法:在一个目录上右键 -> Marjk Direcrotry as -> Resources Root。
在这里插入图片描述

8.2 自定义ClassLoader

根据自身需要自定义ClassLoader。注意,自定义的类加载器是在最后才执行。

为什么要自定义类加载器?
因为系统的类加载器默认从CLASSPATH路径中加载类。自定义类加载器可以自己指定路径。包括磁盘路径或者网络主机路径。但不管怎么加载,最后的数据都是以字节来描述的。

在这里插入图片描述
为了测试自定义类加载器,我们直接拷贝一个class文件到E盘根目录。注意,此class文件并没有进行打包处理,因此不能通过CLASSPATH加载。

随后我们自定义一个类加载器,并继承自ClassLoader。在ClassLoader类中有一个方法可以把字节文件转化为类结构对象Class:

  • protected final Class<?> defineClass​(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) throws ClassFormatError

因为这是一个protected方法,只能在子类中访问,这是为什么我们自定义类加载器需要继承ClassLoader。

范例:自定义类加载器

class MyClassLoader extends ClassLoader {
    private static final String CLASSPATH = "E:" +File.separator + "Company.class";
    /**
     *
     * @author
     * @date 2021/7/2
     * @param name 类的完整名称(包.类)
     * @return 类的class对象
     **/
    public Class<?> loadData(String name) throws Exception {
        byte[] data = getClassData();
        if(data != null) {
            return super.defineClass(name, data, 0, data.length);
        }
        return null;
    }
    
 	/**
     * 获取类的字节码文件
     * @author
     * @date 2021/7/2
     * @param 
     * @return 类的字节码文件
     **/
    private byte[] getClassData() throws Exception{
        InputStream in = null;
        ByteArrayOutputStream bos = new ByteArrayOutputStream(); //内存写入流
        byte[] data = new byte[1024];
        byte[] finalData = null;
        try{
            in = new FileInputStream(new File(CLASSPATH));
            int len = -1;
            while((len = in.read(data)) != -1){
                bos.write(data, 0, len);
            }
            // 以上过程也可以用in.transferTo(bos)方法代替
            finalData = bos.toByteArray();
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            if(in != null){
                in.close();
            }
            if(bos != null){
                bos.close();
            }
        }
        return finalData;
    }
}

Company类:保存在E:\\Company.class

public class Company {
    private String name;
    private String location;

    public Company(String name, String location) {
        this.name = name;
        this.location = location;
    }

    public Company() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    @Override
    public String toString() {
        return "【公司名:" + name + "、公司地址:" + location + "】";
    }
}

测试类:

public class Demo6 {
    public static void main(String[] args) {
        try {
            Class<?> c = new MyClassLoader().loadData("com.kkb.xzk.bean.Company");
            if(c != null) {
                Object obj = c.getDeclaredConstructor().newInstance();
                Method m = c.getDeclaredMethod("setName", String.class);
                m.invoke(obj, "腾讯");
                m = c.getDeclaredMethod("setLocation", String.class);
                m.invoke(obj, "深圳");
                System.out.println(obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

输出结果:

【公司名:腾讯、公司地址:深圳】

以后结合网络程序开发的话,可以通过一个远程服务器来确定类的功能。换言之,客户端上操作的类可以直接从服务器上获得。一旦服务器上修改了类文件,客户端也可以即时响应到变化,加载到最新的class文件。

在这里插入图片描述

总结:

  • ClassLoader类中提供Class<?> findClass(String name)方法可以加载CLASSPATH下定义的类。
  • 要想在CLASSPATH之外的地方加载类(包括其他磁盘路径或者网络路径),必须自己实现类加载器。自定义类加载器必须继承ClassLoader。
  • 自己设计的类加载器,首先要能够读取class的字节码数据(byte数组),其次根据这个字节数组,利用ClassLoader的Class<?> defineClass(String name, byte[] data, int off, int len)方法转化为class对象。

观察类加载器的执行情况:

public class Demo6 {
    public static void main(String[] args) {
        try {
            Class<?> c = new MyClassLoader().loadData("com.kkb.xzk.bean.Company");
            if(c != null) {
                System.out.println(c.getClassLoader());
                System.out.println(c.getClassLoader().getParent());
                System.out.println(c.getClassLoader().getParent().getParent());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

输出结果:

com.kkb.xzk.test.MyClassLoader@22f71333
jdk.internal.loader.ClassLoaders$AppClassLoader@1f89ab83
jdk.internal.loader.ClassLoaders$PlatformClassLoader@3498ed

可以看到自定义类加载器总是最后执行。

假如我们自己定义了一个java.lang.String的类,并且通过自定义类加载器加载,则会成功加载吗?

答案是否定的, java对类加载器提供双亲加载机制,不同的类加载器加载指定的类。

  • 类通常是按需加载,即第一次使用该类时才加载。由于有了类加载器,Java运行时系统不需要知道文件与文件系统。学习类加载器时,掌握Java的委派概念很重要。
  • 双亲委派模型:如果一个类加载器收到了一个类加载请求,它不会自己去尝试加载这个类,而是把这个请求转交给父类加载器去完成。每一个层次的类加载器都是如此。因此所有的类加载请求都应该传递到最顶层的启动类加载器中,只有到父类加载器反馈自己无法完成这个加载请求(在它的搜索范围没有找到这个类)时,子类加载器才会尝试自己去加载。委派的好处就是避免有些类被重复加载。
    在这里插入图片描述

9 反射与代理设计模式

9.1 传统的代理模式的缺陷

(参考面向对象高级中的接口部分关于代理模式的介绍)

代理模式首先是面向接口的设计模式。对于一个接口而言,它有真实业务实现的子类,还有一个代理类,代理类不仅执行真实的业务(接口定义的业务,通过真实业务类完成),还可以执行其他的一些任务。这就是代理设计模式。

范例:传统的静态代理设计模式

public class Demo7 {
    public static void main(String[] args) {
        IMessage messageReal = new MessageReal();
        IMessage proxy = new MessageProxy(messageReal);
        proxy.send(); 
    }
}

// 代理设计模式必须有接口
interface IMessage{
    void send();
}

// 真实业务实现类
class MessageReal implements IMessage{
    @Override
    public void send() {
        System.out.println("【真实业务】消息发送");
    }
}

// 代理类,负责真实业务执行,还可以执行其他任务
class MessageProxy implements IMessage{
    private IMessage message;

    public MessageProxy(IMessage message) {
        this.message = message;
    }

    public MessageProxy() {
    }

    @Override
    public void send() {
        if(this.message != null){
            if(this.connect()) { //代理执行额外的任务
                message.send(); //执行真实业务
                this.close();
            }
        }
    }

    public boolean connect(){
        System.out.println("【代理操作】正在连接通道...");
        System.out.println("【代理操作】连接成功!");
        return true;
    }

    public void close(){
        System.out.println("【代理操作】通道关闭");
    }
}

输出结果:

【代理操作】正在连接通道...
【代理操作】连接成功!
【真实业务】消息发送
【代理操作】通道关闭

上述代码的缺陷在于,主类需要知道MessageProxy和MessageReal两个子类。换句话说,客户端的接口与子类产生了耦合问题。从实际开发的角度,最好再引入工厂设计模式获取子类对象。

在这里插入图片描述

静态代理设计的特点是:一个代理类只为一个接口服务。如果现在有3000个业务接口,则需要3000个代理类进行处理,并且这3000个代理类实现的服务是类似的。

所以现在的问题是:如何设计一个代理类服务所有的功能一致的业务接口

9.2 动态代理设计

Java中提供了一些系统内部类实现动态代理设计。主要是两个:Proxy类和InvocationHandler接口。

  • Proxy类使用其静态方法newProxyInstance,根据真实业务对象返回一个相应的代理类对象
  • InvocationHandler实现子类需要覆写invoke方法和所有业务类的统一操作方法。invoke方法定义最后的业务操作逻辑(包含真实业务操作和其他的操作)。
    在这里插入图片描述
    InvocationHandler接口的方法:
/** 通过代理调用真实业务方法
 * proxy: 代理业务对象
 * method:真实业务的方法(即接口中定义的方法)
 * args:真是业务方法的参数
*/
Object invoke​(Object proxy, Method method, Object[] args) throws Throwable

Proxy类中的方法:

/** 返回一个代理业务对象。当我们通过此代理业务对象执行接口定义的方法时,实际上调用的是h的invoke方法。
 * loader:指明生成代理对象使用哪个类装载器。这里可以写真实业务对象的类加载器(通过obj.getClass().getClassLoader()获得),这样该方法的返回值类型就是真实业务对象的类型。
 * interfaces:真实业务对象的接口(通过obj.getClass().getInterfaces()获得)
 * h:InvocationHandler对象,其invoke方法为代理业务方法。
*/
public static Object newProxyInstance​(ClassLoader loader, 
Class<?>[] interfaces, InvocationHandler h)

思考newProxyInstance的返回值类型,可以发现其就是ClassLoader指定的类型。
在这里插入图片描述

范例:

  1. 自定义统一代理类实现InvocationHandler接口。以下是基本实现:需要保存真实业务主体对象,并且提供真实业务主体类转化为代理类的方法
	class MyInvocationHandler implements InvocationHandler{
	    private Object realObject; // 真实业务对象
	    /**
	     * 实现真实业务对象和代理业务对象的绑定。
	     * @author
	     * @date 2021/7/3
	     * @param realObject 真实业务对象
	     * @return 代理业务对象
	     **/
	    public Object bind(Object realObject){
	        this.realObject = realObject;
	        return Proxy.newProxyInstance(realObject.getClass().getClassLoader(), realObject.getClass().getInterfaces(), this);
	    }
	    .......
}
  1. 定义统一代理类的其他方法。这些方法应该是每个代理类都会执行到的。
	public boolean connect(){  //代理类代理的统一操作
        System.out.println("【代理操作】正在进行连接");
        System.out.println("【代理操作】已连接!");
        return true;
    }

    public void close(){  //代理类代理的统一操作
        System.out.println("【代理操作】连接关闭");
    }
  1. 定义invoke方法。这个方法就是代理业务方法。在此方法中要显式调用真实业务方法。此方法的第一个参数proxy不需要关注,因为它是代理对象调用真实业务方法时自动传入的。
	@Override
    /**
     * 代理类执行操作。
     * @author
     * @date 2021/7/3
     * @param proxy
     * @param method 接口中定义的方法,即真实业务方法
     * @param args 真实业务方法的参数
     * @return
     **/
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object returnData = null;
        if(this.connect()){ //代理类执行其他任务
            returnData = method.invoke(realObject, args);
            this.close(); //代理类执行其他任务
        }
        return returnData;
    }
  1. 测试类
	public class Demo8 {
	    public static void main(String[] args) {
	        IMessage msg = (IMessage) new MyInvocationHandler().bind(new MessageReal()); //msg即为IMessage接口的代理类对象
	        msg.send();  //调用此方法时,实际是通过invocationHandler的invoke方法执行的。
	    }
	}

Proxy就是根据真实业务主体对象,返回一个代理业务对象(newProxyInstance方法)。
InvocationHandler就是包装一个代理类对象的代理操作(invoke方法)。根据之前的静态代理设计可以发现,代理类之中必须含有一个真实业务类的对象

10 反射与Annotation(注解)

我们知道注解可以声明类、对象以及类的属性成员,因此获得注解信息的方法,理论上在Class<?>,Constructor<?>,Method和Field类上都应该存在。事实上,在java.lang.reflect包中,有一个AccessibleObject的类(是后三者的父类),其中存在有得到注解信息的方法如下:

  • 获取全部Annotations:
    public Annotation[] getAnnotations()
  • 获取指定Annotation:
    public <T extends Annotation> T getAnnotation​(Class<T> annotationClass)

有的Annotation可以获取到(比如@FunctionalInterface),有的获取不到(比如@SuppressWarnings)。
@FunctionalInterface 定义:

@Documented 
@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

@SuppressWarnings定义:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

注意,前者@Retention(RetentionPolicy.RUNTIME),说明在运行时生效,可以在运行时获取。后者@Retention(RetentionPolicy.SOURCE),说明在源代码编写时生效。而在RetationPolicy中还有一个CLASS的定义,表示在类定义的时候生效。

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

10.1 元注解

元注解,即对注解的注解。

10.1.1 包含哪些

  • @Retention - 标识这个注解怎么保存,与枚举类RetentionPolicy搭配使用,标明此注解是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
  • @Documented - 标记这些注解是否包含在用户文档中 javadoc。
  • @Target - 标记这个注解应该是哪种 Java 成员。
  • @Inherited - 标记这个注解是自动继承的
    • 子类会继承父类使用的注解中被@Inherited修饰的注解
    • 接口继承关系中,子接口不会继承父接口中的任何注解,不管父接口中使用的注解有没有被@Inherited修饰
    • 类实现接口时不会继承任何接口中定义的注解

10.2 自定义Annotation

10.2.1 注解架构

在这里插入图片描述

Annotation与RetentionPolicy 与ElementType 。

每 1 个 Annotation 对象,都会有唯一的 RetentionPolicy 属性;至于 ElementType 属性,则有 1~n个。

ElementType(注解的用途类型) - @Target

“每 1 个 Annotation” 都与 “1~n 个 ElementType” 关联。当 Annotation 与某个 ElementType 关联时,就意味着:Annotation有了某种用途。例如,若一个 Annotation 对象是 METHOD 类型,则该Annotation 只能用来修饰方法。

package java.lang.annotation;
public enum ElementType {
	TYPE, /* 类、接口(包括注释类型)或枚举声明 */
	FIELD, /* 字段声明(包括枚举常量) */
	METHOD, /* 方法声明 */
	PARAMETER, /* 参数声明 */
	CONSTRUCTOR, /* 构造方法声明 */
	LOCAL_VARIABLE, /* 局部变量声明 */
	ANNOTATION_TYPE, /* 注释类型声明 */
	PACKAGE /* 包声明 */
}
RetentionPolicy(注解作用域策略)- @Retention

Enum RetentionPolicy是一个枚举类型,这个枚举决定了Retention注解应该如何去保持,也可理解为Rentention 搭配 RententionPolicy使用。RetentionPolicy有3个值:CLASS RUNTIME SOURCE

  • 用@Retention(RetentionPolicy.CLASS)修饰的注解,表示注解的信息被保留在class文件(字节码文件)中当程序编译时,但不会被虚拟机读取在运行的时候;

  • 用@Retention(RetentionPolicy.SOURCE )修饰的注解,表示注解的信息会被编译器抛弃,不会留在class文件中,注解的信息只会留在源文件中;

  • 用@Retention(RetentionPolicy.RUNTIME )修饰的注解,表示注解的信息被保留在class文件(字节码文件)中当程序编译时,会被虚拟机保留在运行时,所以他们可以用反射的方式读取。RetentionPolicy.RUNTIME 可以让你从JVM中读取Annotation注解的信息,以便在分析程序的时候使用.

10.2.2 定义格式

@interface 自定义注解名{}

10.2.3 注意事项

  1. 定义的注解,自动继承了java.lang,annotation.Annotation接口
  2. 注解中的每一个方法,实际是声明的注解配置参数
    • 方法的名称就是 配置参数的名称
    • 方法的返回值类型,就是配置参数的类型。只能是:基本类型/Class/String/enum
  3. 可以通过default来声明参数的默认值
  4. 如果只有一个参数成员,一般参数名为value
  5. 注解元素必须要有值,我们定义注解元素时,经常使用空字符串、0作为默认值。

@interface的声明就像声明一个接口,里面的方法也是抽象方法。只不过用的时候和接口所有不同罢了。

10.2.4 范例

10.2.4.1 标准范例
  1. 标准格式
	@Documented
	@Target(ElementType.TYPE)
	@Retention(RetentionPolicy.RUNTIME)
	public @interface MyAnnotation1 {
		参数类型 参数名() default 默认值;
	}
  1. 范例
    使用@interface自定义Annotation。
    范例:自定义Annotation
public class Demo9 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Method method = A.class.getDeclaredMethod("send", String.class);
        DefaultAnnotation anno1 = method.getAnnotation(DefaultAnnotation.class);
        String message = anno1.title() + "、" + anno1.url();
        method.invoke(A.class.getDeclaredConstructor().newInstance(), message); // 反射调用方法
    }
}

@Retention(RetentionPolicy.RUNTIME) //声明什么时候生效
@interface DefaultAnnotation{  //自定义的Annotation
    public String title();
    public String url() default "abc"; //带默认值的方法
}

class A{
    @DefaultAnnotation(title = "title") // 必须把没有默认值的方法声明出来
    public void send(String msg){
        System.out.println("【消息发送】" + msg);
    }
}

使用Annotation最大的好处是可以结合反射实现更复杂的功能。比如上述程序中可以把发送内容定义在Annotation里。

10.2.4.2 ORM框架范例

ORM,即Object-Relation-Mapping,意为将对象与数据库表关联的一种框架。具体来说,就是把每个类对应到一个表,类中的每一个属性对应表的每一个字段。将类与表关联可以通过注解来实现:

  • 定义表注解
/**
 * @Author: HD
 * @Description: 注明每一个类对应哪一个数据库
 * @Date Created in 2021-07-05 13:55
 * @Modified By:
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TableAnnotation {
    /**
     * 表名
     * @author
     * @date 2021/7/5
     * @return
     **/
    String value();
}
  • 定义字段注解
/**
 * 注解类的Field,表示类每一个Field对应数据库的哪个字段
 * @author
 * @date 2021/7/5
 * @param null
 * @return
 **/
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ColumnAnnotation {
    /**
     * 字段名
     * @author
     * @date 2021/7/5
     * @return
     **/
    String name();
    /**
     * 字段的类型
     * @author
     * @date 2021/7/5
     * @return
     **/
    String type();
    /**
     * 字段的长度
     * @author HanDing
     * @date 2021/7/5
     * @return
     **/
    String length();
}
  • 定义bean类,注解类和属性
/**
 * @Author: HD
 * @Description:
 * @Date Created in 2021-07-05 13:54
 * @Modified By:
 */
@TableAnnotation("test_table")
public class Book {
    @ColumnAnnotation(name = "name", type = "varchar", length = "50")
    private String name;
    @ColumnAnnotation(name = "info", type = "varchar", length = "5000")
    private String info;
    @ColumnAnnotation(name = "id", type = "int", length = "11")
    private int id;
    // 构造方法,普通方法等略
}
  • 测试
/**
 * @Author: HD
 * @Description:
 * @Date Created in 2021-07-05 14:29
 * @Modified By:
 */
public class Demo11 {
    public static void main(String[] args) throws Exception {
        Class<?> c = Class.forName("com.kkb.xzk.bean.Book");
        TableAnnotation tableAnnotation = c.getAnnotation(TableAnnotation.class);
        System.out.println("类对应的数据库表:" + tableAnnotation.value()); //得到注解的信息

        Field[] fs = c.getDeclaredFields();
        for(Field f : fs){
            ColumnAnnotation ca = f.getAnnotation(ColumnAnnotation.class);
            System.out.println("属性名称:" + f.getName());
            System.out.println("\t字段名称:" + ca.name());
            System.out.println("\t字段类型:" + ca.type());
            System.out.println("\t字段长度:" + ca.length());
        }
    }
}

输出结果:

类对应的数据库表:test_table
属性名称:name
	字段名称:name
	字段类型:varchar
	字段长度:50
属性名称:info
	字段名称:info
	字段类型:varchar
	字段长度:5000
属性名称:id
	字段名称:id
	字段类型:int
	字段长度:11

通过上面的结果可以发现,这样就把类和类中的属性对应到一个表中了。

10.3 Annotation整合工厂模式

首先我们先实现代理模式和工厂模式的整合。

public class Demo10 {
    public static void main(String[] args) {
        new MessageService().send("ABCD");
    }
}
//发送消息的主类
class MessageService{
    private IMessage2 message;
    public MessageService(){
        this.message = MessageFactory.getInstance(MessageImpl.class);
    }
    public void send(String str){
        this.message.send(str);
    }
}

//消息工厂,通过代理获得一个IMessage2的实例化对象
class MessageFactory{
    private MessageFactory(){}
    public static <T> T getInstance (Class<T> clazz) {
        try {
            return (T) new MessageProxy2().bind(clazz.getDeclaredConstructor().newInstance());
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }
}

interface IMessage2{
    void send(String str);
}

class MessageProxy2 implements InvocationHandler{
    private Object target; //真实业务对象
    public Object bind(Object target){ //绑定真实业务对象,返回一个伪造的代理对象
        this.target = target;
        return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this);
    }

    public boolean connect(){
        if(this.target == null){
            return false;
        }
        System.out.println("【代理操作】正在进行连接...");
        System.out.println("【代理操作】连接成功");
        return true;
    }

    public void close(){
        System.out.println("【代理操作】连接关闭");
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object obj = null;
        try {
            if(connect()) {
                obj = method.invoke(this.target, args);
                this.close();
            }else{
                throw new RuntimeException("消息发送失败");
            }
        }catch(RuntimeException e){
            e.printStackTrace();
        }
        return obj;
    }
}

class MessageImpl implements IMessage2{
    @Override
    public void send(String str) {
        System.out.println("【真实业务】消息发送" + str);
    }
}

上述程序可以实现基本的代理与工厂设计模式。但上述程序有一个不好的地方,在消息发送主类里面,需要手动指定是哪个真实业务类(this.message = MessageFactory.getInstance(MessageImpl.class);),那么怎么可以在外部告诉程序是哪个类?

可以使用注解来将这个信息传给消息发送主类。

首先自定义注解:

@Retention(RetentionPolicy.RUNTIME)
@interface UseMessage{
    public Class<?> clazz();
}

其次将该注解声明到消息主类上。这样消息主类就可以获取到该注解,从而拿到注解里声明的clazz。

@UseMessage(clazz=MessageImpl.class)
class MessageService{
    private IMessage2 message;
    public MessageService(){
        UseMessage a = MessageService.class.getAnnotation(UseMessage.class);
        Class<?> clazz = a.clazz();
        this.message = (IMessage2) MessageFactory.getInstance(clazz);
    }
    public void send(String str){
        this.message.send(str);
    }
}

这样我们只需要修改注解的@UseMessage(clazz=MessageImpl.class)中的clazz的值,即可整个程序代码的修改。

11 内省

11.1 简介

基于反射 , java所提供的一套应用到JavaBean的API

	一个定义在包中的类 ,
			拥有无参构造器
			所有属性私有,
			所有属性提供get/set方法
			实现了序列化接口
	这种类, 我们称其为 bean类 .
	
Java提供了一套java.beans包的api , 对于反射的操作, 进行了封装 !

内省可以帮助我们更快的获取一个类的属性的setter、getter方法。它的操作流程如下。

在这里插入图片描述

11.2 使用

11.2.1 Introspector

获取Bean类信息

方法:
	BeanInfo getBeanInfo(Class cls)
	通过传入的类信息, 得到这个Bean类的封装对象 .

11.2.2 BeanInfo

常用的方法:

	MethodDescriptor[] getPropertyDescriptors():
	获取bean类的 get/set方法 数组

11.2.3 MethodDescriptor

常用方法:
	1. Method getReadMethod();
	获取一个get方法
	
	2. Method getWriteMethod();
	获取一个set方法
	
有可能返回null 注意 ,加判断 !

范例:

public class Demo12 {
    public static void main(String[] args) throws IntrospectionException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        BeanInfo bi = Introspector.getBeanInfo(Employee.class); //获得BeanInfo对象
        PropertyDescriptor[] pds = bi.getPropertyDescriptors();
        Employee emp = (Employee) Employee.class.getDeclaredConstructor().newInstance();
        for(PropertyDescriptor pd : pds){
            Class<?> type = pd.getPropertyType(); //得到属性的类型
            String name = pd.getName(); //得到属性的名称
            Method get = pd.getReadMethod(); //得到属性的get方法
            Method set = pd.getWriteMethod(); //得到属性的set方法
            System.out.println("属性名称:" + name);
            System.out.println("属性类型:" + type.getSimpleName());
            System.out.println("属性的set方法:" + set);
            System.out.println("属性的get方法:" + get);
        }
    }
}

输出结果:

属性名称:class
属性类型:Class
属性的set方法:null
属性的get方法:public final native java.lang.Class java.lang.Object.getClass()
属性名称:department
属性类型:Department
属性的set方法:public void com.kkb.xzk.bean.Employee.setDepartment(com.kkb.xzk.bean.Department)
属性的get方法:public com.kkb.xzk.bean.Department com.kkb.xzk.bean.Employee.getDepartment()
属性名称:hireDate
属性类型:Date
属性的set方法:public void com.kkb.xzk.bean.Employee.setHireDate(java.util.Date)
属性的get方法:public java.util.Date com.kkb.xzk.bean.Employee.getHireDate()
属性名称:id
属性类型:long
属性的set方法:public void com.kkb.xzk.bean.Employee.setId(long)
属性的get方法:public long com.kkb.xzk.bean.Employee.getId()
属性名称:name
属性类型:String
属性的set方法:public void com.kkb.xzk.bean.Employee.setName(java.lang.String)
属性的get方法:public java.lang.String com.kkb.xzk.bean.Employee.getName()
属性名称:salary
属性类型:double
属性的set方法:public void com.kkb.xzk.bean.Employee.setSalary(double)
属性的get方法:public double com.kkb.xzk.bean.Employee.getSalary()

注意:

  • 每个类中都有一个class对象,该对象没有set方法,因此set方法那里得到的是null。
  • boolean属性的get方法实际命名是isXxx,这里要注意。

以上只有在开发框架的时候比较有用,我们实际开发中99%可能都遇不到。这里提醒的是在编写bean类的时候的规范

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值