Spring注解驱动开发---Servlet 3.0整合Spring MVC

服务器:运行本war包的web容器为:tomcat-8.0(最高支持到了Servlet3.1~)

一、pom.xml文件

<?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.fsx</groupId>
	<artifactId>demo-war</artifactId>
	<version>1.0-SNAPSHOT</version>
	<packaging>war</packaging>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>5.0.6.RELEASE</version>
		</dependency>

		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<scope>provided</scope>
			<version>1.18.4</version>
		</dependency>

		<!-- 记录log日志  logback-core并不需要显示导入-->
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>1.2.3</version>
		</dependency>

		<!-- Spring MVC自动数据封装依赖的包  否则可能出现下面的错误,若使用@RequestBody的时候 -->
		<!-- Content type 'application/json' not supported 当然还有其余配置,原理了解-->

		<!-- 此处需要导入databind包即可, jackson-annotations、jackson-core都不需要显示自己的导入了-->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.9.8</version>
		</dependency>
		<!-- fastjson -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.57</version>
		</dependency>

	</dependencies>

	<build>
		<plugins>
			<!-- 该插件是为了没有web.xml情况下,打war包。编译不要报错 -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-war-plugin</artifactId>
				<version>2.6</version>
				<configuration>
					<failOnMissingWebXml>false</failOnMissingWebXml>
				</configuration>
			</plugin>

			<!-- 编译环境在1.8编译 -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.0</version>
				<configuration>
					<source>${java.version}</source>
					<target>${java.version}</target>
					<compilerVersion>${java.version}</compilerVersion>
					<encoding>${project.build.sourceEncoding}</encoding>
				</configuration>
			</plugin>
		</plugins>


	   <!--
			directory:属性指定资源文件放置的目录。
			includes:包含哪些配置文件(.class文件不用写)
			filtering:如果设置为false的话,则表示上文的filters配置失效;如果设置为true,则会根据${env}.properties里面的键值对来
					   填充includes指定文件里的${xxxx}占位符(若不做环境区分,一般就是false即可)
		-->
		<resources>
			<resource>
				<directory>src/main/java</directory>
				<includes>
					<include>**/*.properties</include>
					<include>**/*.xml</include>
					<include>**/*.tld</include>
				</includes>
				<filtering>false</filtering>
			</resource>
		</resources>

	</build>
</project>

二、日志配置

<configuration scan="true" scanPeriod="60 seconds" debug="false">
	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
		</encoder>
	</appender>

	<root level="INFO">
		<appender-ref ref="STDOUT"/>
	</root>
</configuration>

可以配置Servlet进行启动了

/**
 * @author fangshixiang
 * @description
 * @date 2019-02-16 22:04
 */
@WebServlet(urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		resp.getWriter().write("hello servlet...");
	}
}

三、SpringMVC容器(子容器)整合其余模块

之前:通过web.xml配置文件

Servlet3.0:给我们提供的特别特别重要的一个类ServletContainerInitializer来整个其它模块组件

主要功能:

  • Shared libraries(共享库)
  • runtimes pluggability(运行时插件能力)

Servlet容器启动会扫描当前应用里面每一个jar包ServletContainerInitializer的实现

coder可以自己提供ServletContainerInitializer的实现类;然后自己书写逻辑。但是,但是,但是要记住,一定要必须绑定在,META-INF/services/javax.servlet.ServletContainerInitializer这个文件里,文件内容为就是ServletContainerInitializer实现类的全类名;
实际效果:
image

自定义的实现类:

//容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口等)传递过来;
@HandlesTypes(value = {HelloService.class})
public class MyServletContainerInitializer implements ServletContainerInitializer {

	/**
	 * 应用启动的时候,会运行onStartup方法;
	 * <p>
	 * Set<Class<?>> c:感兴趣的类型的所有子类型;
	 * ServletContext ctx:代表当前Web应用的ServletContext;一个Web应用一个ServletContext;
	 * <p>
	 */
	@Override
	public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
		//这里的c会把所有我们感兴趣的类型都拿到
		System.out.println("感兴趣的类型:");
		for (Class<?> claz : c) {
			System.out.println(claz);
		}

	   /*
			//==========================编码形式注册三大组件============================
			//必须在项目启动的时候来添加(为了安全考虑,若已经启动完成再添加,是无效的)
			//注册组件  ServletRegistration
			//ServletRegistration.Dynamic servlet = ctx.addServlet("userServlet", new UserServlet());
			//配置servlet的映射信息
			//servlet.addMapping("/user");
			//注册Listener
			//ctx.addListener(UserListener.class);
			//
			//注册Filter  FilterRegistration
			//FilterRegistration.Dynamic filter = ctx.addFilter("userFilter", UserFilter.class);
			//配置Filter的映射信息
			//filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
		*/
	}

}

四、整合SpringMVC容器(子容器)

Spring容器推荐使用父子容器的概念:
image

父容器的配置类:

image

子容器的配置类:

image

