Spring是什么
Spring是分层的Java SE/EE 应用 full-stack 轻量级开源框架,以IOC(Inverse of Control控制翻转)和AOP(Aspect Oriented Programming)为内核
Spring的优势
方便解耦,简化开发
通过Spring提供的IOC容器,可以将对象间的依赖关系交给Spring进行控制,避免硬编码所造成的过度耦合。用户也不必为单例模式类、属性文件解析等这些底层需求编写代码,可以更专注于上层的应用。
AOP编程的支持
通过Spring的AOP功能,可以方便进行面向切面的编程,许多不容易用OOP实现的功能可以通过AOP轻松实现
方便集成各种优秀的框架
Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struct、Hibernate、MyBatis等)的直接支持
体系结构
程序间的耦合与解耦
程序的耦合,即程序间的依赖关系,包括类之间的依赖和方法间的依赖,如果一个程序耦合度太高,会导致难以维护,所以要降低程序间的依赖关系。在开发程序中应该做到编译期不依赖,而在运行时依赖(不使用new而是使用反射来创建对象)
用一个工厂类解耦
思路:
- 通过一个配置文件来映射全限定类名和唯一标识符(id)
- 通过读取配置文件(properties文件或者xml文件)来反射创建Bean对象
- 因为单例模式创建出来的对象如果不使用会被GC回收,所以需要定义一个容器储存起来
BeanFactory
public class BeanFactory {
//定义properties文件对象
private static Properties prop;
//定义一个容器,储存创建出来的对象
private static Map<String, Object> beans;
//为文件对象赋值
static {
try{
//实例化成员
prop = new Properties();
beans = new HashMap<>();
//使用反射获取resource文件下的配置文件的输入流
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
//绑定输入流
prop.load(in);
//通过配置文件创建对象并存入容器中
Enumeration keys = prop.keys();
//遍历枚举
while(keys.hasMoreElements()){
String key = keys.nextElement().toString();
//获取全限定类名
String beanPath = prop.getProperty(key);
//创建对象
Object value = Class.forName(beanPath).getDeclaredConstructor().newInstance();
//存入容器
beans.put(key,value);
}
}catch (Exception e){
throw new ExceptionInInitializerError("初始化配置文件失败!");
}
}
//从容器中取出并返回一个Bean对象,单例模式
public static Object getSingletonBean(String beanName){
return beans.get(beanName);
}
//多例模式创建对象
public static Object getPrototypeBean(String beanName){
Object bean = null;
String beanPath = prop.getProperty(beanName);
//使用反射创建对象
try {
bean = Class.forName(beanPath).newInstance();
}catch (Exception e){
e.printStackTrace();
}
return bean;
}
}
beans.properties
accountDao=com.my.Dao.impl.AccountDaoImpl
模拟持久层的实现类
AccountDao
import com.my.Dao.IAccountDao;
public class AccountDaoImpl implements IAccountDao {
public void saveAccount(){
System.out.println("保存了账户");
}
}
IOC的作用
IOC即控制反转(Inverse of Control),把创建对象的权利交给框架,是Spring的重要功能
在没有使用工厂类时,我们在获取对象时都是采用new的方法,是主动的,程序间的耦合性高
当我们使用工厂类时,从工厂类的容器中获取如BeanFactory.getSingletonBean()
,这其中有工厂为我们查找和创建对象,是被动的,程序间的耦合度低
基于XML的SpringIOC配置
导入pom.xml坐标
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<!--Spring组件-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>RELEASE</version>
</dependency>
</dependencies>
AccountService
import com.my.Dao.impl.AccountDaoImpl;
import com.my.service.IAccountService;
/**
* 模拟服务层实现类
*/
public class AccountServiceImpl implements IAccountService {
private AccountDaoImpl accountDao;
public void saveAccount(){
System.out.println("saveAccount执行了。。。");
}
}
bean的配置
在<bean>
标签中配置bean
id
是唯一标识符
class
是全限定类名
ApplicationContext.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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountDao" class="com.my.service.impl.AccountServiceImpl"/>
</beans>
测试类
其中的context对象是获取核心容器对象,用于读取读取配置文件
两个常用接口:
- ApplicationContext:采用立即加载,一读取配置就加载容器里的对象
- BeanFactory:采用延迟加载,调用获取方法时才创建对象
ApplicationContext的三个常用的实现类:
- ClassPathXmlApplicationContext:加载类路径下的配置文件(一般选用)
- FileSystemXmlApplicationContext:加载任意磁盘下的配置文件
- AnnotationConfigContext:读取注解来创建容器(使用纯注解选用)
import com.my.service.IAccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringBeanTest {
private static ClassPathXmlApplicationContext context;
@Test
public void SpringBeanTest(){
IAccountService accountService = (IAccountService)context.getBean("accountService");
accountService.saveAccount();
}
}
context.getBean()
方法返回的对象是Object类型的,需要进行强制类型转换,传入的参数是唯一标识符,即id
- 如果传入目标对象的类类型可以直接获得相应类型的对象,类似
context.getBean("accountService", IAccountService.class)
bean的创建方法
InstanceFactory
模拟一个工厂类(类可能存在于jar包中,无法通过修改源码获得默认构造函数)
public class InstanceFactory {
public IAccountDao getAccountDao(){
return new AccountDaoImpl();
}
public static IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
- 第一种创建方法,使用默认构造函数
<bean id="accountDao" class="com.my.Dao.impl.AccountDaoImpl"/>
- 第二种创建方法,把通过工厂类中的方法创建的对象存入容器(例如SqlSessionFactory)
<bean id="instanceFactory" class="com.my.factory.InstanceFactory"/>
再指定创建对象的工厂类的方法
<bean id="accountDao" factory-bean="instanceFactory" factory-method="getAccountDao"/>
- 第三种创建方法,使用工厂类中的静态方法获取对象并存入容器
<bean id="accountService" class="com.my.factory.InstanceFactory" factory-method="getAccountService"/>
bean的生命周期
<bean>
标签设置生命周期的属性
- scope指定作用域
- singleton 单例(默认)
- prototype 多例
- request web的一次请求
- session web的一个会话
- global-session 集群环境的全局会话范围
- init_method指定初始化方法(需要在类中实现相应的方法)
- destroy_method指定销毁方法(需要在类中实现相应的方法)
- 单例:
- 出生:容器创建时
- 活着:容器存在时
- 死亡:容器销毁时
- 与容器共生死
- 多例:
- 出生:当时用getBean方法获取时创建
- 活着:对象一直在使用中
- 死亡:当长时间不用或者没有对象引用它时,被java的垃圾回收器回收
例:<bean id="accountService" class="com.my.service.impl.AccountServiceImpl" scope="singleton" init-method="init" destroy-method="destroy" />
依赖注入(Dependency Injection)
如果当前类需要用到其他类的对象,由Spring为我们提供,需要在配置文件中声明这被称为依赖注入
- 能被注入的类型:
- 基本类型和String
- bean类型(在配置文件中声明过的)
- 复杂类型/集合类型
- 注入的方式:
- 使用构造函数
- 使用set方法
- 使用注解
构造函数注入
被注入的对象需要声明构造函数
User
import java.util.Date;
public class User {
private Integer id;
private String name;
private Date birthday;
//带参构造函数
public User(Integer id, String name, Date birthday) {
this.id = id;
this.name = name;
this.birthday = birthday;
}
}
带参构造函数注入需要使用<constructor-arg>
标签
标签中的属性:
用于指定给谁赋值
- type:用于指定参数的数据类型
- index:指定参数索引位置,从0开始
- name:指定参数名(常用)
用于指定值的内容
- value:用于给基本类型或String类型赋值
- ref:用于引用其他的bean类型
例
<bean id="user" class="com.my.entity.User">
<constructor-arg name="id" value="0"/>
<constructor-arg name="name" value="小明"/>
<!--引用日期对象-->
<constructor-arg name="birthday" ref="now"/>
</bean>
<!--创建一个日期bean对象-->
<bean id="now" class="java.util.Date"/>
set方法注入(常用)
被注入的对象需要声明set方法
User
import java.util.Date;
public class User {
private Integer id;
private String name;
private Date birthday;
public User(){}
public void setId(Integer id) {
System.out.println("setId...");
this.id = id;
}
public void setName(String name) {
System.out.println("setName...");
this.name = name;
}
public void setBirthday(Date birthday) {
System.out.println("setBirthday...");
this.birthday = birthday;
}
}
使用<property>
标签
标签中的属性:
- name:指定注入时调用的set方法
- value:用于给基本类型或String类型赋值
- ref:用于引用其他的bean类型
例
<bean id="user2" class="com.my.entity.User2">
<property name="id" value="1"/>
<property name="name" value="小红"/>
<!--引用日期对象-->
<property name="birthday" ref="now"/>
</bean>
复杂类型注入/集合类类型注入(set注入)
被注入的对象需要声明set方法
User
import java.util.*;
public class User3 {
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String, String> myMap;
private Properties[] propers;
public void setMyStrs(String[] myStrs) {
this.myStrs = myStrs;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setPropers(Properties[] propers) {
this.propers = propers;
}
}
用于给List结构集合注入的标签:
- list array set
用于给Map结构集合注入的标签: - map props
结构相同,标签可以互换
例
<bean id="user3" class="com.my.entity.User3">
<property name="myStrs">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="myList">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<property name="mySet">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="a" value="A"/>
<entry key="b" value="B"/>
<entry key="c" value="C"/>
</map>
</property>
<property name="propers">
<props>
<prop key="prop1">11</prop>
<prop key="prop2">22</prop>
</props>
</property>
</bean>