一、引子
前面我们在Spring集成Junit中为读者引出了Spring善于集成其它框架的优势,而Spring项目不可能仅限于小范围的某个方法的测试,终究会落脚于Web项目上。于是,我们就从这里正式进入Spring集成Web的话题。由于笔者会从原生的Java Web开发过渡到Spring集成Web,从而体现出Spring集成的魅力,因此在阅读这篇文章之前,希望读者已经有关于Java Web的一些基础(可先浏览Java Web专栏),包括Servlet,Tomcat,JSP等。
这里笔者想多说几句,虽然现在主流的开发似乎都不再与Servlet,Tomcat,JSP之流直接接触了,但是理解一个技术,是要理解这个技术为什么产生,是解决了当时的什么问题,这样能帮助我们将不断迭代的技术连贯起来,因此我们就需要了解这门技术产生之前使用的是什么技术,这样会让我们更深入地理解现在使用的技术的优势。Servlet是Web应用的基石,虽不直接使用却是必要掌握的;Tomcat(这里泛指Web服务器)似乎在SpringBoot的兴起后而不再需要像此前一样关注,但是Web项目离不开Tomcat;JSP更是一门难用、逐渐被摒弃的语言,虽然JSP是过时的技术,但依旧不妨去学习到能看懂JSP代码的程度,这样你将更能理解什么是前后端分离。重要的是,当你慢慢熟悉技术的迭代发展,你会对一些似乎耳熟能详的词语感觉到豁然开朗而不是"看似"理解了这些概念。
二、仅用Spring框架如何实现?
(1)利用我们上一篇介绍的Spring注解开发,声明一个配置类:
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.bylearning")
public class SpringConfiguration {
}
(2)定义Dao层与Service层类,注解在类上添加注解,表明将此类交给Spring容器:
import org.springframework.stereotype.Repository;
@Repository
public class UserDao {
public void save() {
System.out.println("save successfully...");
}
}
Service层注入Dao层对象属性
import com.bylearning.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void saveUser() {
userDao.save();
}
}
Web层先延用在Java Web中使用的Servlet:
import com.bylearning.config.SpringConfiguration;
import com.bylearning.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/demo")
public class DemoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
ApplicationContext ioc = new AnnotationConfigApplicationContext(SpringConfiguration.class);
UserService us = ioc.getBean(UserService.class);
us.saveUser();
}
}
配置Tomcat,启动项目,在浏览器地址栏中访问该Servlet便可在控制台中看到Dao层中打印的输出语句。至此便完成了原生的Spring 对于Web的开发。
三、在此基础上怎么改进?
我们注意到在DemoServlet类中,我们为了获取到容器中的Service层对象,利用配置类创建了一个IoC容器。我们不禁想到:难道每个Servlet中都要重复这一段代码吗,这不仅是代码的重复,可以想象更是性能的负担,我们不可能这么做。有经验的小伙伴可能会想到一个办法:提供一个静态方法来获取单例的IoC容器呗。例如这样:
import com.bylearning.config.SpringConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class IoCUtil {
public static ApplicationContext ioc;
static {
ioc = new AnnotationConfigApplicationContext(SpringConfiguration.class);
}
public static ApplicationContext getIoC() {
return ioc;
}
}
但是我们根据Java类加载顺序知道,静态代码块会在类第一次被加载的时候自动执行,这意味着创建容器的耗时操作将落在第一次被访问的Servlet时,这似乎也不太优雅。
解决办法:还记得我们在介绍Java Web阶段简单提及的三大组件之一——Listener(回顾)。监听器中其中有一个ServletContextListener就是监听ServletContext对象的创建与销毁。因此我们在启动Web项目时,会自动执行该监听器的contextInitialized()方法。我们把创建容器的逻辑放在这个方法里很优雅了。
监听类中创建IoC容器:
import com.bylearning.config.SpringConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class ContextLoaderListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("listener init...");
ApplicationContext ioc = new AnnotationConfigApplicationContext(SpringConfiguration.class);
ServletContext servletContext = servletContextEvent.getServletContext();
servletContext.setAttribute("ioc", ioc);
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
提供一个工具类:
import org.springframework.context.ApplicationContext;
import javax.servlet.ServletContext;
public class WebApplicationContextUtils {
public static ApplicationContext getWebApplicationContext(ServletContext servletContext) {
ApplicationContext ioc = (ApplicationContext) servletContext.getAttribute("ioc");
return ioc;
}
}
改造Servlet获取Service层对象:
import com.bylearning.WebApplicationContextUtils;
import com.bylearning.service.UserService;
import org.springframework.context.ApplicationContext;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/demo")
public class DemoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
ApplicationContext ioc = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
UserService us = ioc.getBean(UserService.class);
us.saveUser();
}
}
四、Spring为我们做好了——Spring集成Web
如此典型的优化动作,Spring作为一个优秀的框架,当然为我们做好了。于是我们要开始介绍Spring集成Web。
1、引入依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.1</version>
</dependency>
2、使用spring-web依赖中提供的工具类。我们此时删除import,重新引入时发现了两个工具类供我们选择,一个是我们在上一步中自己实现的,下一个是spring-web提供的。我们选择下面那一个。
3、在web.xml里配置监听类。不用我们自己在定义监听类了,只需要在web.xml里配置好,并将Spring配置文件作为全局参数配置好。前面我们特意设计将我们自定义的监听类与spring-web提供的监听类名称一致。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:applicationContext.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
4、Spring配置文件。进行包扫描即可
<?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:component-scan base-package="com.bylearning"/>
</beans>
五、总结
至此,我们便完成了Spring集成Web的介绍,我们可以看到我们只需要完成监听器的配置与初始参数的配置,接着就可以利用工具类获取到IoC容器了。接下来,我们将为读者继续介绍SpringMVC的内容。
读者在阅读第三、四节时可能会有一个疑问:这里为什么是用一个Servlet来充当Web层呢,这和当下流行的Controller是什么关系呢?因为我们是从原生的Java Web开发过渡而来,因此采用在Java Web开发中常用的继承HttpServlet接口来承接客户端请求,下面我们将为读者介绍SpringMVC的内容,这将与大家熟知的Controller联系起来,请读者继续阅读SpringMVC-基本概念。