栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring Boot与Web开发(下)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring Boot与Web开发(下)

目录

4.SpringBoot的嵌入式Servlet容器

1.嵌入式Servlet容器配置修改

2.注册servlet三大组件

3.切换其他嵌入式Servlet容器

4.嵌入式Servlet容器自动配置原理

5.使用外部Servlet容器

6.外部Servlet容器启动SpringBoot应用原理

5.SpringBoot作为单体Web应用的使用


4.SpringBoot的嵌入式Servlet容器

Spring 默认的Servlet容器是:Tomcat, 当前SpringBoot 2.3.6 的版本是对应 tomcat9

1.嵌入式Servlet容器配置修改
  • 1.通过全局配置文件修改
    • 如果带了具体的服务器名称则是单独对该服务器进行设置,比如 server.tomcat.xxx 就是专门针对tomcat的配置
    • 可以通过server.xxx 来进行web服务配置, 没有带服务器名称的则是通用配置
server.port=8080
server.servlet.context-path=/tomcat
  • 2.通过WebServerFactoryCustomizer的Bean修改
    • 修改server.xxx 配置的相关内容
    • 会跟配置文件形成互补

@Component
public class CustomizationBean implements WebServerFactoryCustomizer {

    @Override
    public void customize(ConfigurableServletWebServerFactory factory) {
        factory.setPort(8088);
        factory.setContextPath("/customTomcat");
    }

}

2.注册servlet三大组件
  • servlet listener filter

servlet3.0规范提供的注解方式注册

@WebServlet
@WebListener
@WebFilter

        1.声明servlet 及映射

@WebServlet(name="HelloServlet",urlPatterns = "/HelloServlet")

public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter writer = resp.getWriter();
        writer.println("hello servlet!");

    }
}

        2.加上@ServletComponentScan才会扫描那3个注解

@SpringBootApplication
@ServletComponentScan
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

SpringBoot提供的注册

使用ServletRegistrationBean,FilterRegistrationBean以及ServletListenerRegistrationBean

@Configuration
public class MyWebMvcConfigurer  {

    @Bean
    public ServletRegistrationBean myServlet(){
        // 声明一个servlet注册器Bean
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
        // 设置相应的servlet
        servletRegistrationBean.setServlet(new BeanServlet());
        // 设置名字
        servletRegistrationBean.setName("BeanServlet");
        // 添加映射规则
        servletRegistrationBean.addUrlMappings("/BeanServlet");
        return servletRegistrationBean;

    }
}

3.切换其他嵌入式Servlet容器
  • Spring Boot包含对嵌入式Tomcat,Jetty和Undertow服务器的支持
    • tomcat(默认)
    • Jetty(socket)
    • Undertow(响应式)

    org.springframework.boot
    spring-boot-starter-web
    
    
        
            spring-boot-starter-tomcat
            org.springframework.boot
        
    




4.嵌入式Servlet容器自动配置原理
  • ServletWebServerFactoryAutoConfiguration servlet容器自动配置类
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
// 只要依赖任意一个servlet容器都会存在该来ServletRequest
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
// 启用servet.xxx的所有的配置信息绑定到ServerProperties
@EnableConfigurationProperties(ServerProperties.class)
@import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

1.为什么可以根据配置的依赖自动使用对应的servlet容器?

        通过@import 导入Embeddedxxxx

@import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })

每个Embeddedxxxx 中都配置了相应的@ConditionalOnClass,会根据当前servlet容器start依赖判断classpath是否存在对应的类, 如果存在就使用对应的servlet容器。 比如EmbeddedTomcat:

2.怎么根据配置文件中server.xxx 以及 WebServerFactoryCustomizer 去设置servlet容器属性?

  • ServletWebServerFactoryCustomizer 也实现了WebServerFactoryCustomizer ,说明它也是定制servlet容器的
  • Servlet容器配置文件通用定制器
	@Bean
	public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
		return new ServletWebServerFactoryCustomizer(serverProperties);
	}

        根据配置文件中server.xxx 来进行定制servlet容器

