Spring Framework是由Spring团队研发的模块化、轻量级开源框架。其主要目的是为了简化项目开发。
Spring Framework前身是interface21,由Rod Johnson于2002年研发,主要是为了不使用EJB下依然能够构建高质量Java EE项目。EJB是当年横行一时的Java EE框架,目前已经属于淘汰技术。
2002年,大约有3万行代码的interface21出现。
2003年,Rod Johnson和朋友在interface21基础上研发了Spring。
2004年3月,Spring 1.0发布。
2006年10月,Spring 2.0发布。
2007年11月,更名为 SpringSource,同时发布了 Spring 2.5。
2009年12月,Spring 3.0 发布。同时把Spring由一个jar包拆分成多个jar包,真正实现模块化。
2013年12 月,Spring 框架 4.0发布。
2017年9月,Spring框架5.0发布。
暂时使用Spring框架版本5.3.16。
Test
对应spring-test.jar。Spring提供的测试工具,可以整合JUnit测试,简化测试环节。
Core Container
Spring的核心组件, 包含了Spring框架最基本的支撑。
Beans, 对应spring-beans.jar。Spring进行对象管理时依赖的jar包。
Core, 对应spring-core.jar,Spring核心jar包。
Context, 对应spring-context.jar, Spring容器上下文对象。
SpEL, 对应spring-expression.jar, Spring表达式语言。
AOP
面向切面编程,对应spring-aop.jar。
Aspects
Aspects的具体实现为面向切面编程的另一种实现。对应spring-aspects.jar。
Instrumentation
服务器代理接口的具体实现。对应spring-instrument.jar。
Messaging
集成messaging api和消息协议提供支持。对应spring-messaging.jar。
Data Access/Integration
Spring对数据访问层的封装。
JDBC,对应spring-jdbc.jar。Spring对jdbc的封装,当需要使用spring连接数据库时使用spring-jdbc.jar需要依赖spring-tx.jar。
Transactions。对应spring-tx.jar,事务管理。
ORM,对应spring-orm.jar。 spring整合第三方orm框架需要使用的jar包,例如Hibernate框架.
Web
Spring对javax下的接口或类做的扩展功能。
spring-web.jar。对Servlet,Filter,Listener等做的增强。
spring-webmvc.jar,实际上就是SpringMVC框架.。需要依赖spring环境和spring-web.jar。
IoC(Inversion of Control) 控制反转
DI(dependency injection) 依赖注入
IoC/DI指的是一个过程:对象的创建仅仅通过Spring容器负责,Spring容器可以通过对象的构造方法或工厂方法进行实例化对象。在创建对象过程中,如果对象需要依赖其他对象,也可以直接在Spring容器中注入到当前对象。
容器(Container):放置所有被管理的对象(所有的Bean)。其本质是在容器对象里面有一个全局Map对象,map对象中放置所有被管理的对象。Spring中容器是指ApplicationContext接口及子接口或实现类。
beans:容器中所有被管理的对象称为beans。如果单说其中一个对象可称为bean。
Spring IoC/DI使用后可以管理项目中相关对象,让对象管理的事情和业务分离(解耦)。同时程序员也不需要管理对象的依赖关系,所有的依赖关系交给Spring容器进行管理。
IoC/DI 主要作用就是管理对象的实例化和对象之间依赖关系。项目中以前需要自己实例化的层对象、框架或工具包的入口类都可以交给Spring 容器进行管理。
层对象:PeopleMapper 接口代理对象、PeopleServiceImpl。
框架入口类:SqlSessionFactory等。
Spring框架不依赖于Servlet容器(Tomcat),所以普通Java项目就可以使用Spring框架。
Spring框架提供了XML、注解、Java Config三种方式进行使用。
在项目的pom.xml中添加Spring项目的最基本依赖。
Spring项目想要运行起来必须包含:
spring-context.jar。spring上下文依赖,它依赖了下面的四个jar。
spring-core.jar。Spring 核心jar包。它依赖了spring-jcl.jar。
spring-aop.jar。Spring AOP基本支持。
spring-expression.jar。Spring的表达式语言支持。
spring-beans.jar。Spring容器的bean 管理。
spring-jcl.jar。Spring 4版本时是common-logging.jar。从5开始Spring自己对日志进行了封装spring-jcl.jar。
所以在Maven中想要使用Spring框架只需要在项目中导入spring-context就可以了,其他的jar包根据Maven依赖传递性都可以导入进来。
org.springframework
spring-context
5.3.16
package com.sh.pojo;
public class Emp {
private int id;
private String name;
private String addr;
@Override
public String toString() {
return "Emp{" +
"id=" + id +
", name='" + name + '\'' +
", addr='" + addr + '\'' +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
public Emp(int id, String name, String addr) {
this.id = id;
this.name = name;
this.addr = addr;
}
public Emp() {
}
}
在src/main/resources下新建spring-config.xml文件。
小提示:
文件名称没有强制要求。官方示例中配置文件名称叫做applicationContext.xml
创建测试类,测试Spring功能。
@Test
public void test(){
//加载配置文件
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-config01");
//获取bean对象中的实例对象
Object emp01 = ac.getBean("emp01");
System.out.println(emp01);
}
在Spring中实例化Bean有两种方式:
通过构造方法进行实例化。默认使用无参构造。这种方式和以前new的方式是等效的。上面的第一个Spring项目其实就是这种,只需要在XML中通过
的class属性指定类的全限定路径,然后就可以实例化对象。
通过工厂进行实例化。可以通过静态工厂和实例工厂进行实例化。这种方式完全是根据设计模式中工厂模式的思想而研发出的。Spring考虑到如果需要频繁实例化某个类的对象,工厂模式无疑是一个好选择。
上面第一个Spring项目已经演示过第一种实例化Bean的方式,所以下面重点演示使用工厂进行实例化bean。
实例工厂方式需要实例化工厂类。所以工厂中创建bean的方法是一个实例方法。
在项目中创建实例工厂类,并编写方法返回创建的实例对象。
package factory;
import com.sh.pojo.Emp;
public class EmpFactory {
Emp emp = new Emp();
public Emp getInstance(){
return emp;
}
}
在applicationContext.xml中先配置工厂实例,然后通过工厂实例产生自己想要的bean对象,注意bean标签中属性含义:
factory-bean:工厂bean的id属性值。
factory-method:工厂中创建bean的方法。
静态工厂和实例工厂类最主要的区别是,创建bean的方法是static修饰的。
package com.sh.factory;
import com.sh.pojo.Emp;
public class StaticEmpFactory {
//创建静态对象
private static Emp emp = new Emp();
//使用静态方法
public static Emp getInstance(){
return emp;
}
}
在applicationContext.xml中配置bean,注意bean标签中属性含义:
class:静态工厂类的全限定路径
factory-method:静态工厂中创建bean的静态方法。
Spring中给Bean属性赋值有两种方式:
构造注入(Constructor-based Dependency Injection):通过构造方法给bean的属性赋值。所以要求bean的类中必须提供对应参数的构造方法。相当于以前创建对象时new People(1,"张三");
设值注入,又称setter注入(Setter-based Dependency Injection):通过Bean的set方法赋值。所以要求Bean中属性必须提供set方法。相当于以前的:People peo = new People(); peo.setId(1); peo.setName("张三");
调用有参构造方法创建对象及完成属性值的注入,必须提供有参构造方法。
提供了有参构造方法,不再默认提供无参构造方法,不使用构造注入会需要使用无参构造方法创建对象,需要提供无参构造方法。
在配置文件applicationContext.xml中可以通过
的子标签
设置构造方法中一个参数的值。
解释说明:
constructor-arg 里面有5个属性,这5个属性分为2类。
(1)用来确定给哪个属性进行赋值
name:参数名称。
index:参数索引。从0开始算起。
type:参数类型。8大基本数据类型可以直接写关键字。其他类型需要写类型的全限定路径。
这三个属性如果用一个就能精确的告诉Spring,要设置的构造方法参数是哪个,可以使用一个。如果无法精确到某个构造方法参数,可以多个一起结合使用。
(2)设置属性的值
value:简单数据类型直接设置。Spring会自动进行类型转换。
ref:需要引用另一个bean的id。也就是说这个参数是一个引用类型,且这个类的对象也被Spring容器管理。
调用set方法完成属性值的注入,必须提供set方法。
Setter注入一般都是结合无参构造方法一起使用。所以类中有无参构造方法和set方法。
使用属性property
无论是构造注入还是设值注入都提供了value和ref进行设置值。这两个属性只能给属性赋予简单数据类型或其他bean的引用。
如果类的属性是数组、集合等类型需要通过下面方式进行设置。
一旦使用了下面子标签方式,就不能对
或
设置value属性或ref属性。
如果需要引用其他Bean,直接在property标签中使用ref引用就可以。使用子标签ref也可以,但是没有直接用ref属性的方式简单。
也可以让构造注入和设值注入混合使用。这两种方式只要保证有对应的有参构造方法和setter方法,就可以使用其中一种方式设置部分属性的值。
配置文件设置
1
1
1
1
1
1
1
1
1
1
1
小提示:
自动注入指的都是bean之间的自动注入。能够自动注入必须保证Spring 容器中包含能被自动注入的bean。
在Spring中,允许Bean的自动注入,有两种方式进行配置。
在根标签
中配置default-autowire属性,自动注入的策略,可取值有5个。
default:默认值,不自动注入。(使用在bean标签中有其它含义)
no:不自动注入。
byName:
通过名称自动注入。名称:属性名与bean的id属性值。
会自动寻找容器中与注入值属性名同名的id属性值的bean进行注入。
底层为setter注入,设值注入。
byType:
通过类型自动注入。类型:属性类型与bean的class类型。
会自动寻找容器中与注入值类型相同的bean进行注入。如果有多个相同类型的bean注入会出现异常。
底层为setter注入,设值注入。
constructor:
通过构造方法进行注入。调用有参构造方法完成对象创建及属性自动注入。
寻找bean的有参构造方法中只包含需要注入属性参数的构造方法。自动属性注入。
底层为构造注入。
配置文件
在
标签中配置autowire属性,和default-autowire取值相同。
default:使用上级标签
的default-autowire属性值。
default-autowire表示全局的配置。如果autowire和default-autowire同时存在,autowire生效。即局部的优先级高
Spring中
的scope控制的是Bean的有效范围。
一共有6个可取值,官方截图如下:
singleton:默认值。bean是单例的,每次获取Bean都是同一个对象。(单例设计模式)
prototype:bean时原型的,每次获取bean都重新实例化。
request:每次请求重新实例化对象,同一个请求中多次获取时单例的。
session:每个会话内bean是单例的。
application:整个应用程序对象内bean是单例的。
websocket:同一个websocket对象内对象是单例的。
里面的singleton和prototype在Spring最基本的环境中就可以使用,不需要web环境。
但是里面的request、session、application、websocket都只有在web环境才能使用。
衍生问题:Spring 中 Bean是否是线程安全的?
1. 如果bean的scope是单例的,bean不是线程安全的。
2. 如果bean的scope是prototype,bean是线程安全的。
设计模式:根据面向对象五大设计思想衍生出的常见23种设计模式,每种写法可以专门解决一类问题。
单例设计模式:保证某个类在整个应用程序中只有一个对象。
单例写法分为两种:饿汉式、懒汉式。
重要提示:
单例设计模式必须达到用纸手写的能力。
//单例设计模式 - 饿汉式
public class SingletonHungry {
//因为是单例,要设置为静态对象,只创建一次
private static SingletonHungry s = new SingletonHungry();
public SingletonHungry() {
}
//提供静态方法返回对象
public static SingletonHungry getInstance(){
return s;
}
}
package com.sh.util;
//单例设计模式 - 懒汉式
public class SingletonLazy {
private static SingletonLazy s;
//在方法被调用的时候才创建对象
public static SingletonLazy getInstance(){
//为了使线程安全,要加锁
//使用双重检查锁DCL(Double Check Lock双端检锁)
//但是双重检查锁DCL(Double Check Lock双端检锁)也不一定是线程安全的
//因为有指令重排
//必须关闭指令重排才能解决线程安全问题
//可以使用volatile关键字来避免此问题。
//private static volatile SingletonLazy instance = null;
if (s == null){
synchronized (SingletonLazy.class){
//解决多线程重复创建对象,里面还需要再次判断
if (s == null){
s = new SingletonLazy();
return s;
}
}
}
return s;
}
}
循环注入即多个类相互依赖,产生了一个闭环。
在Spring IoC/DI使用过程中,可能出现循环注入的情况。
当两个类都是用构造注入时,没有等当前类实例化完成就需要注入另一个类,而另一个类没有实例化完整还需要注入当前类,所以这种情况是无法解决循环注入问题的的。会出现BeanCurrentlyInCreationException异常。
原因:
单例的setter注入:
如果两个类都使用设值注入且scope为singleton的就不会出现问题,可以正常执行。因为单例默认下有三级缓存(DefaultSingletonBeanRegistry),可以暂时缓存没有被实例化完成的Bean。
Spring的单例对象的初始化主要分为三步:
实例化:其实也就是调用对象的构造方法实例化对象。
注入:填充属性,这一步主要是对bean的依赖属性进行填充。
初始化:属性注入后,执行自定义初始化操作。
A的某个field依赖了B的实例对象,同时B的某个field依赖了A的实例对象,这种情况为循环依赖。
一级缓存(singletonObjects):存放实例化,属性注入,初始化完成的对象。
二级缓存(earlySingletonObjects):存放早期暴露出来的Bean对象,属性还未填充完整。
三级缓存(singletonFactories):存放bean创建工厂,以便于后面扩展有机会创建代理对象。
二级缓存就可以解决简单的循环依赖,三级缓存是为了提高拓展性
BeanFactory是Spring中的顶级接口,接口中定了Spring容器最基本功能。是Spring IoC的最核心接口。
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader xbdr = new XmlBeanDefinitionReader(factory);
xbdr.loadBeanDefinitions(new FileSystemResource("C:\IdeaWS\spring_day01\src\main\resources\application.xml"));
Object teacher = factory.getBean("teacher");
BeanFactory是在调用getBean方法的时候才实例化Bean。(懒汉式)
ApplicationContext是BeanFactory的子接口。所以要比BeanFactory的功能更加强大,除了BeanFactory的功能,还包含了:
AOP 功能
国际化(MessageSource)
访问资源,如URL和文件(ResourceLoader)
消息发送机制(ApplicationEventPublisher)
Spring集成Web时的WebApplicationContext
在使用时ApplicationContext时多使用ClassPathXmlApplicationContext
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
Object teacher = applicationContext.getBean("teacher");
ApplicationContext是在加载配置文件后立即实例化Bean。(饿汉式)
可以通过bean标签的lazy-init属性进行控制,让bean懒加载。
创建一个普通Maven项目,并在pom.xml中添加依赖
4.0.0
com.sh
Spring02
1.0-SNAPSHOT
org.springframework
spring-context
5.3.16
org.mybatis
mybatis-spring
2.0.7
org.mybatis
mybatis
3.5.9
mysql
mysql-connector-java
8.0.28
junit
junit
4.12
test
创建com.sh.pojo.User
package com.sh.pojo;
import java.io.Serializable;
public class User implements Serializable {
private int id;
private String username;
private String password;
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public User(int id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public User() {
}
}
创建com.sh.mapper.UserMapper
package com.sh.mapper;
import com.sh.pojo.User;
import java.util.List;
public interface UserMapper {
List selectAll();
}
package com.sh;
import static org.junit.Assert.assertTrue;
import com.sh.mapper.UserMapper;
import com.sh.pojo.User;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.w3c.dom.ls.LSException;
import java.util.List;
/**
* Unit test for simple App.
*/
public class AppTest
{
/**
* Rigorous Test :-)
*/
@Test
public void shouldAnswerWithTrue()
{
assertTrue( true );
}
@Test
public void test(){
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("ApplicationContext.xml");
//底层已经封装好了获取代理对象的方法,直接通过接口的类对象就能获取代理对象
UserMapper mapper = ac.getBean(UserMapper.class);
List list = mapper.selectAll();
System.out.println(list);
}
}
Spring 项目不需要依赖Web环境,但是Java项目大多数是Web项目,所以Spring也支持集成到Web环境中。
Spring 集成Web环境是通过Listener实现的,在ServletContext对象创建时加载Spring容器。Spring已经在spring-web.jar包中提供了ContextLoaderListener实现加载Spring配置文件的代码。我们只需要在web.xml配置
标签让ContextLoaderListener生效,并且告诉ContextLoaderListener加载Spring配置文件的路径即可。
创建Maven的web项目(添加上webapp目录等,并配置web模块),并在pom.xml配置依赖。
4.0.0
war
Spring03Web
com.sh
Spring03Web
1.0-SNAPSHOT
junit
junit
4.11
test
org.springframework
spring-context
5.3.16
org.springframework
spring-web
5.3.16
javax.servlet
javax.servlet-api
4.0.1
provided
javax.servlet.jsp
javax.servlet.jsp-api
2.3.3
provided
org.apache.tomcat.maven
tomcat7-maven-plugin
2.2
/
80
UTF-8
随意创建一个类,为了测试是否能从容器中获取到bean。
创建com.sh.pojo.User。
package com.sh.pojo;
import java.io.Serializable;
public class User implements Serializable {
private int id;
private String username;
private String password;
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public User(int id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public User() {
}
}
建议:建立上applicationContext.xml的模板。
里面创建User的bean即可。
为了使spring容器只创建一次,避免每次访问都重新创建容器,可以使用静态代码创建容器,
tomcat中有三个域对象:
请求域,会话域,应用域
应用域(ServletContext)在tomcat启动后创建,tomcat关闭后销毁,且服务器中都共用,且创建一次
我们可以把spring容器存储在ServletContext中进行使用,可以通过监听器监听它的状态
可以自己写一个监听器,监听ServletContext的状态
package com.sh.lister;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
//配置监听器
//让web项目启动时自动创建Spring容器对象
//手写监听器
public class AppListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
//ServletContext初始化后就创建spring容器并保存在servletContext中
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
ServletContext servletContext = sce.getServletContext();
servletContext.setAttribute("ac",ac);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
在web.xml中配置监听器,让web项目启动时自动创建Spring容器对象(WebApplicationContext)。
Archetype Created Web Application
com.sh.lister.AppListener
spring中内置了一个监听器,我们可以直接使用,但是需要对其进行配置
上下文参数名param-name的值是固定的contextConfigLocation。这个值是监听器需要获取的值。
上下文参数值需要带有classpath,表示加载类路径内容。target/classes目录内容。
classpath*:表示当前项目依赖的jar中资源也会寻找。
classpath:后面的值支持星号。例如applicationContext-*.xml
表示加载所有以applicationContext-开头的xml文件。
Archetype Created Web Application
contextConfigLocation
classpath:applicationContext.xml
org.springframework.web.context.ContextLoaderListener
package com.sh.controller;
import com.sh.pojo.User;
import org.springframework.context.ApplicationContext;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/query")
public class UserServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = req.getServletContext();
ApplicationContext ac = (ApplicationContext) servletContext.getAttribute("ac");
User user = ac.getBean(User.class);
System.out.println(user);
}
}