今天我们继续整理反射与简单类、与代理模式、与Annotation的关系知识点。
反射与简单Java类
传统属性赋值弊端
简单Java类主要是由属性所组成,并且提供有相应的setter、getter处理方法,同时简单Java类最大的特征就是通过对象来保存相应的类的属性内容,但是如果使用传统的简单Java类开发,那么也会面临一些弊端。
我们先来看一个简单Java类:
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 "Emp [ename=" + this.ename + ", job=" + this.job + "]";
}
}
为了方便理解,Emp类中定义的ename和job都是String类型的,按照传统的做法,应该首先实例化Emp对象,然后根据实例化对象进行setter和getter方法的调用来设置属性:
public class TestDemo {
public static void main(String[] args) throws Exception {
Emp emp = new Emp(); //更多的情况下开发者关注的是内容的设置
emp.setEname("zijun");
emp.setJob("UI");
//使用为对象的设置
System.out.println(emp.toString());
}
}
在整个整个Emp对象实例化并设置数据的操作过程之中,设置数据的部分是最麻烦的,如果现在Emp中提供有50个属性,那么对于整个程序而言将会有一堆的setter调用。在实际的开发中,简单Java类的个数是非常多的,如果所有的简单Java类都牵扯到属性赋值的时候,这种情况代码编写的重复性就会非常的高。
按照传统的直观的编程方式所带来的问题就是代码会存在有大量的重复操作,如果想要解决对象的重复处理操作唯一的解决方案就是反射机制,反射机制最大的特征就是根据其自身的特点(Object类直接操作、可以直接操作属性或方法)实现相同功能类的重复操作的抽象处理。
属性自动赋值思路
经过了分析之后已经确认了当前简单Java类操作的问题所在,而对于开发者而言就与要想办法通过一种解决方案来实现属性内容的自动设置,强烈建议使用字符串的形式描述对应的类型。
在进行程序开发的时候String字符串可以描述内容有很多,并且也可以由开发者自行定义字符串的结构,下面采用“属性:内容|属性:内容|”的形式来为简单Java类中的属性初始化:
应该由一个专门的ClassInstanceFactory类来负责所有的反射处理,即:接受反射对象与要设置的属性内容,同时可以获取指定类的实例化对象:
class ClassInstanceFactory{
private ClassInstanceFactory() {} //构造方法私有化
/**
* 实例化对象创建的方法,该对象可以根据传入的字符串结构“属性:值|属性:值|”
* @param clazz 要进行反射实例化的Class对象,有Class就可以反射实例化对象
* @param value 要设置给对象的属性内容
* @return 一个已经配置好属性内容的简单Java类对象
*/
public static <T> T create(Class<?> clazz, String value){
return null;
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
String value = "ename:zijun|job:UI";
Emp emp = ClassInstanceFactory.create(Emp.class, value);
System.out.println(emp.toString());
}
}
在当前的开发之中,所需要留给用户完善的就是ClassInstanceFactory.create()方法,调用方法传入相应属性与内容然后返回实例化对象,这就是我们的设计思路。
单级属性赋值
对于此时的Emp类中发现所给出的数据内容没有其他的引用关联了,只是描述了Emp本类的对象这样的设置就称为单级属性处理,所以此时应该处理两件事:
- 通过反射进行指定的类对象实例化处理;
- 进行内容的设置(Field的属性类型、Method的名称、要设置的内容);
首先我们要定义一个StringUtils实现首字母大写功能,因为获取到的属性名称都是小写的如ename,要转换成方法名称首字母要大写:
class StringUtils{
public static String initcap(String str) {
if(str == null || "".equals(str)) {
return str;
}
if(str.length() == 1) {
return str.toUpperCase();
}else {
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
}
}
然后我们要来定义BeanUtils工具类,该工具类主要实现属性的设置:
class BeanUtils{
private BeanUtils() {}
/**
* 实现指定对象的属性设置
* @param obj 要进行反射操作的实例化对象
* @param value 包含有指定内容的实例化字符串,格式“属性:值|属性:值|”
*/
public static void setValue(Object obj, String value) {
String results [] = value.split("\\|"); //按照竖线“|”拆分
for(int i = 0; i < results.length; i ++) { //循环设置属性内容
//attval[0]保存的是“属性名称”,attval[1]保存的是“属性的值”
String attval [] = results[i].split(":"); //获取“属性”和“值”
try {
Field field = obj.getClass().getDeclaredField(attval[0]);
Method setMethod = obj.getClass().getDeclaredMethod("set" +
StringUtils.initcap(attval[0]), field.getType());
setMethod.invoke(obj, attval[1]);
}catch(Exception e){
}
}
}
}
最后我们实现一下ClassInstanceFactory类:
class ClassInstanceFactory{
private ClassInstanceFactory() {} //构造方法私有化
/**
* 实例化对象创建的方法,该对象可以根据传入的字符串结构“属性:值|属性:值|”
* @param clazz 要进行反射实例化的Class对象,有Class就可以反射实例化对象
* @param value 要设置给对象的属性内容
* @return 一个已经配置好属性内容的简单Java类对象
*/
public static <T> T create(Class<?> clazz, String value){
//如果要想采用反射进行Java属性设置的时候,类中必须要有无参构造
try {
Object obj = clazz.getDeclaredConstructor().newInstance();
BeanUtils.setValue(obj, value); //通过反射设置属性
return (T)obj; //返回对象
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
}
这样一执行,就可以了:
Emp [ename=zijun, job=UI]
即使现在类中的属性再多,也可以轻松的实现setter的调用(类对象实例化处理)
设置多种数据类型
现在已经成功的实现了单级的属性配置,但这里依然要考虑一个实际的情况,当前所给的数据类型只是String,但在实际的开发中,面对简单Java类中的属性类型一般的可选方案有:long(Long)、int(Integer)、double(Double)、String、Date(日期或日期时间),所以对于当前的程序代码就必须做出修改,要求可以实现各种数据类型的配置。
既然要求实现不同类型的内容设置,并且BeanUtils类主要是完成属性赋值处理的,就可以在这个类中追加有一系列的属性处理方法:
class BeanUtils{
private BeanUtils() {}
/**
* 实现指定对象的属性设置
* @param obj 要进行反射操作的实例化对象
* @param value 包含有指定内容的实例化字符串,格式“属性:值|属性:值|”
*/
public static void setValue(Object obj, String value) {
String results [] = value.split("\\|"); //按照竖线“|”拆分
for(int i = 0; i < results.length; i ++) { //循环设置属性内容
//attval[0]保存的是“属性名称”,attval[1]保存的是“属性的值”
String attval [] = results[i].split(":"); //获取“属性”和“值”
try {
Field field = obj.getClass().getDeclaredField(attval[0]);
Method setMethod = obj.getClass().getDeclaredMethod("set" + StringUtils.initcap(attval[0]), field.getType());
Object converValue = BeanUtils.converAttributeValue(field.getType().getName(), attval[1]);
setMethod.invoke(obj, converValue);
}catch(Exception e){
}
}
}
/**
* 实现属性类型转换处理
* @param type 属性类型,通过Field获取
* @param value 属性值,传入的都是字符串,要将其变为指定类型
* @return 转换后的数据
*/
private static Object converAttributeValue(String type, String value) {
if("long".equals(type) || "java.lang.long".equals(type)){ //长整型
return Long.parseLong(value);
}else if("int".equals(type) || "java.lang.int".equals(type)){ //整型
return Integer.parseInt(value);
}else if("double".equals(type) || "java.lang.double".equals(type)){ //双精度浮点型
return Double.parseDouble(value);
}else if("java.util.Date".equals(type)){ //日期
SimpleDateFormat sdf = null;
if(value.matches("\\d{4}-\\d{2}-\\d{2}")) { //日期类型
sdf = new SimpleDateFormat("yyyy-MM-dd");
} else if(value.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}")){ //日期时间类型
sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
} else {
return new Date(); //当前日期
}
try {
return sdf.parse(value);
} catch (ParseException e) {
return new Date(); //当前日期
}
}else { //字符串
return value;
}
}
}
接下来我们在Emp中多添加几种类型,看一下效果:
class Emp{
private long eno;
private String ename;
private String job;
private double salary;
private Date hiredate;
public long getEno() {
return eno;
}
public void setEno(long eno) {
this.eno = eno;
}
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;
}
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 "Emp [eno=" + this.eno + ", ename=" + this.ename + ", job=" +
this.job + ", salary=" + this.salary + ", hiredate=" + this.hiredate + "]";
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
String value = "eno:9527|ename:zijun|job:UI|salary:8500|hiredate:2020-04-09";
Emp emp = ClassInstanceFactory.create(Emp.class, value);
System.out.println(emp.toString());
}
}
Emp [eno=9527, ename=zijun, job=UI, salary=8500.0, hiredate=Thu Apr 09 00:00:00 CST 2020]
这个例子只是举了常用的几种类型,如果要想将一个产品推广就要考虑所有的可能出现的类型,同时所有可能出现的日期格式也要考虑到。
级联对象实例化
如果说现在给定的类对象存在有其他引用的级联关系,就称其为多级设置,例如:一个雇员属于一个部门,一个部门属于一个公司。这个时候对于简单Java类的基本关系定义如下:
class Company{
private String cname;
private Date createDate;
}
class Dept{
private String dname;
private String loc;
private Company company;
}
class Emp{
private long eno;
private String ename;
private String job;
private double salary;
private Date hiredate;
private Dept dept;
}
如果通过Emp进行操作,则应使用“ . ”作为级联关系的处理:
- dept.dname:财务部 -> Emp类实例化对象.getDept().setDname("财务部");
- dept.company.name:优快云 -> Emp类实例化对象.getDept().getCompany().setName("优快云");
但是考虑到代码的简洁性,就要考虑使用级联的配置自动实现类中属性的实例化:
String value = "eno:9527|ename:zijun|job:UI|salary:8500|hiredate:2020-04-09" +
"|dept.dname:财务部|dept.company.cname:优快云";
现在的属性存在有多级关系,而多级关系应该与单级关系配置区分开:
/**
* 实现指定对象的属性设置
* @param obj 要进行反射操作的实例化对象
* @param value 包含有指定内容的实例化字符串,格式“属性:值|属性:值|”
*/
public static void setValue(Object obj, String value) {
String results [] = value.split("\\|"); //按照竖线“|”拆分
for(int i = 0; i < results.length; i ++) { //循环设置属性内容
//attval[0]保存的是“属性名称”,attval[1]保存的是“属性的值”
String attval [] = results[i].split(":"); //获取“属性”和“值”
try {
if(attval[0].contains(".")) { //说明是多级关系
String temp [] = attval[0].split("\\.");
Object currentObject = obj;
//最后一位肯定是指定类中的属性名称,不再实例化处理的范畴之内
for(int x = 0; x < temp.length - 1; x ++) { //实例化
//调用相应的getter方法,如果返回null,表示该对象未实例化
Method getMethod = currentObject.getClass().getDeclaredMethod("get" + StringUtils.initcap(temp[x]));
Object tempObject = getMethod.invoke(currentObject);
if(tempObject == null) { //对象未实例化
Field field = currentObject.getClass().getDeclaredField(temp[x]); //获取属性类型
Method setMethod = currentObject.getClass().getDeclaredMethod("set" + StringUtils.initcap(temp[x]), field.getType());
Object newObject = field.getType().getDeclaredConstructor().newInstance(); //实例化
setMethod.invoke(currentObject, newObject);
currentObject = newObject;
}else {
currentObject = tempObject;
}
}
}else { //单级配置
Field field = obj.getClass().getDeclaredField(attval[0]);
Method setMethod = obj.getClass().getDeclaredMethod("set" + StringUtils.initcap(attval[0]), field.getType());
Object converValue = BeanUtils.converAttributeValue(field.getType().getName(), attval[1]);
setMethod.invoke(obj, converValue);
}
}catch(Exception e){
}
}
}
这里要注意几个Object的关系:
- 如果是多级关系,那么就使用currentObject来获得obj,这时不影响下一步操作时的obj调用;
- 然后使用tempObject充当中间角色,让tempObject获得调用currentObject.getXXX()方法的返回值;
- 如果返回值为空,说明多级关系的对象还未实例化,使用newObject实例化currentObject对象,并将其赋给currentObject;
- 如果返回值不为空,说明对象已实例化,直接将tempObject赋给currentObject;
然后我们检查一下:
public class TestDemo {
public static void main(String[] args) throws Exception {
String value = "eno:9527|ename:zijun|job:UI|salary:8500|hiredate:2020-04-09" +
"|dept.dname:财务部|dept.company.cname:优快云";
Emp emp = ClassInstanceFactory.create(Emp.class, value);
System.out.println(emp.toString());
System.out.println(emp.getDept());
System.out.println(emp.getDept().getCompany());
}
}
Emp [eno=9527, ename=zijun, job=UI, salary=8500.0, hiredate=Thu Apr 09 00:00:00 CST 2020]
com.test.Dept@90f6bfd
com.test.Company@47f6473
这种自动的级联实例化的操作,在以后项目的编写中一定会使用到。
级联属性赋值
现在已经成功的实现了级联对象实例化,现在就该考虑级联的属性设置了,在之前考虑级联对象的实例化处理的时候发现,循环的时候都将最后一位少掉了,因为最后一位是类中的属性。
也就是说,当上述的代码操作完成后,我们看到的currentObject就是现在要操作的对象,也就是说可以进行setter、getter方法调用的对象了,并且理论上该对象一定不可能为空,随后就可以按照之前的方式利用对象进行setter方法调用:
//进行属性内容的设置
Field field = currentObject.getClass().getDeclaredField(temp[temp.length - 1]); //获得成员
Method setMethod = currentObject.getClass().getDeclaredMethod("set" +
StringUtils.initcap(temp[temp.length - 1]), field.getType());
Object converValue = BeanUtils.converAttributeValue(field.getType().getName(), attval[1]);
setMethod.invoke(currentObject, converValue);
在下面继续进行属性的赋值,现在就非常简单,唯一要注意的一点是obj已经变成了currentObject,最后来检查一下:
public class TestDemo {
public static void main(String[] args) throws Exception {
String value = "eno:9527|ename:zijun|job:UI|salary:8500|hiredate:2020-04-09" +
"|dept.dname:财务部|dept.company.cname:优快云";
Emp emp = ClassInstanceFactory.create(Emp.class, value);
System.out.println(emp.toString());
System.out.println(emp.getDept().getDname());
System.out.println(emp.getDept().getCompany().getCname());
}
}
Emp [eno=9527, ename=zijun, job=UI, salary=8500.0, hiredate=Thu Apr 09 00:00:00 CST 2020]
财务部
优快云
ClassLoader类加载器
ClassLoader类加载器简介
在Java语言中提供有一个系统的环境变量:CLASSPATH,这个环境属性的作用是在JVM进程启动的时候进行类加载路径的定义,在JVM中可以根据类加载器进行指定路径的加载,也就是说找到了类的加载器就找到了类的来源。
如果现在想获得类的加载器,一定要通过ClassLoader来获取,而要获取ClassLoader对象,就一定要通过Class类(反射的根源)来实现,方法:public ClassLoader getClassLoader();但获取了ClassLoader之后,还可以获取其父类的ClassLoader类对象:public final ClassLoader getParent();
从JDK1.8之后的版本提供有一个PlatformClassLoader的类加载器,而在JDK1.8以前的版本中,提供的加载器为ExtClassLoader,因为在JDK安装目录中有一个ext目录,开发者可以将*.jar文件拷贝到此目录中,就可以直接执行了,但这样的处理开发并不安全,所以从JDK1.9开始将其彻底废除了,同时为了与系统类加载器和应用类加载器之间保持设计的平衡,还提供有平台类加载器。
当获得了类加载器之后就可以利用类加载器来实现类的反射加载处理。
自定义ClassLoader处理类
清楚了类加载器的功能之后就可以根据需要实现自定义的类加载器,但是千万要记住自定义的加载器加载顺序是在所有系统类加载器之后。系统中的类加载器都是根据CLASSPATH路径进行类加载的,而如果有了自定义的类加载器,就可以由开发者任意指派类的加载位置。
随意编写一个程序类并将这个类保存在磁盘上:
public class Message {
public void send() {
System.out.println("www.csdn.com");
}
}
将此类直接拷贝到D盘上进行编译处理并且不打包,原始的处理方式是先要进行javac Message.java编译为class文件才能执行的,如果此时这个类没有编译处理,就无法通过CLASSPATH正常加载。
自定义一个类加载器并且继承自ClassLoader类,在ClassLoader类中为用户提供有一个直接转换为类结构的方法:
- 定义类:protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) throws ClassFormatError
public class ZIJUNClassLoader extends ClassLoader {
private static final String MESSAGE_CLASS_PATH = "D:" + File.separator + "Message.class";
/**
* 进行指定类的加载
* @param className 类的完整名称“包.类名称”
* @return 返回一个指定的Class对象
* @throws Exception 如果类文件不存在,则无法加载
*/
public Class<?> loadData(String className) throws Exception{
byte data [] = this.loadClassData(); //读取二进制数据文件
if(data != null) { //读取到了
return super.defineClass(className, data, 0, data.length);
}
return null;
}
private byte [] loadClassData() throws Exception { //通过文件进行类的加载
InputStream input = null;
ByteArrayOutputStream bos = null; //数据加载到内存中
byte data [] = null;
try {
bos = new ByteArrayOutputStream(); //实例化内存流对象
input = new FileInputStream(new File(MESSAGE_CLASS_PATH));
input.transferTo(bos); //读取数据
data = bos.toByteArray(); //将所有读取到的字节数据取出
}catch(Exception e) {
e.printStackTrace();
}finally {
if(input != null) {
input.close();
}
if(bos != null) {
bos.close();
}
}
return data;
}
}
编写测试类实现类加载控制:
public class TestDemo {
public static void main(String[] args) throws Exception {
ZIJUNClassLoader classloader = new ZIJUNClassLoader(); //实例化自定义类加载器
Class<?> cls = classloader.loadData("com.zijun.util.Message"); //进行类的加载
Object obj = cls.getDeclaredConstructor().newInstance();
Method method = cls.getDeclaredMethod("send");
method.invoke(obj);
}
}
www.csdn.com
如果在以后结合到网络程序开发的话,就可以通过一个远程的服务器来确定类的功能:
因为自定义加载类是最后执行的,所以还是有一定隐患的,如果说现在我们自定义了一个类,名字叫做java.lang.String,并利用了类加载器进行加载处理,这个类将不会被加载,Java之中针对于类加载器提供有双亲加载机制,如果现在要加载的程序类是由系统提供的类则会由系统进行加载,如果现在开发者定义的加载类与系统类名称相同,为了保证系统的安全性绝对不会加载。
反射与代理设计模式
代理设计模式是在程序开发之中使用最多的设计模式,带设计模式的核心就是有正式的业务实现类与代理业务实现类,并且代理类要完成比真实业务更多的处理操作,代理设计模式在之前的一篇文章中简单的提到过。
静态代理设计模式
所有的代理设计模式如果按照设计要求来讲,必须是基于接口的设计,也就是说要首先定义核心接口的组成,我们来模拟一个消息发送的代理操作结构:
interface IMessage{ //传统代理设计必须有接口
public 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 boolean connect() {
System.out.println("{消息代理处理}通道连接");
return true;
}
public void close() {
System.out.println("{消息代理处理}关闭通道");
}
@Override
public void send() {
if(this.connect()) {
this.message.send(); //消息发送处理
this.close();
}
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
IMessage msg = new MessageProxy(new MessageReal());
msg.send();
}
}
{消息代理处理}通道连接
发送消息!
{消息代理处理}关闭通道
这是一个非常简单的代理模式,但是这个代理模式是有缺陷的,我们能看到要实现这个代理模式,主类除了要知道IMessahe还要知道MessageProxy和MessageReal,这时我们就可以发现客户端的接口与具体的子类会产生耦合问题,所以这样的操作从实际的开发来讲最好再引入工厂设计模式进行代理对象的获取。
以上的代理设计模式为静态代理设计模式,这种静态代理设计的特点是:一个代理类只为一个接口服务,那么如果现在准备了很多业务接口,按照这种做法就要编写很多代理类并且这些代理类操作形式类似。
现在需要解决的问题在于,如何可以让一个代理类满足于所有的业务接口操作需求,我们可以采用反射机制来实现。
动态代理设计模式
通过静态代理设计模式的缺陷可以发现,最好的做法是为所有的功能一致的业务操作提供有统一的代理处理操作,而这就可以通过动态代理机制实现,但是在动态代理机制里面需要考虑到如下几点:
不管是动态代理类还是静态代理类都一定要接收真实业务实现子类对象;
由于动态代理类不再与某一个具体的接口进行捆绑,所以应该可以动态获取类的接口信息;
在进行动态代理实现的操作中,首先需要关注的就是一个InvocationHandler接口,这个接口规定了代理方法的执行:
interface InvocationHandler{
/**
* 代理方法调用,代理主题类之中执行的方法都是此方法
* @param proxy 要代理的对象
* @param method 要执行的接口方法名称
* @param args 传递的参数
* @return 某一个方法的返回值
* @throws Throwable 方法调用时出现的错误继续向上抛出
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
在进行动态代理设计的时候,对于动态对象的创建是由JVM底层完成的,此时主要依靠的是java.lang.reflect.Proxy程序类,而这个程序类之中只提供有一个核心方法:
/**
* 代理对象
* @param loader 获取当前真实主体类的ClassLoader
* @param interfaces 代理是围绕接口进行的,所以要获得真是主体类的接口信息
* @param h 代理处理的方法
* @return
*/
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {
}
interface IMessage{ //传统代理设计必须有接口
public void send(); //业务方法
}
class MessageReal implements IMessage{
@Override
public void send() {
System.out.println("发送消息!");
}
}
class ZIJUNProxy implements InvocationHandler{
private Object target; //保存真实业务对象
/**
* 进行真实业务对象与代理业务对象之间的绑定处理
* @param target 真实业务对象
* @return Proxy生成的代理业务对象
*/
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
public boolean connect() {
System.out.println("{消息代理处理}通道连接");
return true;
}
public void close() {
System.out.println("{消息代理处理}关闭通道");
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("{执行方法}" + method);
Object returnData = null;
if(this.connect()) {
returnData = method.invoke(this.target, args);
this.close();
return returnData;
}
return null;
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
IMessage msg = (IMessage)new ZIJUNProxy().bind(new MessageReal());
msg.send();
}
}
{执行方法}public abstract void com.zijun.wang1.IMessage.send()
{消息代理处理}通道连接
发送消息!
{消息代理处理}关闭通道
如果认真观察系统中提供的Proxy.newProxyInstance()方法,该方法会使用大量的底层机制实现代理对象的动态创建,所有的代理类是符合所有相关相关功能需求的操作功能类,不再代表具体的接口,这样在处理的时候就必须依赖类加载器与接口进行代理对象的伪造。
CGLIB实现代理设计模式
从JAVA的官方来讲已经明确要求了如果想要实现代理设计模式,那么一定是基于接口的应用,所以在官方给出的Proxy类创建类代理对象的时候都需要传递该对象所有的接口信息:
Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
但有一部分开发者认为不应该强迫性基于接口实现代理设计,所以一些开发者就开发出了一个CGLIB的开发包,利用这个开发包就可以实现基于类的代理设计模式。
CGLIB是一个第三方的程序包,需要单独在eclipse中进行配置。然后来编写一个程序类,这个类不实现任何接口:
class Message {
public void send() {
System.out.println("发送消息!");
}
}
利用CGLIB编写代理类,但这个代理类需要做一个明确,此时相当于使用了类的形式实现了代理设计的处理所以该代理设计需要通过CGLIB生成代理对象,定义一个代理类:
class ZIJUNProxy implements MethodInterceptor{ //拦截器
private Object target; //保存真实主题对象
public ZIJUNProxy(Object target) {
this.target = target;
}
public boolean connect() {
System.out.println("{消息代理处理}通道连接");
return true;
}
public void close() {
System.out.println("{消息代理处理}关闭通道");
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
Object returnData = null;
if(this.connect()) {
returnData = method.invoke(this.target, args);
this.close();
}
return returnData;
}
}
此时如果想创建代理类对象,就必须进行一系列的CGLIB处理:
public class TestDemo {
public static void main(String[] args) throws Exception {
Message realObject = new Message(); //真实主题对象
Enhancer enhancer = new Enhancer(); //负责代理操作的程序类
enhancer.setSuperclass(realObject.getClass()); //假定一个父类
enhancer.setCallback(new ZIJUNProxy(realObject)); //设置代理类
Message proxyObject = (Message)enhancer.create(); //创建代理类对象
proxyObject.send();
}
}
这里要注意可能运行后会报错,是因为Java项目没有使用Maven管理,需要手动添加asm的jar包,一般来说asm和cglib是要匹配的,所以不知道怎么匹配的话两个jar都添加最新版的就可以了。
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by net.sf.cglib.core.ReflectUtils$1 (file:/D:/Eclipse/My_Demo/lib/cglib-3.3.0.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of net.sf.cglib.core.ReflectUtils$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
{消息代理处理}通道连接
发送消息!
{消息代理处理}关闭通道
在进行代理设计模式的定义时除了可以使用接口之外,也可以不受接口的限制而实现基于类的代理设计,但是如果从正常的设计角度来讲,强烈建议使用基于接口的设计会比较合理。
反射与Annotation
从JDK1.5之后,Java开发提供了Annotation支持,这种技术支持为项目编写带来了新的模型,而后经过了十多年的发展,Annotation技术得到了非常广泛的应用,并且在所有项目开发之中都会存在,在之前的一篇文章中整理了Annotation的知识点。
反射取得Annotation信息
在进行类或方法定义的时候,都可以使用一系列的Annotation进行声明,如果想要获取Annotation的信息,就可以直接通过反射来完成。在java.lang.reflect里面有一个AccessibleObject类,在这个类中提供有获取Annotation的方法:
- 获取全部Annotation:public Annotation[] getAnnotations();
- 获取指定Annotation:public <T extends Annotation> T getAnnotation(Class<T> annotationClass);
@FunctionalInterface
@Deprecated
interface IMessage{ //有两个Annotation
public void send(String msg);
}
@SuppressWarnings("serial")
class MessageImpl implements IMessage, Serializable{
@Override
public void send(String msg) {
System.out.println("{消息发送}" + msg);
}
}
这个时候我们写一个接口并加上两个Annotation,来看一下能不能获取到接口上的Annotation信息和类上的Annotation信息:
public class TestDemo {
public static void main(String[] args) throws Exception {
//获取接口上的Annotation信息
{
Annotation[] annotations = IMessage.class.getAnnotations();
for(Annotation temp : annotations) {
System.out.println(temp);
}
}
System.out.println("**************************");
//获取子类上的Annotation信息
{
Annotation[] annotations = MessageImpl.class.getAnnotations();
for(Annotation temp : annotations) {
System.out.println(temp);
}
}
System.out.println("**************************");
//获取方法上的Annotation信息
{
Method method = MessageImpl.class.getDeclaredMethod("send", String.class);
Annotation[] annotations = method.getAnnotations();
for(Annotation temp : annotations) {
System.out.println(temp);
}
}
}
}
@java.lang.FunctionalInterface()
@java.lang.Deprecated(forRemoval=false, since="")
**************************
**************************
这时候就会发现有的可以获取有的不行,不同的Annotation有它的存在范围,我们来对比一下两个Annotation:
- @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();
}
自定义Annotation
现在已经清楚了Annotation的获取以及Annotation的运行策略,那么如何可以实现自定义的Annotation呢?为此在Java中提供有新的语法“@interface”来定义Annotation:
@Retention(RetentionPolicy.RUNTIME) //定义Annotation的运行策略
@interface DefaultAnnotation{ //自定义的Annotation
public String title(); //获取数据
public String url() default "www.csdn.com"; //获取数据,默认值
}
class Message{
@DefaultAnnotation(title = "zijun")
public void send(String msg) {
System.out.println(msg);
}
}
这是我们就定义了一个Annotation并且可以找到它:
public class TestDemo {
public static void main(String[] args) throws Exception {
Method method = Message.class.getMethod("send", String.class);
DefaultAnnotation anno = method.getAnnotation(DefaultAnnotation.class);
System.out.println(anno.title());
System.out.println(anno.url());
}
}
zijun
www.csdn.com
或者直接可以发送消息:
public class TestDemo {
public static void main(String[] args) throws Exception {
Method method = Message.class.getMethod("send", String.class);
DefaultAnnotation anno = method.getAnnotation(DefaultAnnotation.class);
String msg = anno.title() + "(" + anno.url() + ")";
method.invoke(Message.class.getDeclaredConstructor().newInstance(), msg);
}
}
zijun(www.csdn.com)
使用Annotation做大的特点是可以结合反射机制实现程序的处理。
工厂设计模式与Annotation整合
现在已经清楚了Annotation的整体作用,但在开发中Annotation到底可以干什么?下面结合工厂设计模式来应用Annotation:
interface IMessage{
public void send(String msg);
}
class MessageImpl implements IMessage{
@Override
public void send(String msg) {
System.out.println("{消息发送}" + msg);
}
}
class MessageProxy implements InvocationHandler{
private Object target;
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if(this.connect()) {
return method.invoke(this.target, args);
}else {
throw new Exception("{err}消息无法进行发送");
}
}finally {
this.close();
}
}
public boolean connect() {
System.out.println("{代理操作}消息发送通道链接");
return true;
}
public void close() {
System.out.println("{代理操作}消息发送通道关闭");
}
}
class Factory{
private Factory() {}
public static <T> T getInstance(Class<T> clazz) { //返回一个实例化操作的对象
try {
return (T)new MessageProxy().bind(clazz.getDeclaredConstructor().newInstance());
} catch (InstantiationException |
IllegalAccessException |IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
IMessage msg = Factory.getInstance(MessageImpl.class);
msg.send("www.csdn.com");
}
}
这就是一个非常典型的反射+工厂类实现代理设计模式的代码,但这样是体现不出Annotation的特色的,所以再加一些东西:
@UseMessage(clazz = MessageImpl.class)
class MessageService{
private IMessage message;
public MessageService() {
UseMessage use = MessageService.class.getAnnotation(UseMessage.class);
this.message = (IMessage)Factory.getInstance(use.clazz());
}
public void send(String msg) {
this.message.send(msg);
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface UseMessage{
public Class<?> clazz();
}
这时候就利用了Annotation实现了类的使用,我们来试一下:
public class TestDemo {
public static void main(String[] args) throws Exception {
MessageService messageService = new MessageService();
messageService.send("www.csdn.com");
}
}
{代理操作}消息发送通道链接
{消息发送}www.csdn.com
{代理操作}消息发送通道关闭
现在假如我们需要更换一下实现子类,比如将MessageImpl换为NetMessageImpl,这是只需要将MessageService上的Annotation的clazz更换为NetMessageImpl.class就可以了。
现在可以通过注解来控制程序的执行,这就是Annotation的特征。
反射的知识点就先整理到这里,下次我们将整理List集合、Set集合、Map集合以及一些集合工具类,我们下次见👋