ServletWebServerFactoryCustomizer类中的customize方法

	@Override
	public void customize(ConfigurableServletWebServerFactory factory) {
		PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
 // if(serverProperties.getPort()!=null){
    //      factory.setPort(serverProperties.getPort())
   //}
		map.from(this.serverProperties::getPort).to(factory::setPort);
		map.from(this.serverProperties::getAddress).to(factory::setAddress);
		map.from(this.serverProperties.getServlet()::getContextPath).to(factory::setContextPath);
		map.from(this.serverProperties.getServlet()::getApplicationDisplayName).to(factory::setDisplayName);
		map.from(this.serverProperties.getServlet()::isRegisterDefaultServlet).to(factory::setRegisterDefaultServlet);
		map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession);
		map.from(this.serverProperties::getSsl).to(factory::setSsl);
		map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp);
		map.from(this.serverProperties::getCompression).to(factory::setCompression);
		map.from(this.serverProperties::getHttp2).to(factory::setHttp2);
		map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);
		map.from(this.serverProperties.getServlet()::getContextParameters).to(factory::setInitParameters);
		map.from(this.serverProperties.getShutdown()).to(factory::setShutdown);
	}
  • TomcatServletWebServerFactoryCustomizer Tomcat配置文件定制器
    • 根据配置文件中servet.tomcat.xxxx 定制servlet容器
	@Bean
	@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
	public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
			ServerProperties serverProperties) {
		return new TomcatServletWebServerFactoryCustomizer(serverProperties);
	}

 TomcatServletWebServerFactoryCustomizer 类中的customize方法

	@Override
	public void customize(TomcatServletWebServerFactory factory) {
		ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat();
		if (!ObjectUtils.isEmpty(tomcatProperties.getAdditionalTldSkipPatterns())) {
			factory.getTldSkipPatterns().addAll(tomcatProperties.getAdditionalTldSkipPatterns());
		}
		if (tomcatProperties.getRedirectContextRoot() != null) {
			customizeRedirectContextRoot(factory, tomcatProperties.getRedirectContextRoot());
		}
		customizeUseRelativeRedirects(factory, tomcatProperties.isUseRelativeRedirects());
		factory.setDisableMBeanRegistry(!tomcatProperties.getMbeanregistry().isEnabled());
	}

怎么让所有的WebServerFactoryCustomizer Bean一一调用的

        BeanPostProcessorsRegistrar实现了importBeanDefinitionRegistrar 会提供一个方法,并且提供BeanDefinitionRegistar 让我们去注册bean

public static class BeanPostProcessorsRegistrar implements importBeanDefinitionRegistrar, BeanFactoryAware {

		private ConfigurableListableBeanFactory beanFactory;

		@Override
		public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
			if (beanFactory instanceof ConfigurableListableBeanFactory) {
				this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
			}
		}

		@Override
		public void registerBeanDefinitions(Annotationmetadata importingClassmetadata,
				BeanDefinitionRegistry registry) {
			if (this.beanFactory == null) {
				return;
			}
			registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
					WebServerFactoryCustomizerBeanPostProcessor.class);
			registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
					ErrorPageRegistrarBeanPostProcessor.class);
		}

		private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class beanClass) {
			if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
				RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
				beanDefinition.setSynthetic(true);
				registry.registerBeanDefinition(name, beanDefinition);
			}
		}

	}

注册了:WebServerFactoryCustomizerBeanPostProcessor

 实现了BeanPostProcessor接口,并实现了两个方法在bean初始化前后调用

//初始化前
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 判断当前创建的bean是不是WebServerFactory
        if (bean instanceof WebServerFactory) {
            this.postProcessBeforeInitialization((WebServerFactory)bean);
        }

        return bean;
    }
//初始化后
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
        ((Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this.getCustomizers(), webServerFactory, new Object[0]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).invoke((customizer) -> {
            customizer.customize(webServerFactory);
        });
    }

当对应Embeddedxxxx 启用时, 就会在里面配置一个WebServerFactory 类型的一个Bean, 负责创建对应的容器和启动

 TomcatServletWebServerFactory 是WebServerFactory类型的一个Bean

 我们具体看一下初始化之后调用的方法

