67、web整合Spring | 三层架构回顾
三层架构
MVC :
JavaEE 中的三层架构,是为了实现代码逻辑解耦的经典设计模式:
web.xml 方式
现有项目工程如下:
-
web.xml的编写
与spring整合的servlet,其 web.xml 该 什么时机加载呢?怎么把 Spring 的配置文件spring-web.xml加载上来呢?加载完成后应该放在哪儿呢?
-
Web 容器启动时就初始化。因为 Web 容器启动的过程中服务是不对外开放的,只有等应用初始化完成后外界才能访问得到。
-
借助 Listener 加载配置文件。Servlet 的三个核心组件 Servlet 、Filter 、Listener ,想都不用想肯定是 Listener 最合适,因为前两者触发的时机都比较靠后,而选择合适的 Listener 可以在应用刚开始初始化的时候就触发。
-
在 SpringFramework 中,其实它内置了一个监听器,就是给
web.xml中提供 IOC 容器初始化时机的,它叫ContextLoaderListener。所以我们可以在web.xml中配置这样一个监听器:<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>又因它默认监听的是
/WEB-INF/applicationContext.xml文件,所以在web.xml中修改目标文件路径:<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-web.xml</param-value> </context-param>
-
-
IOC 容器已经被放到 application 域中,也就是
ServletContext。 Web 中的四大作用域分别是 pageContext 、request 、session 、application ,应放在 application 作用域。因为 IOC 容器本来就是对应着一个应用的,只有 application 域才能在任何位置都获取到,不限制IOC的作用。- 从进入 Servlet 开始后,任意位置都能拿到
ServletContext。
- 从进入 Servlet 开始后,任意位置都能拿到
-
-
servlet的编写
UserServlet 需要调用 UserService的服务:
@WebServlet(urlPatterns = "/user") public class UserServlet extends HttpServlet { private UserService userService; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String user = userService.get(); resp.getWriter().println(user); } }上面的写法中,userService 会报空指针异常的。所以
UserServlet如何才能拿到UserService呢?什么时机拿为好呢?-
应该重写
HttpServlet的 父类GenericServlet的init方法(Servlet 的生命周期),这个方法中有一个ServletConfig,可以从中取到ServletContext。private UserService userService; @Override public void init(ServletConfig config) throws ServletException { super.init(config); ServletContext servletContext = config.getServletContext(); // 法一:按照类名一个个 getBean WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext); this.userService = ctx.getBean(UserService.class); } -
对 line10-line11 的改进:不用 ctx.getBean 方法,使用 注解依赖注入。防止 Servlet 依赖的类太多导致的编程复杂性。
@Autowired private UserService userService; @Override public void init(ServletConfig config) throws ServletException { super.init(config); ServletContext servletContext = config.getServletContext(); // 法二:自动获取所有 bean SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this, servletContext); }
-
注解方式 (Servlet 3.0 规范)
使用注解方式需要将 web.xml 中的内容全部注释掉。 servlet的编写同上。
-
Servlet 3.0 规范中,
ServletContainerInitializer就是web.xml的替代,加载时机也是在项目的初始化阶段。- ServletContainerInitializer 这个接口通常会配合 @HandlesTypes 注解一起使用,这个注解中可以传入一些我们所需要的接口 / 抽象类,支持 Servlet 3.0 规范的 Web 容器会在容器启动项目初始化时,把这些接口 / 抽象类的实现类全部都找出来,整合为一个
Set,传入ServletContainerInitializer的onStartup方法参数中,这样我们就可以在 onStartUp 中拿到这些实现类,随机反射创建调用等等的动作。
- ServletContainerInitializer 这个接口通常会配合 @HandlesTypes 注解一起使用,这个注解中可以传入一些我们所需要的接口 / 抽象类,支持 Servlet 3.0 规范的 Web 容器会在容器启动项目初始化时,把这些接口 / 抽象类的实现类全部都找出来,整合为一个
-
Spring就支持Servlet3.0规范。
*
spring-web*的 jar 包中,就可以找到这个/META-INF/services/javax.servlet.ServletContainerInitializer文件,里面定义了org.springframework.web.SpringServletContainerInitializer这个类,它需要的实现类的接口类型是WebApplicationInitializer。@HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer{}而 SpringFramework 就将
WebApplicationInitializer类型封装在了AbstractContextLoaderInitializer抽象类中,这里面实现了大部分逻辑,我们只需要实现抽象类,并实现相应方法(如创建 IOC 容器:覆写createRootApplicationContext()方法,并返回一个IOC容器)。
68、SpringWebMvc简单实例
SpringWebMvc 是基于 Servlet 规范之上实现的,所以要运行基于 SpringWebMvc 的项目,需要 Servlet 容器服务器支撑(Tomcat 、Jetty 等)。
-
控制器:
对于 SpringWebMvc 而言,只要标注了
@Controller注解的类,扫描到后都算控制器(类似于 Servlet 的概念)。// 当浏览器请求 /demo 时,跳转到 demo.jsp 页面 @Controller public class DemoController { @RequestMapping("/demo") public String demo(){ return "demo"; } }@RequestMapping注解表示了它要监听的请求 uri ,方法的返回值是 String, 代表要跳转的页面,返回值就是页面的相对路径(无需加 .jsp 的后缀) -
编写 jsp 文件
-
编写 spring-mvc.xml 配置文件 (resources/spring-mvc.xml)
负责:开启组件扫描、引入视图解析器,分别负责找到bean、找到jsp文件
<!-- 开启组件扫描 --> <context:component-scan base-package="num68"/> <!-- 设置jsp文件的前缀、后缀 --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/" /> <property name="suffix" value=".jsp" /> </bean>
DispatcherServlet
继续上面的实例——
-
编写 web.xml 文件 (webapp/WEB-INF/web.xml)
SpringWebMvc 的核心控制器是一个 Servlet ,它的名称叫
DispatcherServlet。<servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>-
关于拦截:
- 拦截 / ,它代表的含义是:拦截所有请求、静态资源,但不拦截 .jsp 。
- 拦截 /* ,它的含义是:拦截所有请求、静态资源、.jsp 页面。所以简单的来说, / 会额外拦截 .jsp 页面*。
-
关于spring配置文件的加载:
- 在 项 内部增加 项。
-
实践问题:名为 dispatcherServlet 的 servlet 和 servlet-mapping 映射不上
-
-
使用servlet3.0规范,将web.xml文件替换为注解实现
上一章借助的是 SpringWeb 提供的抽象类:
AbstractContextLoaderInitializer。在引入 SpringWebMvc 后,这里面又多了一些新的抽象类。
-
进行注解驱动开发时:通常用
AbstractAnnotationConfigDispatcherServletInitializer类;- createServletApplicationContext(),用来创建WebApplicationContext,是 servlet 容器。注意之前的createRootApplicationContext()是创建的 root 容器。
- getServletMappings(),用来声明
DispatcherServlet拦截的路径。
-
xml 配置文件与注解驱动混合使用时:使用
AbstractDispatcherServletInitializer类 。- getRootConfigClasses(); 返回root容器配置,以对象数组形式返回
- getServletConfigClasses(); 返回servlet容器配置,以对象数组形式返回
-
根容器与子容器
每个应用需要配置自己的非共享的控制层、视图层以及业务模型组件;而那些共享的业务模型组件只需要配置一次,每个应用就都可以使用。
因此,每个应用都需要一个独立的Spring IoC容器(我们把它叫做Servlet IoC)来配置和管理自己的组件,还需要一个整个Servlet容器级别的Spring IoC容器(我们把它叫做Root IoC)来配置和管理所有应用共享的组件。
很自然的,如果一个DispatcherServlet在自己的Servlet IoC容器中没有找到需要的组件,那么它就应该去Root IoC容器找需要的组件;如果找到了,就不需要再去Root IoC容器找了。
也就是说,一个DispatcherServlet应该指向两个Spring IoC容器,每个DispatcherServlet都指向了一个自己的Servlet IoC容器以及Root IoC容器:
注意:在进行包扫描的时候,Servlet 子容器只能扫描 @Controller 注解,而不能扫描 @Service 、@Repository 等注解,否则会导致子容器中存在 Service 而不会去父容器中寻找,从而引发一些问题(如事务失效、AOP 增强失效等)。
69、WebMvc 与 Dao 整合的两种方式
\