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
实现类的全类名;
实际效果:
自定义的实现类:
//容器启动的时候会将@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容器推荐使用父子容器的概念:
父容器的配置类:
子容器的配置类:
@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是正常的:
但是在Service层注入,启动的时候就会报错了:
报错信息:
结论:
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;
}
七、Filter的执行顺序问题
在web.xml中,我们知道,执行顺序是谁在前边执行谁。
Servlet容器中:
servlet容器是按照Filter的类名按照自然顺序排序的。什么意思呢?比如我有两个Filter:UserLoginFilter
和ApiLog
。因为这两个文件的首字母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