private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
    // 调用getCustomizers()
   LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
         .withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
         
         .invoke((customizer) -> customizer.customize(webServerFactory));
}
  • 1.调用getCustomizers()
private Collection> getCustomizers() {
        if (this.customizers == null) {
            this.customizers = new ArrayList(this.getWebServerFactoryCustomizerBeans());
            this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
            this.customizers = Collections.unmodifiableList(this.customizers);
        }

        return this.customizers;
    }
  • 2.getWebServerFactoryCustomizerBeans() 就获取了所有实现了WebServerFactoryCustomizer接口的Bean
    • 获取 自定义的,和ServletWebServerFactoryCustomizer、TomcatServletWebServerFactoryCustomizer

    private Collection> getWebServerFactoryCustomizerBeans() {
        //从spring容器中拿到类型为WebServerFactoryCustomizer的bean
        //包括自动配置类中的,也包括我们自己定制化的
        return this.beanFactory.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
    }
  • 3.在invoke方法中循环调用所有实现了WebServerFactoryCustomizer接口的Bean的customize方法进行一一定制

3.嵌入式servlet容器是怎么启动的

TomcatServletWebServerFactory

  • 自动配置中根据不同的依赖, 启动对应一个Embeddedxxxx, 然后配置一个对应的servlet容器工厂类, 比如tomcat:TomcatServletWebServerFactory
  • 在springboot应用启动的时候 , 就会调用容器refresh方法, onRefresh , 调用getWebServer, 创建servlet及启动(spring源码)
    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);
    }

 getTomcatWebServer方法

    protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
        return new TomcatWebServer(tomcat, this.getPort() >= 0, this.getShutdown());
    }

TomcatWebServer构造方法会调用this.initialize(),initialize方法中调用this.tomcat.start();启动tomcat

    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);
            }

        }
    }

5.使用外部Servlet容器
  • 外部servlet容器
    • 服务器、本机 安装tomcat 环境变量...
    • 部署: war---运维--->tomcat webapp startup.sh 启动
    • 开发: 将开发绑定本地tomcat
    • 开发 、 运维 服务器配置 war
  • 内嵌servlet容器:
    • 部署: jar---> 运维---java -jar 启动

使用:

1. 下载tomcat服务

2.设置当前maven项目的打包方式

war

3.让tomcat相关的依赖不参与打包部署 ,因为外置tomcat服务器已经有这些jar包


    spring-boot-starter-tomcat
    org.springframework.boot
    provided

4. 为了让它支持springboot需要加上: 才能启动springboot应用

// 当tomcat启动时就会调用configure方法, 从而在springboot启动类的基础启动springboot
// 什么时候调用?
public class TomcatStartSpringBoot extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(Application.class);
    }
}

 5. 在idea中运行

6.外部Servlet容器启动SpringBoot应用原理

tomcat---> web.xml--filter servlet listener 3.0+

tomcat不会主动去启动springboot应用 ,, 所以tomcat启动的时候肯定调用了SpringBootServletInitializer的SpringApplicationBuilder , 就会启动springboot

public class TomcatStartSpringBoot extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder (SpringApplicationBuilder builder) {
        return builder.sources(Application.class);
    }
}

servlet3.0 规范官方文档: 8.2.4

