Spring 与 Web
在 Web 项目中使用 Spring 框架,首先要解决在 web 层(这里指 Servlet)中获取到 Spring容器的问题。只要在 web 层获取到了 Spring 容器,便可从容器中获取到 Service 对象。
1、Web 项目使用 Spring 的问题【了解】
1)新建maven project
类型 maven-archetype-webapp
2)添加maven依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.b0kuwa</groupId>
<artifactId>ch07-spring-web</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<!-- servlet依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- jsp依赖 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2.1-b03</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory><!--所在的目录-->
<includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
3)使用之前的spring-mybatis整合项目上修改
A、dao包
StudentDao接口
public interface StudentDao {
int addStudent(Student student);
int updateStudent(Student student);
int deleteStudentById(Integer id);
Student queryStudentById(Integer id);
List<Student> queryAllStudent();
}
StudentDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.b0kuwa.dao.StudentDao">
<insert id="addStudent">
insert into student(name,age) values (#{name},#{age})
</insert>
<update id="updateStudent">
update from student set name = #{name},age=#{age} where id = #{id}
</update>
<delete id="deleteStudentById">
delete from student where id = #{id}
</delete>
<select id="queryStudentById" resultType="com.b0kuwa.domain.Student">
select id,name,age from student where id = #{id}
</select>
<select id="queryAllStudent" resultType="com.b0kuwa.domain.Student">
select id,name,age from student
</select>
</mapper>
B、domain实体类
C、service包
接口:
public interface StudentService {
int addStudent(Student student);
int updateStudent(Student student);
int deleteStudentById(Integer id);
Student queryStudentById(Integer id);
List<Student> queryAllStudent();
}
实现类:
@Service(value = "studentService")
public class StudentServiceImpl implements StudentService {
@Resource
private StudentDao studentDao;
@Override
public int addStudent(Student student) {
return studentDao.addStudent(student);
}
@Override
public int updateStudent(Student student) {
return studentDao.updateStudent(student);
}
@Override
public int deleteStudentById(Integer id) {
return studentDao.deleteStudentById(id);
}
@Override
public Student queryStudentById(Integer id) {
return studentDao.queryStudentById(id);
}
@Override
public List<Student> queryAllStudent() {
return studentDao.queryAllStudent();
}
}
D、resources
jdbc.properties
jdbc.url = jdbc:mysql://192.168.31.205:3306/springdb
jdbc.username = root
jdbc.password = 111111
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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.b0kuwa.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<context:component-scan base-package="com.b0kuwa.service"/>
</beans>
mybatis.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!-- 别名 -->
<typeAliases>
<package name="com.b0kuwa.domain"/>
</typeAliases>
<!-- mapper文件 -->
<mappers>
<package name="com.b0kuwa.dao"/>
</mappers>
</configuration>
4)定义index页面
5)定义 RegisterServlet【重点代码】
public class RegisterServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//接收请求参数
String name = req.getParameter("name");
String age = req.getParameter("age");
//创建spring容器,获取service对象
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
System.out.println("容器信息:"+ac);
StudentService studentService = (StudentService) ac.getBean("studentService");
Student student = new Student();
student.setName(name);
student.setAge(Integer.parseInt(age));
//调用service方法
studentService.addStudent(student);
//显示处理结果的jsp
req.getRequestDispatcher("/result.jsp").forward(req,resp);
}
}
6)定义 success 页面
7)web.xml 注册 Servlet
<servlet>
<servlet-name>RegisterServlet</servlet-name>
<servlet-class>com.b0kuwa.controller.RegisterServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RegisterServlet</servlet-name>
<url-pattern>/registerServlet</url-pattern>
</servlet-mapping>
8)运行结果分析
当表单提交,跳转到 result.jsp 后,多刷新几次页面,查看后台输出,发现每刷新一次页面,就 new 出一个新的 Spring 容器。即,每提交一次请求,就会创建一个新的 Spring 容器。对于一个应用来说,只需要一个 Spring 容器即可。所以,将 Spring 容器的创建语句放在 Servlet 的 doGet()或 doPost()方法中是有问题的。
此时,可以考虑,将 Spring 容器的创建放在 Servlet 进行初始化时进行,即执行 init()方法时执行。并且,Servlet 还是单例多线程的,即一个业务只有一个 Servlet 实例,所有执行该业务的用户执行的都是这一个 Servlet 实例。这样,Spring 容器就具有了唯一性了。
但是,Servlet 是一个业务一个 Servlet 实例,即 LoginServlet 只有一个,但还会有StudentServlet、TeacherServlet 等。每个业务都会有一个 Servlet,都会执行自己的 init()方法,也就都会创建一个 Spring 容器了。这样一来,Spring 容器就又不唯一了。
2、使用 Spring 的监听器 ContextLoaderListener【掌握】
对于 Web 应用来说,ServletContext 对象是唯一的,一个 Web 应用,只有一个ServletContext 对象,该对象是在 Web 应用装载时初始化的。若将 Spring 容器的创建时机,放在 ServletContext 初始化时,就可以保证 Spring 容器的创建只会执行一次,也就保证了Spring 容器在整个应用中的唯一性。
当 Spring 容器创建好后,在整个应用的生命周期过程中,Spring 容器应该是随时可以被访问的。即,Spring 容器应具有全局性。而放入 ServletContext 对象的属性,就具有应用的全局性。所以,将创建好的 Spring 容器,以属性的形式放入到 ServletContext 的空间中,就保证了 Spring 容器的全局性。
上述的这些工作,已经被封装在了如下的 Spring 的 Jar 包的相关 API 中:spring-web-5.2.5.RELEASE
1)maven 依赖 pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
2)注册监听器 ContextLoaderListener
若要在 ServletContext 初 始 化 时 创 建 Spring 容 器 , 就 需 要 使 用 监 听 器 接 口ServletContextListener 对 ServletContext 进行监听。在 web.xml 中注册该监听器。
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
Spring 为该监听器接口定义了一个实现类 ContextLoaderListener,完成了两个很重要的工作:创建容器对象,并将容器对象放入到了 ServletContext 的空间中。
打开 ContextLoaderListener 的源码。看到一共四个方法,两个是构造方法,一个初始化方法,一个销毁方法。
所以,在这四个方法中较重要的方法应该就是 contextInitialized(),context 初始化方法。
跟踪 initWebApplicationContext()方法,可以看到,在其中创建了容器对象。
并且,将创建好的容器对象放入到了 ServletContext 的空间中,key 为一个常量:WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。
3)指定 Spring 配置文件的位置<context-param>
ContextLoaderListener 在对 Spring 容器进行创建时,需要加载 Spring 配置文件。其默认的 Spring 配置文件位置与名称为:WEB-INF/applicationContext.xml。但,一般会将该配置文件放置于项目的 classpath 下,即 src 下,所以需要在 web.xml 中对 Spring 配置文件的位置及名称进行指定。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
从监听器 ContextLoaderListener 的父类 ContextLoader 的源码中可以看到其要读取的配置文件位置参数名称 contextConfigLocation。
4)获取 Spring 容器对象
在 Servlet 中获取容器对象的常用方式有两种:
A、直接从 ServletContext 中获取
从对监听器 ContextLoaderListener 的源码分析可知,容器对象在 ServletContext 的中存放的 key 为 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。所以,可以直接通过 ServletContext 的 getAttribute()方法,按照指定的 key 将容器对象获取到。
//获取容器
String attr = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
WebApplicationContext ac = (WebApplicationContext) this.getServletContext().getAttribute(attr);
B、通过 WebApplicationContextUtils 获取
工具类 WebApplicationContextUtils 有一个方法专门用于从 ServletContext 中获取 Spring容器对象:getRequiredWebApplicationContext(ServletContext sc)
调用 Spring 提供的方法获取容器对象:
//框架提供的方法,获取容器对象
WebApplicationContext ac = WebApplicationContextUtils
.getRequiredWebApplicationContext(getServletContext());
查其源码,看其调用关系,就可看到其是从 ServletContext 中读取的属性值,即 Spring容器。
以上两种方式,无论使用哪种获取容器对象,刷新 result 页面后,可看到代码中使用的 Spring 容器均为同一个对象。