Spring框架使用的前提与理解
使用Spring框架需要下载对应的jar包,事实上我的理解就是spring框架的创始人创建好了对应的包给使用者使用,初学者不必在意spring底层实现细节,导包即用即可。
Spring中有一个重要的概念,即IOC容器,(名字叫控制反转)。
什么是IOC?
IOC事实上是为了项目解耦出现的,将对象的创建以及对应属性对象的注入交由Spring进行管理。
在web项目的初期,对象之间大量使用new方法来进行创建,使得层与层之间耦合严重,牵一发而动全身,具体例子就是:service层需要DAO层对象来对数据库进行操作,如果直接new一个DAO层对象的话,后续如果想对DAO进行如移动class类到其他包,改名等操作,会疯狂爆红(报bug)。
为了项目后期的维护更新以及升级,IOC容器应运而生,在XML中进行对象的配置(在XML中对象统称为bean),IOC通过读取XML中的配置来进行对象的统一创建(默认是单例模式创建),如果对象中引用到其他bean对象,IOC会在统一创建所有bean对象后,在一一根据配置的属性进行依赖注入。(这种是读取XML文件进行手动配置)。
简易IOC的实现原理
之前博主曾跟随一个视频进行过手写简易版ioc容器,所以大概能够明白ioc容器的实现过程:大致上就是通过特定的类,读取到你配置的bean中id与class属性,通过反射的方式用class的全类名创建对象,然后将id作为key值,bean对象作为value值封装到一个BeanMap集合中,所有对象创建完毕之后,再重头读取bean节点的子节点中的属性值,通过name属性作为key值在BeanMap中得到对应的bean对象,此时有两种方法可以进行属性注入,Spring中使用xml配置的话默认是通过该类设置的set方法进行注入,如果没设置到set方法会报错,配置失败;而博主的手写简易IOC容器是使用反射进行暴力破解注入,这种方法不安全,也不需要使用到该类方法配置的set方法。 (具体视频是尚硅谷的最新版Javaweb视频,讲师是张益桃老师)
private Map<String,Object> beanMap = new HashMap<>();
private String path = "applicationContext.xml" ;
public ClassPathXmlApplicationContext(){
this("applicationContext.xml");
}
public ClassPathXmlApplicationContext(String path){
if(StringUtil.isEmpty(path)){//StringUtil是手写的工具类,作用只是判断字符串是否为空
throw new RuntimeException("IOC容器的配置文件没有指定...");
}
try {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(path);
//1.创建DocumentBuilderFactory
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
//2.创建DocumentBuilder对象
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder() ;
//3.创建Document对象
Document document = documentBuilder.parse(inputStream);
//4.获取所有的bean节点
NodeList beanNodeList = document.getElementsByTagName("bean");
for(int i = 0 ; i<beanNodeList.getLength() ; i++){
Node beanNode = beanNodeList.item(i);
if(beanNode.getNodeType() == Node.ELEMENT_NODE){
Element beanElement = (Element)beanNode ;
String beanId = beanElement.getAttribute("id");
//读取到对应id的属性值
String className = beanElement.getAttribute("class");
//读取到对应class的全类名
Class beanClass = Class.forName(className);
//创建bean实例
Object beanObj = beanClass.newInstance() ;
//将bean实例对象保存到map容器中
beanMap.put(beanId , beanObj) ;
//到目前为止,此处需要注意的是,bean和bean之间的依赖关系还没有设置
}
}
//5.组装bean之间的依赖关系
for(int i = 0 ; i<beanNodeList.getLength() ; i++){
Node beanNode = beanNodeList.item(i);
if(beanNode.getNodeType() == Node.ELEMENT_NODE) {
Element beanElement = (Element) beanNode;
String beanId = beanElement.getAttribute("id");
//先得到是哪个bean需要被注入属性依赖
NodeList beanChildNodeList = beanElement.getChildNodes();
for (int j = 0; j < beanChildNodeList.getLength() ; j++) {
Node beanChildNode = beanChildNodeList.item(j);
//判断这个bean中的子节点是否是设置的property节点
if(beanChildNode.getNodeType()==Node.ELEMENT_NODE && "property".equals(beanChildNode.getNodeName())){
Element propertyElement = (Element) beanChildNode;
String propertyName = propertyElement.getAttribute("name");
String propertyRef = propertyElement.getAttribute("ref");
//1) 找到propertyRef对应的实例
Object refObj = beanMap.get(propertyRef);
//2) 将refObj设置到当前bean对应的实例的property属性上去
Object beanObj = beanMap.get(beanId);
Class beanClazz = beanObj.getClass();
Field propertyField = beanClazz.getDeclaredField(propertyName);
propertyField.setAccessible(true);
propertyField.set(beanObj,refObj);
}
}
}
}
IOC容器中基于XML的属性注入的格式要求
第一种,常用类属性通过set方法注入
<bean id="book" class="com.atguigu.spring5.Book">
<!--使用 property 完成属性注入
name:类里面属性名称
value:向属性注入的值
-->
<property name="bname" value="易筋经"></property>
<property name="bauthor" value="达摩老祖"></property>
</bean>
第二种,通过有参构造函数注入
public class Orders {
//属性
private String oname;
private String address;
//有参数构造
public Orders(String oname,String address) {
this.oname = oname;
this.address = address;
}
}
<bean id="orders" class="com.atguigu.spring5.Orders">
<constructor-arg name="oname" value="电脑"></constructor-arg>
<constructor-arg name="address" value="China"></constructor-arg>
</bean>
而引用类型注入的话,又分为外部bean对象注入与内部bean对象注入
其中,外部bean对象注入:
public class UserService {
//创建 UserDao 类型属性,生成 set 方法
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void add() {
System.out.println("service add...............");
userDao.update();
}
}
<!--1 service 和 dao 对象创建-->
<bean id="userService" class="com.atguigu.spring5.service.UserService">
<!--注入 userDao 对象
name 属性:类里面属性名称
ref 属性:创建 userDao 对象 bean 标签 id 值
-->
<property name="userDao" ref="userDaoImpl"></property>
</bean>
<bean id="userDaoImpl" class="com.atguigu.spring5.dao.UserDaoImpl"></bean>
内部bean对象注入:
//部门类
public class Dept {
private String dname;
public void setDname(String dname) {
this.dname = dname;
}
}
//员工类
public class Emp {
private String ename;
private String gender;
//员工属于某一个部门,使用对象形式表示
private Dept dept;
public void setDept(Dept dept) {
this.dept = dept;
}
public void setEname(String ename) {
this.ename = ename;
}
public void setGender(String gender) {
this.gender = gender;
}
}
<!--内部 bean-->
<bean id="emp" class="com.atguigu.spring5.bean.Emp">
<!--设置两个普通属性-->
<property name="ename" value="lucy"></property>
<property name="gender" value="女"></property>
<!--设置对象类型属性-->
<property name="dept">
<bean id="dept" class="com.atguigu.spring5.bean.Dept">
<property name="dname" value="安保部"></property>
</bean>
</property>
</bean>
后面还有一些细节方面的东西,比如关于数组,集合类型的注入该如何实现,此处博主不想作多赘述,全都是所见即所得的东西,该用时查询使用即可。
还有一种方式是通过注解进行配置,我还没学完。