简单实现spring框架的ioc
首先复习一下依赖倒转原则
依赖倒转原则(ioc)
高层模块不应该依赖于底层模块,二者都应该依赖其抽象
抽象不应该依赖于细节,细节应该依赖于抽象
类A直接依赖于类B,假如未来有可能改成类A依赖于类C,这种场景下
类A就是高层模块,负责复杂的业务逻辑;
类B和类C是低层模块,负责基本的原子操作
* 面向对象 -> 面向接口
在项目中的体现:(service层调用dao层方法,那么就需要在service层创建dao层对象,这个dao层对象是成员属性存在于service层)
为了解决三层架构之间的依赖问题,降低耦合度,推出了这样一个工具类
本类采用IoC/di设计原则通过调用xml映射文件和反射的方式实例化对象
ApplicationContext容器实例化后会自动对所有的单实例Bean进行实例化与依赖关系的装配,使之处于待用状态
还没有学到spring框架的ioc容器部分,采用java se 所学知识模拟。
ioc的概念
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。创建对象的控制权交给第三方容器,需要使用时向第三方容器申请对象。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
我们可以把IoC模式看做是工厂模式的升华,把IoC看做是一个大工厂,只不过这个大工厂里要生成的对象都是在XML文件中给出定义的,然后利用Java的“反射”编程,根据XML中给出的类名生成相应的对象。从实现来看,IoC是把以前在工厂方法里写死的对象生成代码,改变为由XML文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性。
工厂模式
在工厂模式中, 我们在创建对象时不会对用户暴露创建逻辑
并且是通过一个共同的接口来指向新创建的对象
优点:
1. 调用者项创建一个对象, 只需要知道名字就可以了
2. 拓展性高, 如果想增加一种产品, 只要拓展一个工厂类就可以
3. 屏蔽了产品的具体实现, 用户只关心产品的接口
缺点:
每次增加一种产品, 必然要增加一种工厂. 会导致系统中的类越来越多,
提高系统的复杂性, 有可能不是一件好事
DI——Dependency Injection 依赖注入
采用依赖注入技术之后,A的代码只需要定义一个私有的B对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序来将B对象在外部new出来并注入到A类里的引用中。而具体获取的方法、对象被获取时的状态由配置文件(如XML)来指定。
Userservicrimpl类中很多方法需要调用userdaoimpl方法,
所以设置了全局变量private userdao ud以及对应的set方法
配置文件xml格式
若采用ioc依赖倒转原则,配置的xml文件如何实现
<?xml version ="1.0" encoding="UTF-8"?>
<beans>
<bean name="UserService" class="com.bwf.service.impl.UserServiceImpl">
<property name="ud" value="UserDao" />
<property name="ad" value="AdminDao" />
<property name="ld" value="Lottery_infoDao" />
<property name="uld" value="User_lotteryDao" />
</bean>
<bean name="AdminService" class="com.bwf.service.impl.AdminServiceImpl">
<property name="ld" value="Lottery_infoDao" />
<property name="ud" value="UserDao" />
<property name="ad" value="AdminDao" />
<property name="uld" value="User_lotteryDao" />
</bean>
<bean name="UserDao" class="com.bwf.dao.impl.UserDaoImpl" />
<bean name="AdminDao" class="com.bwf.dao.impl.AdminDaoImpl" />
<bean name="Lottery_infoDao" class="com.bwf.dao.impl.Lottery_infoDaoImpl" />
<bean name="User_lotteryDao" class="com.bwf.dao.impl.User_lotteryDaoImpl" />
</beans>
<!-- 1. 再配置一个user对象在xml文件中 在java代码中分别获取2个不同的user对象, 并打印输出 2. 在xml文件中配置一条记录对象,
并赋值属性 在java代码中获取这个记录对象, 并打印输出(可能会出问题, 试着解决一下) -->
Userdao,AdminDao,Lottery_infoDao,User_lotteryDao分别作为bean节点和property节点,因为这4个接口是作为userserviceimpl类的成员属性存在,userserviceimpl依赖于这4个接口
而Userdao,AdminDao,Lottery_infoDao,User_lotteryDao并不是基本数据类型,是引用数据类型,和userservice一样需要进行实例化
service层中的dao层成员属性关联:
private Lottery_infoDao ld;
private UserDao ud;
private AdminDao ad;
private User_lotteryDao uld;
private List<String> list = new ArrayList<>();
public void setLd(Lottery_infoDao ld) {
this.ld = ld;
}
public void setUd(UserDao ud) {
this.ud = ud;
}
public void setUld(User_lotteryDao uld) {
this.uld = uld;
}
public void setAd(AdminDao ad) {
this.ad = ad;
}
在项目中用到applicationcontext的地方为服务器的读线程(实例化service层对象后根据传入的请求类型调用不同方法):
//成员属性:
private UserService us;
private AdminService as;
//run方法中的成员属性实例化:
us = (UserService) ApplicationContext.getBean("UserService");
as = (AdminService) ApplicationContext.getBean("AdminService");
Applicationcontext类的结构
加载外部jar包:dom4j-1.6.1(用于读取xml文件的类)
成员属性:
hashmap 存放类名和该类名实例化的对象,对应xml文件的bean节点的属性name和class
静态方法:
init 方法:再调用2个静态方法遍历
第一次遍历:
加载xml文件,实现控制反转
遍历bean节点,把name对应的属性值(类名)采用反射的方式实例化对象存放到map中
第二遍遍历:
加载xml文件,注入依赖
遍历bean节点,若里面有依赖关系(property节点),那么采用反射的方式注入依赖(给成员属性赋值)
getbean方法
当某个类需要外部对象时调用静态getbean方法传入类名,从map中获取对象即可
具体代码:
public class ApplicationContext {
private static Map<String, Object> beanmap = new HashMap<>();
public static void main(String[] args) {
// TODO Auto-generated method stub
// 初始化,加载xml文件
init();
Record r1 = (Record) getBean("record");
}
public static void init() {
// 初始化,用jar包加载xml文件
SAXReader reader = new SAXReader();
Document dc;
try {
dc = reader.read(ApplicationContext.class.getClassLoader()
.getResourceAsStream("applicationcontext.xml"));
// 获得根节点
Element re = dc.getRootElement();
// 第一遍遍历xml将bean的类名实例化存入map
paresxml(re);
// System.out.println(beanmap);
// 第二遍遍历map,把xml的属性值赋值给bean的成员属性,
// 若某个类依赖于另一个类,则通过map再次实例化被依赖的类,依赖注入
di(re);
} catch (DocumentException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
private static void di(Element re) {
// TODO Auto-generated method stub
// 第二遍遍历xml文件,注入依赖
// bean节点
List<Element> list = re.elements();
try {
// 遍历bean节点
for (Element element : list) {
// String name=element.getName();
String name = element.attributeValue("name");
// 通过bean的name属性值,根据第一遍加载xml文件获得的map找到对应的对象
Object object = beanmap.get(name);
// System.out.println(beanmap);
// 拿到并遍历property节点
List<Element> propertys = element.elements();
for (Element element2 : propertys) {
// 拿到对象的成员属性名name(被依赖的接口对象名)
String p_name = element2.attributeValue("name");
// System.out.println(p_name);
// 拿到对象的成员属性名name(被依赖的接口类名)
String p_value = element2.attributeValue("value");
// System.out.println(p_value);
Field df = object.getClass().getDeclaredField(p_name);
// 通过反射,调用set方法获得被依赖的类的对象
// us实例化的对象,us的成员属性,us的成员属性名name(被依赖的接口类名)
getElement(object, df, p_value);
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static void paresxml(Element re) {
// 第一遍遍历,把所有的beans实例化并放入map
// TODO Auto-generated method stub
try {
List<Element> list = re.elements();
for (Element element : list) {
// String name=element.getName();
String name = element.attributeValue("name");
String classname = element.attributeValue("class");
// System.out.println(classname);
Class<?> forName = Class.forName(classname);
Object object = forName.newInstance();
// 存放 接口名和实例化的对象,若有接口要实例化对象直接调用getElement方法,传入类名即可返回对象
beanmap.put(name, object);
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
private static void getElement(Object object, Field df, String p_value)
throws NoSuchFieldException, SecurityException, NoSuchMethodException,
IllegalAccessException,
IllegalArgumentException, InvocationTargetException, ParseException {
Class<?> class1 = object.getClass();
// 组装set方法名
String setter = "set" + df.getName().substring(0, 1).toUpperCase()
+ df.getName().substring(1);
// System.out.println(setter);
// 反射获得set方法,参数为set方法名和成员属性类型
Method dm = class1.getDeclaredMethod(setter, df.getType());
Object value = null;
// 因为xml读取的数据类型只能是string,
// 但是成员属性不一定为string类型,所以反射调用set方法传非string参数会报错
// 所以根据df成员属性的属性类型进行强制转换类型
switch (df.getType().getSimpleName()) {
case "Double":
value = Double.parseDouble(p_value);
break;
case "Integer":
value = Integer.parseInt(p_value);
break;
// 成员属性date为string类型
// case"Date":
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
// value= sdf.parse(p_value);
// break;
case "String":
value = p_value;
break;
default:
// 若非基本数据类型和string,那么一定是被依赖的某个类
// 这时根据成员属性的接口名从map取出相应的实例调用反射赋值给成员属性
value = beanmap.get(p_value);
// System.err.println(p_value);
break;
}
// System.out.println(dm+" "+value);
// 调用set方法赋值
dm.invoke(object, value);
}
public static Object getBean(String name) {
return beanmap.get(name);
}
}