67-75 SpringMVC基础

221 阅读6分钟

67、web整合Spring | 三层架构回顾

三层架构

MVC :

img

JavaEE 中的三层架构,是为了实现代码逻辑解耦的经典设计模式:

img

web.xml 方式

现有项目工程如下:

image-20210813141410706.png

  • 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的编写

    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 的 父类 GenericServletinit 方法(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 ,传入 ServletContainerInitializeronStartup 方法参数中,这样我们就可以在 onStartUp 中拿到这些实现类,随机反射创建调用等等的动作。
  • 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 映射不上

      image-20210816095122525.png

  • 使用servlet3.0规范,将web.xml文件替换为注解实现

    上一章借助的是 SpringWeb 提供的抽象类: AbstractContextLoaderInitializer

    在引入 SpringWebMvc 后,这里面又多了一些新的抽象类。

    • 进行注解驱动开发时:通常用 AbstractAnnotationConfigDispatcherServletInitializer 类;

      • createServletApplicationContext(),用来创建WebApplicationContext,是 servlet 容器。注意之前的createRootApplicationContext()是创建的 root 容器。
      • getServletMappings(),用来声明 DispatcherServlet 拦截的路径。
    • xml 配置文件与注解驱动混合使用时:使用 AbstractDispatcherServletInitializer类 。

      • getRootConfigClasses(); 返回root容器配置,以对象数组形式返回
      • getServletConfigClasses(); 返回servlet容器配置,以对象数组形式返回

根容器与子容器

blog.csdn.net/weixin_4363…

blog.csdn.net/weixin_3439…

每个应用需要配置自己的非共享的控制层、视图层以及业务模型组件;而那些共享的业务模型组件只需要配置一次,每个应用就都可以使用。

因此,每个应用都需要一个独立的Spring IoC容器(我们把它叫做Servlet IoC)来配置和管理自己的组件,还需要一个整个Servlet容器级别的Spring IoC容器(我们把它叫做Root IoC)来配置和管理所有应用共享的组件。

很自然的,如果一个DispatcherServlet在自己的Servlet IoC容器中没有找到需要的组件,那么它就应该去Root IoC容器找需要的组件;如果找到了,就不需要再去Root IoC容器找了。

也就是说,一个DispatcherServlet应该指向两个Spring IoC容器,每个DispatcherServlet都指向了一个自己的Servlet IoC容器以及Root IoC容器: img

注意:在进行包扫描的时候,Servlet 子容器只能扫描 @Controller 注解,而不能扫描 @Service@Repository 等注解,否则会导致子容器中存在 Service 而不会去父容器中寻找,从而引发一些问题(如事务失效、AOP 增强失效等)。

69、WebMvc 与 Dao 整合的两种方式

\