【精选】Spring注解驱动开发第54讲

您所在的位置:网站首页 spring和servlet整合 【精选】Spring注解驱动开发第54讲

【精选】Spring注解驱动开发第54讲

2023-11-19 03:07| 来源: 网络整理| 查看: 265

写在前面

在上一讲中,我们分析清楚了Servlet 3.0整合Spring MVC的底层原理。接下来,我们就要以注解的方式将Spring MVC整合进来了。其实,大家完全可以参考Spring的官方文档的这一块的写法。

在这里插入图片描述

因为这块的写法跟我们的分析是一模一样的。

Servlet 3.0与Spring MVC的整合

首先,我们来编写一个类,例如MyWebAppInitializer,来继承AbstractAnnotationConfigDispatcherServletInitializer这个抽象类,一开始我们写成下面这样。

package com.meimeixia; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class[] getRootConfigClasses() { // TODO Auto-generated method stub return null; } @Override protected Class[] getServletConfigClasses() { // TODO Auto-generated method stub return null; } @Override protected String[] getServletMappings() { // TODO Auto-generated method stub return null; } }

按照我们之前的分析,以上该类会在web容器启动的时候创建对象,而且在整个创建对象的过程中,会来调用相应方法来初始化容器以及前端控制器。

我们自己写的MyWebAppInitializer类继承了AbstractAnnotationConfigDispatcherServletInitializer抽象类之后,发现它里面需要重写三个抽象方法,下面我们就来详细说一下它们。

getRootConfigClasses方法:它是来获取根容器的配置类的,该配置类就类似于我们以前经常写的Spring的配置文件,而且我们以前是利用监听器的方式来读取Spring的配置文件的哟~,还记得吗?然后,就能创建出一个父容器了

getServletConfigClasses方法:它是来获取web容器的配置类的,该配置类就类似于我们以前经常写的Spring MVC的配置文件,而且我们以前是利用前端控制器来加载Spring MVC的配置文件的哟~,你还记得吗?然后,就能创建出一个子容器了

getServletMappings方法:它是来获取DispatcherServlet的映射信息的。该方法需要返回一个String[],如果我们返回的是这样一个new String[]{"/"}东东,即:

@Override protected String[] getServletMappings() { // TODO Auto-generated method stub return new String[]{"/"}; }

那么DispatcherServlet就会来拦截所有请求,包括静态资源,比如xxx.js文件、xxx.png等等等等,但是不包括*.jsp,也即不会拦截所有的jsp页面。

如果我们返回的是这样一个new String[]{"/*"}东东,即:

@Override protected String[] getServletMappings() { // TODO Auto-generated method stub return new String[]{"/*"}; }

那么DispatcherServlet就是真正来拦截所有请求了,包括*.jsp,也就是说就连jsp页面都拦截,所以,我们切忌不可写成这样(即/*)。否则的话,jsp页面一旦被Spring MVC拦截,最终极有可能我们就看不到jsp页面了,因为jsp页面是由Tomcat服务器中的jsp引擎来解析的。

也就是说,我们最好是在getServletMappings方法中返回这样一个new String[]{"/"}东东,即:

package com.meimeixia; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; /** * 在web容器启动的时候创建对象,而且在整个创建对象的过程中,会调用相应方法来初始化容器以及前端控制器 * 编写好该类之后,就相当于是在以前我们配置好了web.xml文件 * @author liayun * */ public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { /* * 获取根容器的配置类,该配置类就类似于我们以前经常写的Spring的配置文件,然后创建出一个父容器 */ @Override protected Class[] getRootConfigClasses() { // TODO Auto-generated method stub return null; } /* * 获取web容器的配置类,该配置类就类似于我们以前经常写的Spring MVC的配置文件,然后创建出一个子容器 */ @Override protected Class[] getServletConfigClasses() { // TODO Auto-generated method stub return null; } // 获取DispatcherServlet的映射信息 /* * /:拦截所有请求,包括静态资源,比如xxx.js文件、xxx.png等等等等,但是不包括*.jsp,也即不会拦截所有的jsp页面 * /*:真正来拦截所有请求了,包括*.jsp,也就是说就连jsp页面都拦截 */ @Override protected String[] getServletMappings() { // TODO Auto-generated method stub return new String[]{"/"}; } }

由于我们还需要在getRootConfigClasses和getServletConfigClasses这俩方法中指定两个配置类的位置,所以我们来创建上两个配置类,分别如下:

根容器的配置类,例如RootConfig

package com.meimeixia.config; public class RootConfig { }

web容器的配置类,例如AppConfig

package com.meimeixia.config; public class AppConfig { }

以上这两个配置类最终需要形成父子容器的效果。还要有一点,我需要重点来说明,即AppConfig配置类只来扫描所有的控制器(即Controller),以及和网站功能有关的那些逻辑组件;RootConfig配置类只来扫描所有的业务逻辑核心组件,包括dao层组件、不同的数据源等等,反正它只扫描和业务逻辑相关的组件就哦了。

接下来,我们来完善以上两个配置类。首先,先来完善RootConfig配置类,我们可以使用@ComponentScan注解来指定扫描com.meimeixia包以及子包下的所有组件,而且为了形成父子容器,还必须得排除掉一些组件,那排除掉哪些组件呢?很显然,应该排除掉controller控制层组件,即Controller。所以,我们得通过@ComponentScan注解的excludeFilters()方法按照注解的方式来排除掉所有标注了@Controller注解的组件。

package com.meimeixia.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.stereotype.Controller; // 该配置类相当于Spring的配置文件 // Spring容器不扫描Controller,它是一个父容器 @ComponentScan(value="com.meimeixia",excludeFilters={ @Filter(type=FilterType.ANNOTATION, classes={Controller.class}) }) public class RootConfig { }

然后,再来完善AppConfig配置类,我们同样使用@ComponentScan注解来指定扫描com.meimeixia包以及子包下的所有组件,但是呢,与上面正好相反,这儿只扫描controller控制层组件,即Controller,如此一来就能与上面形成互补配置了。OK,那我们就通过@ComponentScan注解的includeFilters()方法按照注解的方式来指定只扫描controller控制层组件。

package com.meimeixia.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.stereotype.Controller; // 该配置类相当于Spring MVC的配置文件 // Spring MVC容器只扫描Controller,它是一个子容器 // useDefaultFilters=false:禁用默认的过滤规则 @ComponentScan(value="com.meimeixia",includeFilters={ @Filter(type=FilterType.ANNOTATION, classes={Controller.class}) },useDefaultFilters=false) public class AppConfig { }

尤其要注意这一点,在以上配置类中通过@ComponentScan注解的includeFilters()方法来指定只扫描controller控制层组件时,需要禁用掉默认的过滤规则,即必须得加上useDefaultFilters=false这样一个配置。千万记得必须要禁用掉默认的过滤规则哟,否则扫描就不会生效了。

但是,在RootConfig配置类中通过@ComponentScan注解的excludeFilters()方法来指定排除哪些组件时,是不需要对useDefaultFilters进行设置的,因为其默认值就是true,表示默认情况下标注了@Component、@Repository、@Service以及@Controller这些注解的组件都会被扫描,即扫描所有。

接下来,我们就要分别来编写一个controller控制层组件和service业务层组件来进行测试了。首先,编写一个service业务层组件,例如HelloService,如下所示。

package com.meimeixia.service; import org.springframework.stereotype.Service; @Service public class HelloService { public String sayHello(String name) { return "Hello, " + name; } }

然后,编写一个controller控制层组件,例如HelloController,并且我们还可以在该HelloController中注入HelloService组件,来调用其方法。

package com.meimeixia.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.meimeixia.service.HelloService; @Controller public class HelloController { @Autowired HelloService helloService; @ResponseBody @RequestMapping("/hello") public String hello() { String hello = helloService.sayHello("tomcat..."); return hello; } }

从以上HelloController的代码中,我们可以看到它里面的hello方法是来处理hello请求的,而且通过@ResponseBody注解会直接将返回的结果(即字符串)写到浏览器页面中。

现在,我们能不能启动咱们的项目进行测试了呢?还不可以,因为我们还没有在我们自己编写的MyWebAppInitializer类中指定两个配置类的位置。OK,那我们来分别指定一下,如下所示。

package com.meimeixia; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; import com.meimeixia.config.AppConfig; import com.meimeixia.config.RootConfig; /** * 在web容器启动的时候创建对象,而且在整个创建对象的过程中,会调用相应方法来初始化容器以及前端控制器 * 编写好该类之后,就相当于是在以前我们配置好了web.xml文件 * @author liayun * */ public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { /* * 获取根容器的配置类,该配置类就类似于我们以前经常写的Spring的配置文件,然后创建出一个父容器 */ @Override protected Class[] getRootConfigClasses() { // TODO Auto-generated method stub return new Class[]{RootConfig.class}; } /* * 获取web容器的配置类,该配置类就类似于我们以前经常写的Spring MVC的配置文件,然后创建出一个子容器 */ @Override protected Class[] getServletConfigClasses() { // TODO Auto-generated method stub return new Class[]{AppConfig.class}; } // 获取DispatcherServlet的映射信息 /* * /:拦截所有请求,包括静态资源,比如xxx.js文件、xxx.png等等等等,但是不包括*.jsp,也即不会拦截所有的jsp页面 * /*:真正来拦截所有请求了,包括*.jsp,也就是说就连jsp页面都拦截 */ @Override protected String[] getServletMappings() { // TODO Auto-generated method stub return new String[]{"/"}; } }

这就相当于分别来指定Spring配置文件和Spring MVC配置文件的位置。

最后,我们就可以启动项目来进行测试了。项目启动成功之后,我们在浏览器地址栏中输入http://localhost:8080/springmvc-annotation-liayun/hello进行访问,发现浏览器页面上确实打印出来了一串字符串,如下图所示。

在这里插入图片描述

这说明我们controller控制层组件和service业务层组件都起作用了。

至此,使用注解的方式(即配置类的方式)来整合Spring MVC,我们就算是彻底讲完了。

总结

结合前两讲中的内容,我想在这儿做一点总结,不然感觉脑子始终一团浆糊。我们知道web容器(即Tomcat服务器)在启动应用的时候,会扫描当前应用每一个jar包里面的META-INF/services/javax.servlet.ServletContainerInitializer文件中指定的实现类,然后再运行该实现类中的方法。

恰好在spring-web-4.3.11.RELEASE.jar中的META-INF/services/目录里面有一个javax.servlet.ServletContainerInitializer文件,并且在该文件中指定的实现类就是org.springframework.web.SpringServletContainerInitializer,打开该实现类,发现它上面标注了@HandlesTypes(WebApplicationInitializer.class)这样一个注解。

因此,web容器在启动应用的时候,便会来扫描并加载org.springframework.web.SpringServletContainerInitializer实现类,而且会传入我们感兴趣的类型(即WebApplicationInitializer接口)的所有后代类型,最终再运行其onStartup方法。

package org.springframework.web; import java.lang.reflect.Modifier; import java.util.LinkedList; import java.util.List; import java.util.ServiceLoader; import java.util.Set; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.annotation.HandlesTypes; import org.springframework.core.annotation.AnnotationAwareOrderComparator; @HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer { @Override public void onStartup(Set webAppInitializerClasses, ServletContext servletContext) throws ServletException { List initializers = new LinkedList(); if (webAppInitializerClasses != null) { for (Class waiClass : webAppInitializerClasses) { // Be defensive: Some servlet containers provide us with invalid classes, // no matter what @HandlesTypes says... if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) { try { initializers.add((WebApplicationInitializer) waiClass.newInstance()); } catch (Throwable ex) { throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex); } } } } if (initializers.isEmpty()) { servletContext.log("No Spring WebApplicationInitializer types detected on classpath"); return; } servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath"); AnnotationAwareOrderComparator.sort(initializers); for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); } } }

从以上onStartup方法中,我们可以看到它会遍历感兴趣的类型(即WebApplicationInitializer接口)的所有后代类型,然后利用反射技术创建WebApplicationInitializer类型的对象,而我们自定义的MyWebAppInitializer就是WebApplicationInitializer这种类型的。而且创建完之后,都会存储到名为initializers的LinkedList集合中。接着,又会遍历该集合,并调用每一个WebApplicationInitializer对象的onStartup方法。

遍历到每一个WebApplicationInitializer对象之后,调用其onStartup方法,实际上就是先调用其(例如我们自定义的MyWebAppInitializer)最高父类的onStartup方法,创建根容器;然后再调用其次高父类的onStartup方法,创建web容器以及DispatcherServlet;接着,根据其重写的getServletMappings方法来为DispatcherServlet配置映射信息,差不多就是这样了。

如有总结不对的地方,还望指出,我来修改。创作不易,还请多多点赞与评论哟😀



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3