一、基于XML的Spring
1.自定义Spring工厂
1.1早期创建对象
在我们传统MVC项目中,后台代码一般分为:Controller层、Service层、Dao层,比如说:UserController雷、UserService接口、UserDao接口、UserServiceImpl实现类、UserDao实现类。
在Controller层、Service层,我们需要创建对应的实例来调用资源。
● Controller:
○ UserService userService = new UserServiceImpl();
● Service:
○ UserDao userDao = new UserDaoImpl();
上面这么写确实是可以实现功能的,但是代码的耦合性比较高,当我们删除UserServiceImpl类或则UserDaoImpl类,代码是编译不通过的,这就耦合性高。我们需要降低代码的耦合性,当删除对应的实现类,代码编译可以通过的,这就是代码耦合性低。但是删除对应的实现类是无法运行项目的。
代码的耦合性只能降低,不能消除。
1.2自定义Spring工厂
为了降低代码的耦合性,我们可以通过自定义一个工厂,将创建对象的任务交给工厂,在项目运行的时候,将对应的Service、Dao层的实体类的对象在工厂中创建,当我们使用的时候,不需要自己创建,只需要从工厂中取出就可以了。
可以在创建工厂的时候创建对象(单例模型,每次取出的对象地址相同,当对象实体类不涉及到变化的成员变量是可以的,相对多例节省资源);
也可以在我们使用对象的时候,再去创建对象(多例模型,每次取出的对象地址是不同的,当对象实体涉及到变化的成员变量,不能使用同一个对象,每次取出对象都需要是不同的对象)。
Spring可以代替我们自创建的工厂完成上述功能。
1.2.1 factory.properties文件
factory.properties文件用来存储实体类的key和 对应的全限定类名,需要保证 key的唯一性。
我们可以根据实体类的key获取到对应的全限定类名,通过反射创建对象。
userDao=com.sofwin.dao.impl.UserDaoImpl
userService=com.sofwin.service.impl.UserServiceImpl
1.2.2 FactoryBean工厂
FactoryBean工厂需要加载factory.properties文件,将对应文件中的key-value存储到:List<Map<String,String>> javabeans ,也可以创建List<Map<String,Object>>来存储对应的key-对象集合。
提供两者获取对象的方法,单例模式和多例模式。
public class FactoryUtil {
private static List<Map<String,String>> javabeans
=new ArrayList<Map<String, String>>();
private static List<Map<String,Object>> objects
= new ArrayList<Map<String, Object>>();
/**静态代码块的作用: 1.读取factory.properties文件;
2.将文件的key-value对应关系存储到:List<Map<String,String>> javabeans中;
3.创建对应实体类的对象,存储到:List<Map<String,Object>> objects中*/
static {
InputStream resourceAsStream =
FactoryUtil.class.getClassLoader().getResourceAsStream("factory.properties");
Properties properties=new Properties();
try {
properties.load(resourceAsStream);
Enumeration<?> enumeration = properties.propertyNames();
while(enumeration.hasMoreElements()){
String enumKey =(String) enumeration.nextElement();
Map map =new HashMap();
map.put(enumKey,properties.get(enumKey));
System.out.println("加载到工厂的map:"+enumKey+" : "+""+ properties.get(enumKey));
javabeans.add(map);
try {
/**
* 在Factory一创建的时候,就将 factory.properties中的对象实例化到集合objects中,
* 当通过单例模型获取对象的时候,可以直接从集合中拿对应的对象
* */
Map object = new HashMap();
object.put(enumKey,Class.forName(String.valueOf(properties.get(enumKey))).newInstance());
objects.add(object);
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.2.3单例模式
单例模式每次获取到的对象是同一个,同样的地址。仅适用于:对象的实体类中不存在变化的成员变量,线程不安全。
对于自定义工厂实现单例模式,就是在工厂创建的时候,创建实体类对象,需要的时候再返回地址。
/**
* 单例模式获取javaBean对象,每次获取到的对象是一个,同样的地址
* @return
*/
public static Object getSingleObject(String key) throws Exception {
Object result =null;
for(Map<String,Object> object:objects){
if(object.containsKey(key)){
result = object.get(key);
}
}
if(result==null){
throw new Exception("Factory工厂中不存 :"+key+"对应的全限定类名");
}else{
System.out.println("获取单例对象:"+result);
}
return result;
}
1.2.4多例模式
多例模式每次获取到的对象是不同的,线程安全,可以用在对象实体类中存在变化的成员变量当中,相对单例资源消耗。
对于自定义工厂实现单例模式,就是在工厂创建的时候,不会创建实体类对象,而是在 需要的时候再创建对象的对象,每次获取到的对象的地址是不同的。
/**
* 多例模式获取javaBean对象,每次获取到对象是不同的,不同的地址
* @param key
* @return
*/
public static Object getMultiObject(String key) throws Exception {
Object result =null;
for(Map<String,String> javabean:javabeans){
if(javabean.containsKey(key)){
result=Class.forName(javabean.get(key).toString()).newInstance();
}
}
if(result==null){
throw new Exception("Factory工厂中不存:"+key+"对应的全限定类名");
}else{
System.out.println("获取多例对象:"+result);
}
return result;
}
1.2.5测试演示
1.3工厂模式与IOC
在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。
那么,这个读取配置文件,创建和获取三层对象的类就是工厂。
控制反转(Inversion Of Control,IOC):把创建对象的权力交给框架(工厂),从工厂中拿对象,不在是自己通过new的方式创建对象。控制反转包括:依赖注入(DI)和依赖查找
明确 ioc 的作用:
削减计算机程序的耦合(解除我们代码中的依赖关系)。
2.Spring
2.1 Spring的概念和作用
Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control: 反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。
使用Spring可以降低代码的耦合性:我们之前通过new创建对象,现在创建对象交给Spring,我们需要的时候从Spring容器中取出就可以。创建对象的权限由之前的我们决定交给了Spring决定,这就是控制反转(IOC)
创建了对象之后,对于对象的 成员变量,我们可以依赖注入(DI)进行赋值
在当前类中需要用到其他类对象,由spring为我们提供,我们需要在配置文件中说明。依赖关系的维护就是依赖注入。
Spring的IOC主要是为了降低耦合。
2.2Spring的体系结构
2.3使用Spring开发
创建一个maven项目,
2.3.1导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
2.3.2创建Service类
package com.Eheart.service;
public class UserService {
}
2.3.3配置文件bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--id是唯一的,通过id从容器中取出对象(根据全限定类名反射创建对象)-->
<bean id="userService" class="com.Eheart.service.UserService"></bean>
</beans>
2.3.4测试
public class test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
UserService userService = ac.getBean("userService", UserService.class);
System.out.println(userService);
}
}
2.4Spring的接口体系
2.4.1ApplicationContext的三个实现类
● ClassPathXmlApplicationContext:加载类路径下的配置文件,resource目录下。(更常用)
● FileSystemXmlApplicationContext:加载磁盘任意路径下的文件。
● AnnotationConfigApplicationContext:用于注解创建容器的类。
ApplicationContext classAc =
new ClassPathXmlApplicationContext("bean.xml");
ApplicationContext fileAc =
new FileSystemXmlApplicationContext("E:\\App\\ideaIU-2019.2.4.win\\workspacesub\\spring01\\src\\main\\resources\\bean.xml");
2.4.2BeanFactory和ApplicationContext的区别
● ApplicationContext:它在构建核心容器的时候,会立即加载立即创建对象(一读取完配置文件会创建配置的对象)加载bean的时候可以通过scope属性进行设置单例多例模式。
● BeanFactory:它在构建容器的时候,采用延迟加载的方式创建对象(什么时候根据id获取对象了,什么时候才真正的创建对象)
2.4.3三种创建bean的方式
● 基于构造函数的创建方式
● 基于工厂的普通方法创建bean对象
● 基于工厂的静态方法创建对象
2.4.4bean的作用范围
● 基于构造函数的创建方式的bean默认是单例模式,每次取出的bean是同样的地址
● 可以通过scope设置bean的作用范围:
○ singleton:单例模式(默认的)
○ prototype:多例模式
○ request:作用于web应用的请求范围
○ session:作用于web应用的会话范围
○ globalSession:全局会话范围,作用于集群环境的会话范围(全局会话范围),当不是集群环境的时候,它就是session
○ application:
○ websocket:
2.4.5bean的生命周期
- 单例对象
a. 出生:当容器创建,单例对象也被创建
b. 活着:只有容器存在,对象一直活着
c. 消亡:容器销毁,对象消亡
d. 总结:单例对象的生命周期和容器的生命周期相同。 - 多例对象
a. 出生:容器创建的时候,不会创建多例对象。只有当调用到对象的时候,才会创建对象
b. 活着:多例对象使用过程中就会一直活着
c. 消亡:Spring不会回收,而是交给java。当对象长时间不用,且没有别的对象引用,由java的GC垃圾回收机制回收。调用close()方法无法销毁对象。
- 问题:由ApplicationContext ac =new ClassPathXmlApplicationContext(“bean.xml”); 创建的对象在Bean的生命周期中无法调用close()方法
解决措施:将对象看出子对象:
ClassPathXmlApplicationContext ac =new ClassPathXmlApplicationContext(“bean.xml”);
2.5DI依赖注入(Dependency Injection)
IOC的作用:降低程序间的耦合(依赖关系),之前通过new方式创建对象的方式被Spring的IOC代替,我们将对象的创建交给Spring,需要对象的时候从Spring容器中取对象。
DI:通过IOC创建的对象,对于对象的属性值,可能是没有值的。我们需要通过DI(依赖注入)来给对象的属性值赋值。
2.5.1 DI注入的数据类型
● 基本数据类型和String类型,可以使用value属性注入
● 其他bean类型(在配置文件或则注解中配置过bean),需要使用ref属性注入值
● 复杂类型/集合类型
○ 用于给List结构集合注入的标签:
■ list、array、set
○ 用于给Map结构集合注入的标签:
■ map、props
○ 结构相同,标签可以互换
2.5.2DI注入的方式
● 使用构造函数注入
○ 使用的标签:constructor-arg
○ 标签出现的位置:bean标签的内部
○ 标签中的属性:
■ type:指定注入数据的数据类型,该数据也是构造函数中的某个或则某些参数的类型
■ index:用于指定要注入的数据,通过构造函数中给指定位置的参数赋值。索引从0开始
■ name:用于给构造函数中指定名称的参数赋值
■ value:用于给基本数据类型和String类型赋值
■ ref:用于指定其他bean类型的数据,它就是在Spring的IOC容器中出现的bean对象。
○ 优势:在获取bean对象的时候,注入时数据是必然的操作。
○ 劣势:当我们在创建对象的时候,如果不需要这些数据,也是必须提供的。
注意:Spring使用构造函数依赖注入Date类型出现的问题:
Could not convert argument value of type [java.lang.String] to required type [java.util.Date]: Failed to convert value of type ‘java.lang.String’ to required type ‘java.util.Date’;
错误的原因:Date类型无法通过value进行赋值,value仅可以赋值:基本类型和String类
<bean id="accountService" class="com.sofwin.service.impl.AccountServiceImpl" scope="singleton">
<constructor-arg type="java.lang.Integer" name="id" index="0" value="99"></constructor-arg>
<constructor-arg type="java.lang.String" name="appName" index="1" value="stringvalue"></constructor-arg>
<!-- 错误的写法:Date类型无法通过value进行赋值,value仅可以赋值:基本类型和String类
<constructor-arg type="java.util.Date" name="createDate" index="2" value="2022-05-21"></constructor-arg>
-->
<constructor-arg type="java.util.Date" index="2" name="createDate" ref="now"></constructor-arg>
</bean>
<bean id="now" class="java.util.Date"></bean>
● 使用set方法提供
○ 涉及的标签:property
○ 出现的位置:bean标签的内部
○ 标签的属性:
■ name:用于指定注入的时所谓用的set方法
■ value:用于提供基本类型和String类型的数据
■ ref:用于指定其他bean类型数据,它就是在spring的IOC容器中出现过的bean对象
○ 优势:创建对象的时候没有明确的限定,可以直接使用默认构造函数
○ 如果需要保证某个成员必须有值,则set方法无法保证一定注入;
2.5.3DI注入的数据类型
● 基本数据类型和String类型:可以直接通过value注入
● 其他bean对象类型:需要通过ref引用其他的bean对象
● 复杂数据类型:List、array、Set;map、properties
3、创建Spring工程中遇到的问题:
- Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanInitializationException: Could not load properties; nested exception is java.util.InvalidPropertiesFormatException: org.xml.sax.SAXParseException; lineNumber: 11; columnNumber: 77; 文档根元素 “beans” 必须匹配 DOCTYPE 根 “null”。
答:在bean.xml(application.xml)文件中存在重复加载的文件