日期:2025/04/04 03:30来源:未知 人气:53
说起反射,学习过Java的小伙伴并不会陌生,反射是Java基础篇里面相对较难的一个知识点,不少同学在一说起这个知识点的时候,更是望而生畏。总得来说了,反射主要难就难在它的逆向思维让习惯了正向思维的咱们一时间适应不过来,就像你习惯了原始的正向思维,比如new一个对象,然后使用对象引用调用当前对象的set方法给属性赋值,或者调用当前对象的方法完成某个功能一样,一切逻辑清晰毫无难度。或者你习惯了最原始的面向对象编程,突然让你接受Stream流函数式编程一般。
反射是一门很厉害的技术,它是一门从宏观上解决问题的技术,比如咱们学习过的大多数框架,很多都是使用反射加上设计模式实现的。大家之所以对它很陌生原因还是在于自己使用它的场景实在是太少了,包括进入公司大多数也是基于业务上的开发,可能很少再像刚开始学习的时候那么热心专注技术的底层了。所以呢,在本次,反射的基础知识我也不给大家讲解了,主要带来几个使用反射的场景,让大家加深对这门技术的理解和体会。
本文内容较长,包含以下内容:
一、什么是反射?
二、反射的使用场景
2.1 系统交互问题
2.2 命令模式或者策略模式改造
2.3 简单的IOC容器
三、总结
一、什么是反射?
反射的概念比较抽象,你可以这样去理解。在Java面向对象中,类是Java的基本单位,通过类可以产生无数的对象,而类是什么,类是抽取一类事务的共性进而合并起来的一个模板。比如一个学生类,设计这个类时需要抽取学生这个主体的公共信息,比如每个学生都有学号、姓名等属性,再比如学生都有完成作业方法,咱们将学生这一类主体经过抽象设计如下的一个学生类:
package com.ignorance.reflect.student;
public class Student {
private Integer no;
private String name;
private String gender;
private String college;
private String grade;
private String tel;
public Student() {
}
public Student(Integer no, String name, String gender, String college, String grade, String tel) {
this.no = no;
this.name = name;
this.gender = gender;
this.college = college;
this.grade = grade;
this.tel = tel;
}
public Integer getNo() {
return no;
}
public void setNo(Integer no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getCollege() {
return college;
}
public void setCollege(String college) {
this.college = college;
}
public String getGrade() {
return grade;
}
public void setGrade(String grade) {
this.grade = grade;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
public void finishHomeWork(String teacherName,String subject){
System.out.println(this.name + "正在完成" + teacherName +"布置的" + subject + "学科的作业");
}
@Override
public String toString() {
return "Student{" +
"no=" + no +
", name='" + name + '\'' +
", gender='" + gender + '\'' +
", college='" + college + '\'' +
", grade='" + grade + '\'' +
", tel='" + tel + '\'' +
'}';
}
}
这个时候类是没有任何使用意义的,类就相当于一个模板,比如制造手机需要有当前手机的设计模型图,而真正卖给用户的肯定不是这个模型图,真正产生价值的一定是通过这个模型生产出来的实体,而这个实体也叫作创建对象,也就是咱们使用构造器创建出来的真正对象实体。
下面的代码我相信对大家再简单不过了,通过学生类创建出一个真正的学生实体对象,然后分别给当前对象属性赋值,然后最后调用finishHomeWork方法完成其对应的功能:
@Test
public void test01(){
Student student = new Student(1001,"李白","男","清华大学","计算机22-1","15512345696");
student.finishHomeWork("韩信","Java");
}
这些代码不用解释,因为对我们来说简直太简单不过了,大家经常在使用,所以不用一一解释。
那么我们使用反射来完成这几行代码对应的功能呢?反射这门技术很简单,它是将咱们所有的类看作对象,那么JVM在加载所有的.class字节码文件的时候,会同时给提供一个字节码对象,简单来说,Class类是所有类的模板类,而我们Java中所有的类就是通过这个Class类衍生出来的对象。咱们不妨思考一下,既然Class类是所有类的公共模板类,那么它肯定抽象了所有类的公共特性,那咱们每个类都有什么公共属性呢?所有类肯定都有成员变量、方法以及构造器、方法参数以及父类等等信息,那么我们在得到字节码对象Class对象时,就可以动态的去获取当前类的各种信息。例如上面的代码我们使用反射的思想完成:
@Test
public void test02() throws Exception{
//获取字节码对象
Class clazz = Class.forName("com.ignorance.reflect.student.Student");
//通过无参构造器创建对应的Student对象
Object instance = clazz.newInstance();
//通过字节码对象获取到no属性
Field no = clazz.getDeclaredField("no");
//因为no属性时private私有的,需要通过暴力反射才能访问
no.setAccessible(true);
//给no属性赋值
no.set(instance,1001);
//通过字节码对象获取到name属性
Field name = clazz.getDeclaredField("name");
//因为name属性时private私有的,需要通过暴力反射才能访问
name.setAccessible(true);
//给name属性赋值
name.set(instance,"李白");
//通过字节码对象获取到gender属性
Field gender = clazz.getDeclaredField("gender");
//因为gender属性时private私有的,需要通过暴力反射才能访问
gender.setAccessible(true);
//给gender属性赋值
gender.set(instance,"男");
//通过字节码对象获取到college属性
Field college = clazz.getDeclaredField("college");
//因为college属性时private私有的,需要通过暴力反射才能访问
college.setAccessible(true);
//给college属性赋值
college.set(instance,"北京大学");
//通过字节码对象获取到grade属性
Field grade = clazz.getDeclaredField("grade");
//因为grade属性时private私有的,需要通过暴力反射才能访问
grade.setAccessible(true);
//给grade属性赋值
grade.set(instance,"计算机22-1");
//通过字节码对象获取到no属性
Field tel = clazz.getDeclaredField("tel");
//因为tel属性时private私有的,需要通过暴力反射才能访问
tel.setAccessible(true);
//给tel属性赋值
tel.set(instance,"15512345696");
//获取finishHomeWork方法
Method finishHomeWork = clazz.getDeclaredMethod("finishHomeWork", String.class, String.class);
//方法调用
finishHomeWork.invoke(instance,"韩信","Java");
}
以上是通过无参构造器创建实例,然后调用set方法给对应的属性赋值,最后再调用finishHomeWork方法。就类似先通过无参构造器new对象,然后分别调用每个属性的set方法给属性赋值,最后调用目标方法finishHomeWork完成功能一样。我们知道通过给属性赋值还可以通过构造器,所以在构造Student类时,我们还声明了对应的有参构造方法。那么接下来我们通过有参构造完成:
@Test
public void test03() throws Exception{
//获取字节码对象
Class clazz = Class.forName("com.ignorance.reflect.student.Student");
//获取有参构造器
Constructor constructor = clazz.getDeclaredConstructor(Integer.class, String.class, String.class, String.class, String.class, String.class);
//通过有参构造器创建对象以及给属性成员赋值
Object instance = constructor.newInstance(1001,"李白","男","清华大学","计算机22-1","15512345696");
//通过字节码对象获取finishHomeWork方法
Method targetMethod = clazz.getDeclaredMethod("finishHomeWork", String.class, String.class);
//调用finishHomeWork方法
targetMethod.invoke(instance,"韩信","Java");
}
二、反射的使用场景
其实吧,反射技术并不难。我们之所以觉得陌生或者难是因为我们自己遇到的场景太少了,对于任何一门技术来说,只要使用的场景比较多,练得次数达到一定的程度,其实没有一定难度可言。为了加强大家对反射的掌握,接下来了,我们讲解几个会用到反射的场景,帮助大家更好的学会这门比较厉害且抽象的技术。
2.1**系统交互问题**
做过开发的同学应该经常遇到过这样的场景,有时候开发一个系统往往还需要需要跟其它的系统一起耦合,协同在一起完成工作。比如开发一个交易系统,肯定离不开客户信息,此时我们还需要客户系统提供第三方接口,完成两个系统的数据交互。而往往这两个系统的字段不能够相互对应,在系统进行交互的时候会给我们带来很多麻烦。所以这个时候多个系统进行相互调用时会形成一份规定的接口文档,比如对学生信息在我们本地系统存储的字段就如下所示:
private Integer no;
private String name;
private String gender;
private String college;
private String grade;
private String tel;
但是这个时候接口调用文档这么写的:
这是一份很常见也很简单的系统对接文档,因为每个系统都有自己规定的字段,如果每个系统都自己定义一份,就会让咱们交互起来特别混乱且复杂,所以为了优化每个系统的交互性,我们需要按照接口文档为调用系统提供一个单独的接口。
我们为了完成这些工作,我们需要从咱们本地数据库查出数据,在这里咱们就不创建数据库了,使用工具类来模仿咱们的数据库操作:
package com.ignorance.reflect.utils;
import java.util.ArrayList;
import java.util.List;
public class DataUtils
private List
public DataUtils() {
this.dataList = new ArrayList<>();
}
public void add(T elementData){
dataList.add(elementData);
}
public List
return this.dataList;
}
}
package com.ignorance.reflect.utils;
import com.ignorance.reflect.student.Student;
public class StudentDataUtils extends DataUtils
}
那么现在我们初始化本地数据源,且查询出本地数据:
package com.ignorance.reflect.student.test;
import com.ignorance.reflect.student.Student;
import com.ignorance.reflect.utils.StudentDataUtils;
import org.junit.Before;
import org.junit.Test;
public class Demo01Test {
private StudentDataUtils studentDataUtils;
@Before
public void init(){
studentDataUtils = new StudentDataUtils();
studentDataUtils.add(new Student(1001,"李白","男","北京大学","计算机15-1","12345678910"));
studentDataUtils.add(new Student(1002,"赵云","男","清华大学","软件17-3","15538912345"));
studentDataUtils.add(new Student(1003,"韩信","男","清华大学","计算机15-4","18293123456"));
studentDataUtils.add(new Student(1004,"貂蝉","女","浙江大学","经管17-4","17634987654"));
}
@Test
public void test01(){
studentDataUtils.getDataList().forEach(System.out::println);
}
}
这个时候在我们本地系统交互是没有任何问题的,但是如果我们系统的是数据需要跟其他系统打交道就比较麻烦了,因为我们在设计自己的系统时,完全是为了咱们系统的方便而设计的,但是其他系统要拿咱们的数据就会很麻烦,第一我们本地的字段映射就会是个很麻烦的问题,就比如我们这边学生系统就用来维护学生信息,另外有个学生考试系统,它需要依赖于咱们学生系统的学生信息。比如我们这边学号字段设计的no,它们那边设计的id,这样每个系统完全按照自己的意愿设计,就会给系统与系统之间的数据同步带来巨大的麻烦,所以呢,这时候为了系统之间的协调,会在两者之间引入接口规范。如果自己系统调用本身,那无所谓,但是一旦涉及到两个系统的通信,就必须严格遵守接口文档,不然后面就是一团乱。
像我们学生系统,查询学生信息,还需要给考试系统提供一个第三方调用接口,比如发送一个json串,但是这个时候字段no就要映射成studentId了、name则映射为studentName了。这个时候我相信其他同学说这还不简单,直接这样写不就可以了吗?
第一步:先根据接口文档创建一个统一字段的实体类:
package com.ignorance.reflect.third;
public class StudentThird {
private Integer studentId;
private String studentName;
private String sex;
private String school;
private String studentGrade;
private String phone;
public StudentThird() {
}
public StudentThird(Integer studentId, String studentName, String sex, String school, String studentGrade, String phone) {
this.studentId = studentId;
this.studentName = studentName;
this.sex = sex;
this.school = school;
this.studentGrade = studentGrade;
this.phone = phone;
}
public Integer getStudentId() {
return studentId;
}
public void setStudentId(Integer studentId) {
this.studentId = studentId;
}
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
public String getStudentGrade() {
return studentGrade;
}
public void setStudentGrade(String studentGrade) {
this.studentGrade = studentGrade;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "StudentThird{" +
"studentId=" + studentId +
", studentName='" + studentName + '\'' +
", sex='" + sex + '\'' +
", school='" + school + '\'' +
", studentGrade='" + studentGrade + '\'' +
", phone='" + phone + '\'' +
'}';
}
}
第二步:建立一个第三方提供类,将原来的数据使用新的实体填充:
package com.ignorance.reflect.third;
import com.ignorance.reflect.student.Student;
import com.ignorance.reflect.utils.StudentDataUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class ThirdInterUtils {
public static void main(String[] args) {
thirdInter001();
}
public static void thirdInter001(){
StudentDataUtils studentDataUtils = new StudentDataUtils();
studentDataUtils.add(new Student(1001,"李白","男","北京大学","计算机15-1","12345678910"));
studentDataUtils.add(new Student(1002,"赵云","男","清华大学","软件17-3","15538912345"));
studentDataUtils.add(new Student(1003,"韩信","男","清华大学","计算机15-4","18293123456"));
studentDataUtils.add(new Student(1004,"貂蝉","女","浙江大学","经管17-4","17634987654"));
System.out.println("【原系统字段数据】:");
studentDataUtils.getDataList().forEach(System.out::println);
System.out.println("【开始组装接口统一数据】:");
List
List
StudentThird studentThird = new StudentThird();
studentThird.setStudentId(o.getNo());
studentThird.setStudentName(o.getName());
studentThird.setSex(o.getGender());
studentThird.setStudentGrade(o.getGrade());
studentThird.setSchool(o.getGrade());
studentThird.setPhone(o.getTel());
return studentThird;
}).collect(Collectors.toList());
System.out.println("【组装字段完成】:" );
thirdList.forEach(System.out::println);
}
}
运行结果如下:
这种情况无疑是可以实现的,也比较简单,但是从一个好的系统设计而言显然是不合格的。第一就是你除了建本地实体类外,还需要建立一个第三方交互的实体类,有的同学可能就会这么说,我直接建立第三方实体类作为本地和其他交互的不就行了,但是开发中你的接口很大可能是在接口文档之前开发的,而且后面第三方交互也很大可能是后面新的需求加的,需求是变化多端的,你永远无法预料到下一个需求是什么。如果此时拿到交互文档后,你再去调整实体类那就很麻烦,且改动的地方太多,你其他地方用到本地实体逻辑的地方全部需要大改,包括数据库映射。这显然是一种很糟糕的想法,就像本次改了,下次又发生变化了呢?
以上这种情况优点是简单比较好理解,缺点依赖性太重,实体类会急剧暴增,比如本地有个订单类,本地和第三方都要各自维护一个实体类,然后写一个方法组装新的数据,如下所示:
接口规范如下:
为了完成第三方数据字段映射,咱们又要重复以上步骤:
package com.ignorance.reflect.utils;
import com.ignorance.reflect.student.Order;
public class OrderDataUtils extends DataUtils
}
package com.ignorance.reflect.third;
import java.math.BigDecimal;
public class OrderThird {
private String id;
private String custName;
private Integer num;
private BigDecimal balance;
private String transDate;
public OrderThird() {
}
public OrderThird(String id, String custName, Integer num, BigDecimal balance, String transDate) {
this.id = id;
this.custName = custName;
this.num = num;
this.balance = balance;
this.transDate = transDate;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getCustName() {
return custName;
}
public void setCustName(String custName) {
this.custName = custName;
}
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
public BigDecimal getBalance() {
return balance;
}
public void setBalance(BigDecimal balance) {
this.balance = balance;
}
public String getTransDate() {
return transDate;
}
public void setTransDate(String transDate) {
this.transDate = transDate;
}
@Override
public String toString() {
return "OrderThird{" +
"id='" + id + '\'' +
", custName='" + custName + '\'' +
", num=" + num +
", balance=" + balance +
", transDate='" + transDate + '\'' +
'}';
}
}
下一步呢,又需要在第三方暴露接口中组装数据:
public static void thirdInter002(){
OrderDataUtils orderDataUtils = new OrderDataUtils();
orderDataUtils.add(new Order("20221127001","Lucy",10,new BigDecimal("100"),"2022-10-01"));
orderDataUtils.add(new Order("20221127002","Jack",5,new BigDecimal("8888"),"2022-10-05"));
orderDataUtils.add(new Order("20221127003","Sally",9,new BigDecimal("666"),"2022-07-17"));
orderDataUtils.add(new Order("20221127004","Tom",7,new BigDecimal("400"),"2022-10-09"));
System.out.println("【原系统字段数据】:");
orderDataUtils.getDataList().forEach(System.out::println);
System.out.println("【开始组装接口统一数据】:");
List
List
OrderThird orderThird = new OrderThird();
; orderThird.setId(o.getOrderId());
orderThird.setCustName(o.getName());
orderThird.setNum(o.getCount());
orderThird.setBalance(o.getAmount());
orderThird.setTransDate(o.getTransDate());
return orderThird;
}).collect(Collectors.toList());
System.out.println("【组装字段完成】:" );
thirdList.forEach(System.out::println);
}
运行结果如下:
之所以给大家重复写两次,相信大家也体会到了,这样实在是太麻烦了,每次提供一个接口给其它后台系统,咱们都要做这么一堆没有任何技术含量的工作,咱们这字段少还好,但是工作中字段可能都是几十个或者上百,想一想这样恶不恶心。我们的大部分时间都用在字段组装上了,这是一件没有任何技术含量且很花费时间的事情。所以呢,我们会发现这几个操作步骤都大同小异,都是将一个字段是映射另外一个名字,只是仅仅因为每个实体类的名字不一样而已,所以我们可以考虑使用反射来大幅度降低咱们的工作压力。
第一步:创建注解类 。
package com.ignorance.annotation;
import java.lang.annotation.*;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ThirdField {
String field() default "";
}
第二步:使用注解标记实体类。
package com.ignorance.reflect.student;
import com.ignorance.annotation.ThirdField;
public class Student {
@ThirdField(field = "studentId")
private Integer no;
@ThirdField(field = "studentName")
private String name;
@ThirdField(field = "sex")
private String gender;
@ThirdField(field = "school")
private String college;
@ThirdField(field = "studentGrade")
private String grade;
@ThirdField(field = "phone")
private String tel;
public Student() {
}
public Student(Integer no, String name, String gender, String college, String grade, String tel) {
this.no = no;
this.name = name;
this.gender = gender;
this.college = college;
this.grade = grade;
this.tel = tel;
}
public Integer getNo() {
return no;
}
public void setNo(Integer no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getCollege() {
return college;
}
public void setCollege(String college) {
this.college = college;
}
public String getGrade() {
return grade;
}
public void setGrade(String grade) {
this.grade = grade;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
public void finishHomeWork(String teacherName,String subject){
System.out.println(this.name + "正在完成" + teacherName +"布置的" + subject + "学科的作业");
}
@Override
public String toString() {
return "Student{" +
"no=" + no +
", name='" + name + '\'' +
", gender='" + gender + '\'' +
", college='" + college + '\'' +
", grade='" + grade + '\'' +
", tel='" + tel + '\'' +
'}';
}
}
第三步:创建第三方接口映射类。
package com.ignorance.reflect.third;
import com.ignorance.annotation.ThirdField;
import com.ignorance.reflect.student.Student;
import com.ignorance.reflect.utils.StudentDataUtils;
import org.junit.Test;
import sun.security.timestamp.TSRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;
public class ThirdUtils
public Map<String,Object> identityField(T instance) {
Map<String,Object> map = new HashMap<>();
//获取本地实体class字节码对象
Class<?> clazz = instance.getClass();
//获取本地实体所有的属性
Field[] fields = clazz.getDeclaredFields();
//获取每个本地属性上面的ThirdField注解
Arrays.stream(fields).forEach(e -> {
ThirdField thildField = e.getAnnotation(ThirdField.class);
String thirdFieldValue = thildField.field();
try {
e.setAccessible(true);
map.put(thirdFieldValue,e.get(instance));
} catch (IllegalAccessException ex) {
ex.printStackTrace();
}
});
return map;
}
@Test
public void test01() {
StudentDataUtils studentDataUtils = new StudentDataUtils();
studentDataUtils.add(new Student(1001,"李白","男","北京大学","计算机15-1","12345678910"));
studentDataUtils.add(new Student(1002,"赵云","男","清华大学","软件17-3","15538912345"));
studentDataUtils.add(new Student(1003,"韩信","男","清华大学","计算机15-4","18293123456"));
studentDataUtils.add(new Student(1004,"貂蝉","女","浙江大学","经管17-4","17634987654"));
System.out.println("【原系统字段数据】:");
studentDataUtils.getDataList().forEach(System.out::println);
System.out.println("【开始组装接口统一数据】:");
ThirdUtils
List<Map<String, Object>> thirdList = studentDataUtils.getDataList().stream().map(thirdUtils::identityField).collect(Collectors.toList());
System.out.println("【字段组装完成】:");
thirdList.forEach(System.out::println);
}
}
运行结果如下:
我们使用这种思维处理后,对于订单接口,我们再也不需要重复写了,只需要在本地系统实体类做好映射注解,直接调用参入参数即可:
package com.ignorance.reflect.student;
import com.ignorance.annotation.ThirdField;
import java.math.BigDecimal;
public class Order {
@ThirdField(field = "id")
private String orderId;
@ThirdField(field = "custName")
private String name;
@ThirdField(field = "num")
private Integer count;
@ThirdField(field = "balance")
private BigDecimal amount;
@ThirdField(field = "transDate")
private String transDate;
public Order(String orderId, String name, Integer count, BigDecimal amount, String transDate) {
this.orderId = orderId;
this.name = name;
this.count = count;
this.amount = amount;
this.transDate = transDate;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public String getTransDate() {
return transDate;
}
public void setTransDate(String transDate) {
this.transDate = transDate;
}
@Override
public String toString() {
return "Order{" +
"orderId='" + orderId + '\'' +
", name='" + name + '\'' +
", count=" + count +
", amount=" + amount +
", transDate='" + transDate + '\'' +
'}';
}
}
调用接口传入参数即可:
@Test
public void test02() {
OrderDataUtils orderDataUtils = new OrderDataUtils();
orderDataUtils.add(new Order("20221127001","Lucy",10,new BigDecimal("100"),"2022-10-01"));
orderDataUtils.add(new Order("20221127002","Jack",5,new BigDecimal("8888"),"2022-10-05"));
orderDataUtils.add(new Order("20221127003","Sally",9,new BigDecimal("666"),"2022-07-17"));
orderDataUtils.add(new Order("20221127004","Tom",7,new BigDecimal("400"),"2022-10-09"));
orderDataUtils.getDataList().forEach(System.out::println);
System.out.println("【开始组装接口统一数据】:");
ThirdUtils
List<Map<String, Object>> thirdList = orderDataUtils.getDataList().stream().map(thirdUtils::identityField).collect(Collectors.toList());
System.out.println("【字段组装完成】:");
thirdList.forEach(System.out::println);
}
运行结果如下:
2.2**命令模式或者策略模式改造**
在现实生活中存在这么一个需求,一个中介公司需要根据顾客的要求给他们推送对应的房源信息。比如:张三租房,他的要求是1000-2000左右,李四是1500-3500之间,王五是2500-5000之间,赵六是3800-7000之间,田七是2500-4000之内。这个时候作为中介公司,手里现在没有对应的房源,需要在有房源时根据顾客的要求将信息推送给对应的顾客。比如现在有一套房,它的租金为3500,这个时候中介公司需要给李四、王五以及田七发送信息。
如果是你,作为开发者的角度,会怎么来进行设计呢?我相信学过设计模式的同学立马就能想命令模式,但是对咱们这种情况使用命令模式肯定是可以,但是会让一个很简单的事情变得很复杂,你需要建立一系列的类,比如命令类,命令的子类,命令与被调用者的聚合关系。命令模式往往更适合于那些依赖性比较强的且系统规模稍大的系统,能起到很好的解耦作用,像咱们这么一个场景,反而会变得更加复杂。
第二种,有同学肯定也会想到策略模式,我让所有顾客实现同一个接口,然后中介公司作为一个类,封装一个通知方法,将被调用者也就是顾客聚合在中介公司这个类里面,使用多态的方式,外部new谁我就调用谁,但这时候也有很大的问题,我们都说设计模式的思想应该符合开闭原则。对被调用者只有一个那肯定可以,但是这个时候我们的被调用者可能为多个,比如刚才房价3500,有三个接受者都会被调用,那此时肯定是不可以使用策略模式的。
这个时候,我们同样可以使用反射来解决。
第一步:构建每个顾客类。
package com.ignorance.emp;
public abstract class Customer {
public String name;
public void receive(String message){
System.out.println(message);
}
}
package com.ignorance.emp;
public class ZhangSan extends Customer{
public ZhangSan() {
this.name = "张三";
}
}
package com.ignorance.emp;
public class LiSi extends Customer{
public LiSi() {
this.name = "李四";
}
}
package com.ignorance.emp;
public class WangWu extends Customer{
public WangWu() {
this.name = "王五";
}
}
package com.ignorance.emp;
public class ZhaoLiu extends Customer {
public ZhaoLiu() {
this.name = "赵六";
}
}
package com.ignorance.emp;
public class TianQi extends Customer{
public TianQi() {
this.name = "田七";
}
}
第二步:创建一个枚举类,用于维护达到每个条件对应的目标调用类。
package com.ignorance.emp;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public enum InvokeEnums {
NOTIFY_MESSAGE_ZS(1000.0,2000.0,"com.ignorance.emp.ZhangSan"),
NOTIFY_MESSAGE_LS(1500.0,3500.0,"com.ignorance.emp.LiSi"),
NOTIFY_MESSAGE_WW(2500.0,5000.0,"com.ignorance.emp.WangWu"),
NOTIFY_MESSAGE_ZL(3800.0,7000.0,"com.ignorance.emp.ZhaoLiu"),
NOTIFY_MESSAGE_TQ(2500.0,4000.0,"com.ignorance.emp.TianQi");
private double min;
private double max;
private String invokeTargetPath;
InvokeEnums(double min, double max, String invokeTargetPath) {
this.min = min;
this.max = max;
this.invokeTargetPath = invokeTargetPath;
}
//根据租金找到合适的被调用者
public static List
return Arrays.stream(InvokeEnums.values()).filter(o -> money >= o.min && money <= o.max).collect(Collectors.toList());
}
}
第三步:创建中介类 。
package com.ignorance.emp;
import java.lang.reflect.Field;
import java.util.List;
public class Intermediary {
public static void sendMessage(double money) {
List
if (targetInvokeList == null || targetInvokeList.size() == 0){
return;
}
targetInvokeList.forEach(e -> {
try {
Class<? extends Customer> classzz = (Class<? extends Customer>)Class.forName(e.getInvokeTargetPath());
Customer targetCustomerInvoker = classzz.newInstance();
Field targetCustomerNameField = classzz.getField("name");
targetCustomerNameField.setAccessible(true);
Object targetName = targetCustomerNameField.get(targetCustomerInvoker);
String message = "你好," + targetName + "先生,现有房产租金为:" + money + "已准备开始出租,请及时关注哦!";
targetCustomerInvoker.receive(message);
} catch (Exception ex) {
ex.printStackTrace();
}
});
}
}
第四步:测试 。
package com.ignorance.emp;
import org.junit.Test;
public class Client {
@Test
public void test01(){
Intermediary.sendMessage(3500);
}
}
运行结果:
2.3**简单的IOC容器**
学过Java的小伙伴肯定知道Spring,那么一提到Spring那么必不可少的就是Spring的核心,也就是IOC容器。咱们都知道IOC最主要的作用是降低类与类之间的强耦合关系,将创建对象的权利交给咱们的IOC容器,进而进行统一管理。我们同样可以使用反射模拟IOC容器的创建。下面是咱们常见的几个业务类:
AController:
package com.ignorance.ioc.controller;
import com.ignorance.ioc.service.AService;
public class AController {
private AService aService;
public void show(){
aService.say();
}
}
AService接口:
package com.ignorance.ioc.service;
public interface AService {
public void say();
}
AServiceImpl实现类 :
package com.ignorance.ioc.service.impl;
import com.ignorance.ioc.dao.ADao;
import com.ignorance.ioc.service.AService;
import sun.dc.pr.PRError;
public class AServiceImpl implements AService {
private ADao aDao;
@Override
public void say() {
aDao.sayHello();
}
}
ADao接口 :
package com.ignorance.ioc.dao;
public interface ADao {
public void sayHello();
}
ADaoImpl实现类 :
package com.ignorance.ioc.dao.impl;
import com.ignorance.ioc.dao.ADao;
public class ADaoImpl implements ADao {
@Override
public void sayHello() {
System.out.println("Hello,SpringIOC!");
}
}
这是我们常见的三层架构,如果此时调用Controller肯定会有很大的问题,咱们AController需要依赖Aservice、AService同样也需要依赖ADao。因为这几个被依赖的属性没有被初始化,这个时候一定会报空指针。
咱们不妨调用一下试试:
package com.ignorance.ioc.test;
import com.ignorance.ioc.controller.AController;
import org.junit.Test;
public class IOCTest {
@Test
public void test01(){
new AController().show();
}
}
运行结果:
这个错误很正常,也很简单也能看出来,大家现在直接想到的是直接在对应的类里面初始化对应的属性不就行了吗?这当然可以,没有任何问题,但是这样彼此之间的耦合度会大幅度提高,在咱们真实开发中,这些业务类肯定会出现多次引用,后面万一被引用的类发生了改变,那么咱们所有的类是不是都需要全部改一遍,这显然对咱们开发人员是巨大的灾难。所以呢,就产生了IOC思想,把咱们的对象依赖关系交给容器管理,进而达到大幅度降低耦合的目的。
第一步:创建IOC核心配置文件spring.xml文件,用于维护IOC容器中的对象以及对象之间的依赖关系。
第二步:创建BeanFactory接口 。
package com.ignorance.ioc;
public interface BeanFactory {
Object getBean(String iocKey);
}
第三步:创建IOC核心类ClassPathXmlApplicationContext类 。
package com.ignorance.ioc;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ClassPathXmlApplicationContext implements BeanFactory {
//IOC核心容器为一个HashMap
private Map<String,Object> beanMap;
@Override
public Object getBean(String iocKey) {
return beanMap.get(iocKey);
}
public ClassPathXmlApplicationContext(String iocFilePath) throws Exception {
beanMap = new HashMap<>();
//通过dom4j解析xml文件
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(this.getClass().getClassLoader().getResourceAsStream(iocFilePath));
//获取根标签beans
Element rootElement = document.getRootElement();
//获取根标签下面的所有bean标签,一个bean标签对应一个对象
/**
*/
List
for (int i = 0;i < beanList.size();i++){
Element element = beanList.get(i);
//获取每个bean标签的id属性值
String id = element.attribute("id").getData().toString();
//获取每个bean标签的class属性值
String classPath = element.attribute("class").getData().toString();
//通过class属性的class引用,使用反射创建出对应的对象
Object beanInstance = Class.forName(classPath).newInstance();
//以id值为key,创建出的对象为value加入到beanMap中
beanMap.put(id,beanInstance);
}
/**
*/
for (int i = 0;i < beanList.size();i++){
Element bean = beanList.get(i);
String id = bean.attribute("id").getData().toString();
String classPath = bean.attribute("class").getData().toString();
Object beanInstance = beanMap.get(id);
Class beanClazz = Class.forName(classPath);
List
if (propertyList == null || propertyList.size() == 0){
continue;
}
for (Element property : propertyList){
//获取每个bean下面的property标签,存在property标签就表示某个bean具有属性需要初始化
//获取name属性
String propertyName = property.attribute("name").getData().toString();
//获取ref属性
String ref = property.attribute("ref").getData().toString();
//通过name值在对应的类中查找对应的属性
Field propertyField = beanClazz.getDeclaredField(propertyName);
//找到属性后进行暴力反射
propertyField.setAccessible(true);
//给属性进行位置,通过ref去IOC容器取出对应的对象,然后赋值给实体类所依赖的属性
propertyField.set(beanInstance,beanMap.get(ref));
}
}
}
}
第四步:使用IOC思想完成调用 。
@Test
public void test02() throws Exception {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring.xml");
Object instance = beanFactory.getBean("aController");
if (instance instanceof AController){
AController aController = (AController)instance;
aController.show();
}
}
代码运行结果如下:
总结
以上就是本篇文章讲解的所有内容,在本篇文章中,咱们主要讲解了反射这门技术,反射在咱们Java开发中是一门很了不起的技术,掌握它对咱们的学习维度会有一个质的提升。反射的一些Api没有过分去讲解,第一因为太多,其次是没有什么必要性,主要还是通过讲解几个例子的形式让大家理解咱们反射主要能用来干什么,我相信大家经过真正的理解和体会,一定会感受到它的强大魅力的。
上一篇:Java 设计模式介绍