以web的原生注解与springboot定制化组件
- 一.原生web注解和用spring方式注入的区别
- 1.使用原生注解servlet API
- 1.1 servlet用法
- 1.2 listener用法
- 1.3 filter用法
- 2.使用spring方式注入Servlet组件
- 3.dispatcherservlet如何注入进来(分析其自动配置类)
- 二.嵌入式servlet容器
- 1. 切换嵌入式servlet容器
- 1.1 原理
- 1.2 切换嵌入式web服务器实操(tomcat切换为undertow)
- 2. 定制servlet容器
- 2.1 修改配置文件
- 2.2 直接自定义ConfigurableServletWebServerFactory
- 2.3 实现WebServerFactoryCustomizer
- 三.定制化原理
- 1.常见方式
- 1.1 修改配置文件
- 1.2 xxxxCustomizer(示例见上面2.3 实现WebServerFactoryCustomizer )
- 1.3 web应用实现webmvcconfigurer来定制web功能
- 1.4 @EnableWebMvc配合实现WebMvcConfigurer的配置类
一.原生web注解和用spring方式注入的区别
1.使用原生注解servlet API
- 在主类上添加servlet组件扫描注解@ServletComponentScan,在其中指定组件的位置,示例如下:
@ServletComponentScan(basePackages = "com.example.admin")
@SpringBootApplication
public class webAdmin {
public static void main(String[] args) {
SpringApplication.run(webAdmin.class,args);
}
}
1.1 servlet用法
- 创建自定义servlet类并继承HttpServlet,然后添加@WebServlet注解,其中指定访问地址,可调用HttpServlet中的方法进行操作,示例如下:
@WebServlet(urlPatterns = "/myServlet")
public class myServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("5533");
}
}
1.2 listener用法
- 创建自定义listener类作为ServletContextListener的实现类,然后添加@WebListener注解,可调用ServletContextListener中的方法进行实现,示例如下:
@WebListener
public class myListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("监听初始化");
}
}
1.3 filter用法
- 创建自定义filter类作为Filter的实现类,然后添加@WebFilter注解,指定过滤地址,然后可调用Filter中的方法进行实现,示例如下:
@WebFilter(urlPatterns = "/image/*")
public class myFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("初始化完成");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("工作");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
System.out.println("销毁");
}
}
2.使用spring方式注入Servlet组件
- 去掉三个组件类的web原生注解,然后创建配置类,以xxRegistrationBean类型的形式将这三个组件注入spring容器中
@Configuration
public class ServletConfig {
@Bean
public ServletRegistrationBean myServlet(){
myServlet servlet = new myServlet();
return new ServletRegistrationBean(servlet,"/my");
}
@Bean
public FilterRegistrationBean myFilter(){
myFilter filter = new myFilter();
//filterRegistrationBean = new FilterRegistrationBean(filter,myServlet());针对myServlet地址
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(filter, myServlet());
filterRegistrationBean.setUrlPatterns(Arrays.asList("/my"));//针对自定义地址
return filterRegistrationBean;
}
@Bean
public ServletListenerRegistrationBean myListener(){
myListener listener = new myListener();
return new ServletListenerRegistrationBean(listener);
}
}
3.dispatcherservlet如何注入进来(分析其自动配置类)
@Bean(
name = {"dispatcherServlet"}
)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
return dispatcherServlet;
}
通过以上源码可知,这个自动配置类把dispatcherservlet属性与WebMvcProperties进行绑定,对应配置文件是spring.mvc,如下所示:
@ConfigurationProperties(
prefix = "spring.mvc"
)
public class WebMvcProperties
又通过ServletRegistrationBean将dispatcherservlet注入进来,如下:
public class DispatcherServletRegistrationBean extends ServletRegistrationBean<DispatcherServlet> implements DispatcherServletPath {
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath());
registration.setName("dispatcherServlet");
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
该注册dispatcherservlet的方法的第一个参数取出之前注入容器的dispatcherservlet,由于之前将这个dispatcherservlet与webmvcproperties进行绑定,然后就可以通过第一行代码,将获取webmvcproperties的path值与dispatcherservlet进行绑定。这个path默认为“/”,可以通过绑定的spring.mvc.servlet.path进行修改。
- 为什么以上代码示例中存在tomcat-servlet和dispatcherservlet两个servlet时,执行的tomcat-servlet?
原因:由于示例中的myServlet针对的路径是/my,而dispatcherservlet默认针对/,由于多个servlet存在时遵循精确优先原则,而myServlet比dispatcherservlet针对的路径更精确,所以执行了它。
二.嵌入式servlet容器
1. 切换嵌入式servlet容器
1.1 原理
- web应用创建一个web的ioc容器ServletWebServerApplicationContext
- 启动时从上到下依次执行以下三个方法,寻找ServletWebServerFactory(web服务器工厂–生产web服务器)
- springboot底层默认有三种web服务器工厂:
JettyServletWebServerFactory
TomcatServletWebServerFactory
UndertowServletWebServerFactory
protected void onRefresh() {
super.onRefresh();
try {
this.createWebServer();
} catch (Throwable var2) {
throw new ApplicationContextException("Unable to start web server", var2);
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = this.getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = this.getWebServerFactory();
this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
this.getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer));
this.getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer));
} else if (servletContext != null) {
try {
this.getSelfInitializer().onStartup(servletContext);
} catch (ServletException var4) {
throw new ApplicationContextException("Cannot initialize servlet context", var4);
}
}
this.initPropertySources();
}
protected ServletWebServerFactory getWebServerFactory() {
String[] beanNames = this.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
if (beanNames.length == 0) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.");
} else if (beanNames.length > 1) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
} else {
return (ServletWebServerFactory)this.getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
}
- 由上图第三个方法可知,目的是获取ServletWebServerFactory,即用来生产webserver。根据代码逻辑,只有当ioc容器存在一个ServletWebServerFactory类型的工厂才行。而由以下自动配置类的注解可知,Embeddedxx类型的三种webserver已经注入了容器中,深入Embeddedxx类型看到第二张图的@Bean就是证据,所以返回了容器中的TomcatServletWebServerFactory。
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration {
- 继续看以上代码createWebServer方法中调用的getWebServer方法,而这个方法属于TomcatServletWebServerFactory,以下代码具体实现可以看出,它创建tomcat服务的能力。
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
this.customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
this.configureEngine(tomcat.getEngine());
Iterator var5 = this.additionalTomcatConnectors.iterator();
while(var5.hasNext()) {
Connector additionalConnector = (Connector)var5.next();
tomcat.getService().addConnector(additionalConnector);
}
this.prepareContext(tomcat.getHost(), initializers);
return this.getTomcatWebServer(tomcat);
}
- 以上代码最终返回的是webServer(接口)的实现类tomcatwebserver,而tomcatwebserver的构造器如下代码,调用了初始化方法,进而通过初始化方法中的this.tomcat.start方法启动。以下第一张图说明了可切换web服务器。
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
this.monitor = new Object();
this.serviceConnectors = new HashMap();
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
this.gracefulShutdown = shutdown == Shutdown.GRACEFUL ? new GracefulShutdown(tomcat) : null;
this.initialize();
}
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false));
synchronized(this.monitor) {
try {
this.addInstanceIdToEngineName();
Context context = this.findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && "start".equals(event.getType())) {
this.removeServiceConnectors();
}
});
this.tomcat.start();
this.rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(), this.getClass().getClassLoader());
} catch (NamingException var5) {
}
this.startDaemonAwaitThread();
} catch (Exception var6) {
this.stopSilently();
this.destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", var6);
}
}
}
1.2 切换嵌入式web服务器实操(tomcat切换为undertow)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<exclusion>
<exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
</dependencies>
2. 定制servlet容器
2.1 修改配置文件
- 由下图代码ServletWebServerFactoryAutoConfiguration的注解可以看到绑定了ServerProperties,而由ServerProperties的注解可知配置前缀是server,则可以通过修改配置server.xxx来进行定制。
@EnableConfigurationProperties({ServerProperties.class})
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration {....
}
@ConfigurationProperties(
prefix = "server",
ignoreUnknownFields = true
)
public class ServerProperties {...}
2.2 直接自定义ConfigurableServletWebServerFactory
- 由于web服务器的生成主要用到了相应的工厂ServletWebServerFactory,而ConfigurableServletWebServerFactory是它的子接口,可以通过实现这个类型的方法,进行自定义参数设置。示例如下
@Bean
public ConfigurableServletWebServerFactory webServerFactory(){
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.setPort(xx);
factory.setSession(xx,TimeUnit.MINUTES);
factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND,"/notfound.html"));
return factory;
}
2.3 实现WebServerFactoryCustomizer
- 分析:由以下第二个方法可知,此接口是ServletWebServerFactoryCustomizer类型的要实现的,而通过ServletWebServerFactoryCustomizer的customize方法(自行源码查阅)和以下第一个方法可知,其作用是将配置文件的值与工厂ServletWebServerFactory进行绑定
ServerProperties就是配置文件的类
@Bean
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
return new ServletWebServerFactoryCustomizer(serverProperties);
}
public class ServletWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {...}
- 实现代码示例如下:
@Component
public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory server) {
server.setPort(9000);
}
}
三.定制化原理
1.常见方式
1.1 修改配置文件
- 原理分析套路:
1.场景starter
2.xxxxAutoConfiguration
3.导入xxx组件
4.绑定xxxProperties
5.绑定配置文件项
1.2 xxxxCustomizer(示例见上面2.3 实现WebServerFactoryCustomizer )
1.3 web应用实现webmvcconfigurer来定制web功能
- 官方文档
- 示例:先通过实现HandlerInterceptor接口来定制一个loginInterceptors,然后通过以下代码进行注册。
@Configuration
public class adminWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new loginInterceptors())
.addPathPatterns("/**")
.excludePathPatterns("/","/login");
}
}
1.4 @EnableWebMvc配合实现WebMvcConfigurer的配置类
- 此组合会实现全面接管springmvc,则所有规则全部需要自己重新配置,原来的功能失效。
- 原理
1.原本依赖于WebMvcAutoConfiguration,使得默认的springmvc能自动配置许多web相关功能类,包括静态资源,欢迎页等
2.一旦使用@EnableWebMvc,该注解添加了@Import({DelegatingWebMvcConfiguration.class})
3.DelegatingWebMvcConfiguration添加了全部的WebMvcConfigurer配置,如下所示,说明凡是向容器中添加了实现WebMvcConfigurer接口的配置类都能生效。
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
4.对于DelegatingWebMvcConfiguration的父类WebMvcConfigurationSupport,则是配置更底层的组件。包括RequestMappingHandlerMapping,及其依赖的组件(方法参数)都可以从容器中取。如下所示
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {...}
5.由于WebMvcAutoConfiguration标注了如下注解,从而导致如果使用@EnableWebMvc会使这个自动配置类失效。
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})