@RestControllerAdvice是Spring4.3后提供的注解。@ControllerAdvice是Spring3.2提供的
它俩的区别就像@Controller和@RestController的区别。(也就是说@RestControllerAdvice``可以省略@ResponseBody`不用写了

useDefaultFilters默认值为true,表示默认情况下@Component、@Repository、@Service、@Controller都会扫描
useDefaultFilters=false加上includeFilters我们就可以只扫描指定的组件了,比如Spring MVC的web子容器只扫描Controller组件
excludeFilters的时候,就不需要去设置useDefaultFilters=false,这样子我们直接排除掉即可哟~

特别注意:useDefaultFilters的正确使用,不要造成重复扫描。否则很有可能造成事务不生效,并且你还非常不好定位这个错误。

然后我们自己来实现AbstractAnnotationConfigDispatcherServletInitializer一个初始化实体类:

/**
 * 自己实现 基于注解驱动的ServletInitializer来初始化DispatcherServlet
 */
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

	/**
	 * 根容器的配置类;(Spring的配置文件)   父容器;
	 */
	@Override
	protected Class<?>[] getRootConfigClasses() {
		return new Class<?>[]{RootConfig.class};
	}

	/**
	 * web容器的配置类(SpringMVC配置文件)  子容器;
	 */
	@Override
	protected Class<?>[] getServletConfigClasses() {
		return new Class<?>[]{AppConfig.class};
	}

	//获取DispatcherServlet的映射信息
	// 注意: /:拦截所有请求(包括静态资源(xx.js,xx.png)),但是不包括*.jsp;
	//  /*:拦截所有请求;连*.jsp页面都拦截;jsp页面是tomcat的jsp引擎解析的;
	@Override
	protected String[] getServletMappings() {
		return new String[]{"/"};
	}

	// 若你想定制化父类的一些默认行为  这里都是可以复写父类的protected方法的~~~~
	// Spring MVC也推荐你这么干~
	@Override
	protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
		DispatcherServlet dispatcherServlet = (DispatcherServlet) super.createDispatcherServlet(servletAppContext);
		// dispatcherServlet.setDetectAllHandlerAdapters(false); 
		return dispatcherServlet;
	}

}

测试类:

@Controller
public class HelloController {

	@Autowired
	HelloService helloService;

	@ResponseBody
	@RequestMapping("/hello")
	public String hello() {
		System.out.println(helloService); //com.fsx.service.HelloServiceImpl@512663b0
		return "hello...";
	}
}

这样我们就可以正常访问controller的请求了。

五、父子容器关系

我们会发现在Controller层注入这两个Bean是正常的:
image

但是在Service层注入,启动的时候就会报错了:
image

报错信息:
image

结论:

1、父子容器的关系就行内部类的关系一样。子容器能得到父容器的Bean,但是父容器得不到子容器的Bean
2、父子容器中,属性值都不是互通的。@Value注入的时候需要注意一下子~

六、定制SpringMVC

之前我们使用xml文件的时候,我们可以配置Spring MVC等相关选项:
比如视图解析器、视图映射、静态资源映射、拦截器。。。

在这里实现之前的效果:

  • 首先:在配置文件里加上注解@EnableWebMvc:开启SpringMVC定制配置功能;

  • 其次: 实现WebMvcConfigurer接口。通过这个接口我们可以发现,里面有很多方法,但大多数情况下我们并不需要配置这么多项,因此Spring MVC也考虑到了这一点,提供给我们一个WebMvcConfigurerAdapter来extends就行,Adapter都是空实现~,这样我们需要配置什么,复写对应方法就行。

    @EnableWebMvc
    @Configuration //一定要说明这个文件是个配置文件
    public class WebMvcConfig implements WebMvcConfigurer {

      //视图解析器
      @Override
      public void configureViewResolvers(ViewResolverRegistry registry) {
      	//默认所有的页面都从 /WEB-INF/ xxx .jsp
      	//registry.jsp();
      	registry.jsp("/WEB-INF/views/", ".jsp");
      }
    
      // 开启静态资源的请求转发到默认servlet上,不配置页面报错404,(默认servlet不是DispatcherServlet!理解的)
      @Override
      public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
      	configurer.enable();
      }
    
      //自定义添加拦截器=========这个比较常用
      @Override
      public void addInterceptors(InterceptorRegistry registry) {
      	//registry.addInterceptor(new MyFirstInterceptor()).addPathPatterns("/**");
      }
    

    }

这样我们就可以通过此配置文件,个性化定制我们的Spring MVC了。

其中,关于自定义视图解析的自定义配置。此处还有一种方法是直接向容器里面注册Bean即可:

//自定义一个视图解析器
@Bean
public ViewResolver viewResolver() {
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix("WEB-INF/views/");
    resolver.setSuffix(".html");
    resolver.setExposeContextBeansAsAttributes(true);
    return resolver;
}

image

七、Filter的执行顺序问题

在web.xml中,我们知道,执行顺序是谁在前边执行谁。

Servlet容器中:

servlet容器是按照Filter的类名按照自然顺序排序的。什么意思呢?比如我有两个Filter:UserLoginFilterApiLog。因为这两个文件的首字母A排U之前,所以每次都会限制性ApiLog

那么我们就是想先要执行UserLoginFilter怎么办呢?这里有个小技巧,我们可以这么来写即可:

Filter0_UserLogin.java
Filter1_ApiLog.java

SpringBoot中:
我们可以用@Order注解:

@Bean
@Order(Integer.MAX_VALUE)

也可这么来:

registration.setOrder(Integer.MAX_VALUE);

Spring boot 会按照order值的大小,从小到大的顺序来依次过滤。也就是说,数字越小,越先执行

八、拓展:

WebMvcConfigurationSupport:
https://www.jianshu.com/p/d47a09532de7
结论:
最佳实践还是继承WebMvcConfigurerAdapter(或直接实现接口WebMvcConfigurer),只不过要多加一个@EnableWebMvc注解而已。

备注:若是SpringBoot环境,请不要加@EnableWebMvc注解,因为springboot已经实例化了WebMvcConfigurationSupport,如果添加了该注解,默认的WebMvcConfigurationSupport配置类是不会生效的

整理自:
https://blog.csdn.net/f641385712/article/details/87474907

posted @ 2021-09-22 17:36  光一  阅读(111)  评论(0)    收藏  举报