大概: 当servlet容器启动时候 就会去meta-INF/services 文件夹中找到javax.servlet.ServletContainerInitializer, 这个文件里面肯定绑定一个ServletContainerInitializer. 当servlet容器启动时候就会去该文件中找到ServletContainerInitializer的实现类,从而创建它的实例调用onstartUp (SPI规范,数据库驱动也用到了)

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
  • @HandlesTypes(WebApplicationInitializer.class).
    • @HandlesTypes传入的类为ServletContainerInitializer感兴趣的
    • 容器会自动在classpath中找到 WebApplicationInitializer 会传入到onStartup方法的webAppInitializerClasses参数中
    • Set> webAppInitializerClasses 这里面也包括之前自己定义的TomcatStartSpringBoot

 SpringServletContainerInitializer 的onStartup方法

	@Override
	public void onStartup(@Nullable Set> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

		List initializers = Collections.emptyList();

		if (webAppInitializerClasses != null) {
			initializers = new ArrayList<>(webAppInitializerClasses.size());
			for (Class waiClass : webAppInitializerClasses) {
				 // 如果不是接口 不是抽象 跟WebApplicationInitializer有关系  就会实例化
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						initializers.add((WebApplicationInitializer)
								ReflectionUtils.accessibleConstructor(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);
		}
	}

 SpringBootServletInitializer的onstartup方法

    public void onStartup(ServletContext servletContext) throws ServletException {
        this.logger = LogFactory.getLog(this.getClass());
        WebApplicationContext rootApplicationContext = this.createRootApplicationContext(servletContext);
        if (rootApplicationContext != null) {
            servletContext.addListener(new SpringBootServletInitializer.SpringBootContextLoaderListener(rootApplicationContext, servletContext));
        } else {
            this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
        }

    }

 首先调用createRootApplicationContext方法

    protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
        SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
        builder.main(this.getClass());
        ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
        if (parent != null) {
            this.logger.info("Root context already created (using as parent).");
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
            builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
        }

        builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
        builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
        builder = this.configure(builder);
        builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
        SpringApplication application = builder.build();
        if (application.getAllSources().isEmpty() && MergedAnnotations.from(this.getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
            application.addPrimarySources(Collections.singleton(this.getClass()));
        }

        Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
        if (this.registerErrorPageFilter) {
            application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
        }

        application.setRegisterShutdownHook(false);
        return this.run(application);
    }

 在createRootApplicationContext方法中builder = this.configure(builder);

而我们自己定义的TomcatStartSpringBoot重写了configure方法,就会来到我们自己定义的继承的SpringBootServletInitializer的configure方法,将Springboot启动类传入到builder.source

public class TomcatStartSpringBoot extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(Application.class);
    }
}

// 调用SpringApplication application = builder.build(); 就会根据传入的Springboot启动类来构建一个SpringApplication

    public SpringApplication build(String... args) {
        this.configureAsChildIfNecessary(args);
        this.application.addPrimarySources(this.sources);
        return this.application;
    }

// 调用 return run(application); 就会帮我启动springboot应用

    protected WebApplicationContext run(SpringApplication application) {
        return (WebApplicationContext)application.run(new String[0]);
    }

它就相当于我们的

public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

其实这2个实现类就是帮我创建ContextLoaderListener 和DispatcherServlet 

listener>
    org.springframework.web.context.ContextLoaderListener



    contextConfigLocation
    classpath:spring-core.xml





    dispatcherServlet
    org.springframework.web.servlet.DispatcherServlet
    
    
        contextConfigLocation
        classpath:spring-mvc.xml
    
    
    1


    dispatcherServlet
    /

5.SpringBoot作为单体Web应用的使用

如果需要动态展示Springmvc的数据到页面上需要使用模板引擎技术:

SpringBoot提供以下模板引擎技术的支持:

  • FreeMarker
  • Groovy
  • Thymeleaf
  • Mustache

以Freemarker为例

        1.添加freemarker的依赖

        
            org.springframework.boot
            spring-boot-starter-freemarker
        
        
            org.springframework.boot
            spring-boot-starter-web
        

        2.设置freemakrer的全局配置

#如果在生产环境可以设置true
spring.freemarker.cache=false

spring.freemarker.charset=UTF-8

spring.freemarker.suffix=.html

        3.添加freemarker的页面




    
    Title


<#list usernames as username>
${username}


        4.对应的控制器

@Controller
@RequestMapping("user")
public class UserController {

    @RequestMapping("index")
    public  String index(Model model){
        model.addAttribute("username","fztx");
        return "index";
    }

    @RequestMapping("list")
    public  String list(Model model){
        List list= Arrays.asList("fztx","zhangsan","lisi");
        model.addAttribute("usernames",list);
        return "list";
    }
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/325038.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号