Chapter5: SpringBoot与Web开发2

news2024/11/24 9:48:00

接上一篇 Chapter4: SpringBoot与Web开发1

10. 配置嵌入式Servlet容器

SpringBoot默认采用Tomcat作为嵌入的Servlet容器;查看pom.xml的Diagram依赖图:
在这里插入图片描述
那么如何定制和修改Servlet容器的相关配置? 下面给出实操方案。

10.1 application.properties配置

SpringBoot全局配置文件中修改与Server相关的配置属性都封装在ServerProperties类中。

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties
		implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {

	/**
	 * Server HTTP port.
	 */
	private Integer port;

	/**
	 * Network address to which the server should bind to.
	 */
	private InetAddress address;

	/**
	 * Context path of the application.
	 */
	private String contextPath;

	/**
	 * Display name of the application.
	 */
	private String displayName = "application";

	@NestedConfigurationProperty
	private ErrorProperties error = new ErrorProperties();

	/**
	 * Path of the main dispatcher servlet.
	 */
	private String servletPath = "/";
    
    // ...
    
}    

修改配置属性

server.port=8082
##springboot 2.0之后,配置为 server.servlet.context-path
#server.servlet.context-path=/boot1
#springboot 2.0之前,配置为 server.context-path
server.context-path=/boot1
# 通用的Servlet容器配置
#server.xxx
#tomcat的配置
# server.tomcat.xxx

10.2 Java代码方式配置

编写一个嵌入式Servlet容器定制器EmbeddedServletContainerCustomizer的实现类,来修改Servlet容器的配置。ConfigurableEmbeddedServletContainer提供了setXXX进行配置属性注入.

// 配置嵌入式的Servlet容器定制器
@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() {

    return new EmbeddedServletContainerCustomizer() {
        // 定制嵌入式的Servlet容器相关的规则
        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) {
            container.setPort(8083);
        }
    };
}

重启应用,从启动日志中查看,应用是从8083端口启动的。
在这里插入图片描述

10.3 注册Servlet三大Web组件

Web的三大组件: Servlet、Filter、Listener

由于SpringBoot默认是以jar包方式的嵌入式Servlet容器来启动SpringBoot的Web应用,没有web.xml文件配置。SpringBoot提供了ServletRegistrationBean、FilterRegistrationBean和ServletListenerRegistrationBean来实现三大组件的注册。
在这里插入图片描述

(1)注册Servlet

首先自定义Servlet

public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().println("hello, my Servlet");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().println("hello, my Servlet");
    }
}

再使用ServletRegistrationBean注册自定义的servlet,并设置匹配的uri.

@Configuration
public class MyServerConfig {
    // 注册三大组件
    // 注册servlet
    @Bean
    public ServletRegistrationBean myServlet() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new MyServlet(), "/myServlet");
        System.out.println("regist servlet");
        return servletRegistrationBean;
    }
}

启动应用后,浏览器访问 /myServlet
在这里插入图片描述
(2)注册Filter

创建自定义Filter

public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init my filter");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("my filter process...");
        // 放行
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        System.out.println("destory my filter");
    }
}

FilterRegistrationBean注册自定义Filter

@Configuration
public class MyServerConfig {
   // ....
    // 注册filter
    @Bean
    public FilterRegistrationBean myFilter() {
        // 启动类上加 @ServletComponentScan注解,会扫描到@WebFilter注解标识的filter类 MyWebFilter; filter实例名称如果重复只会有一个生效
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new MyFilter());
        // 拦截指定请求
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/hello", "/myServlet")); 
        return filterRegistrationBean;
    }
}

测试过滤器,查看启动日志和请求/hello日志
在这里插入图片描述
在这里插入图片描述
查看销毁日志
在这里插入图片描述

(3)注册Listener

创建自定义Listener

public class MyListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("MyListener.contextInitialized...web启动");
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("MyListener.contextDestroyed....web销毁");

    }
}

ServletListenerRegistrationBean注册自定义Listener

@Configuration
public class MyServerConfig {
   //  ...
    // 注册listener
    @Bean
    public ServletListenerRegistrationBean myListener() {
        ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean();
        servletListenerRegistrationBean.setListener(new MyListener());
        return servletListenerRegistrationBean;
    }
}

测试Listener,查看启动日志
在这里插入图片描述
查看销毁日志
在这里插入图片描述
SpringBoot自动配置SpringMVC组件时,其中自动注册的SpringMVC前端控制器(dispatcherServlet)使用了ServletRegistrationBean去注册。

DispatcherServletAutoConfiguration查看源码:

/ 默认拦截所有请求, 包括静态资源,但是不拦截jsp请求; /*会拦截jsp请求。

可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的uri.

@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public ServletRegistrationBean dispatcherServletRegistration(
		DispatcherServlet dispatcherServlet) {
    // 注册dispatcherServlet
	ServletRegistrationBean registration = new ServletRegistrationBean(
        									// 设置拦截的uri使用通配符匹配 / /*
			dispatcherServlet, this.serverProperties.getServletMapping());
	registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
	registration.setLoadOnStartup(
			this.webMvcProperties.getServlet().getLoadOnStartup());
	if (this.multipartConfig != null) {
		registration.setMultipartConfig(this.multipartConfig);
	}
	return registration;
}

也可以使用注解注册三大Web组:@WebServlet, @WebFilter, @WebListener.

10.4 支持其他Servlet容器

上面提到SpringBoot默认支持的嵌入式Servlet容器是tomcat, 也就是只要你引入了Web模块, 就会使用tomcat容器。SpringBoot也支持其他Servlet容器, 比如Jetty, Undertow。
在这里插入图片描述
接口ConfigurableEmbeddedServletContainer提供了支持tomcat, jetty,undertow的实现。
在这里插入图片描述

(1)改造成Jetty容器

首先要排除tomcat的依赖, 再引入Jetty的启动器依赖。

<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>

<!--引入其他web容器 Jetty-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

查看启动日志, 启动的容器是Jetty.

[2023-05-18 22:00:30.030] [org.eclipse.jetty.server.handler.ContextHandler$Context] [main] [2221] [INFO ] Initializing Spring FrameworkServlet 'dispatcherServlet'
[2023-05-18 22:00:30.030] [org.springframework.web.servlet.FrameworkServlet] [main] [489] [INFO ] FrameworkServlet 'dispatcherServlet': initialization started
[2023-05-18 22:00:30.030] [org.springframework.web.servlet.FrameworkServlet] [main] [508] [INFO ] FrameworkServlet 'dispatcherServlet': initialization completed in 7 ms
[2023-05-18 22:00:30.030] [org.eclipse.jetty.server.AbstractConnector] [main] [288] [INFO ] Started ServerConnector@53125718{HTTP/1.1,[http/1.1]}{0.0.0.0:8082}
[2023-05-18 22:00:30.030] [org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainer] [main] [155] [INFO ] Jetty started on port(s) 8082 (http/1.1) // 启动Jetty容器
[2023-05-18 22:00:30.030] [org.springframework.boot.StartupInfoLogger] [main] [57] [INFO ] Started SpringBoot02ConfigApplication in 4.428 seconds (JVM running for 5.0)

(2)改造成undertow容器

排除tomcat的依赖, 再引入undertow的启动器依赖。

<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>

<!--引入其他web容器 undertow-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

查看启动日志

[2023-05-18 22:05:21.021] [org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainer] [main] [160] [INFO ] Undertow started on port(s) 8082 (http) // 启动undertow容器
[2023-05-18 22:05:21.021] [org.springframework.boot.StartupInfoLogger] [main] [57] [INFO ] Started SpringBoot02ConfigApplication in 4.293 seconds (JVM running for 4.863)

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

步骤:

  • SpringBoot根据导入的依赖情况,自动配置类给容器中添加相应的Servlet容器工厂EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】, 获取对应的servlet容器EmbeddedServletContainer【TomcatEmbeddedServletContainer】

  • 容器中某个组件要创建对象就会惊动后置处理器进行初始化工作EmbeddedServletContainerCustomizerBeanPostProcessor 只要是嵌入式的Servlet容器工厂,后置处理器就会工作;

  • 在后置处理器中,从容器中获取所有的EmbeddedServletContainerCustomizer定制器,调用定制器的customize定制方法设置配置属性, ServerProperties#customize设置servlet相关配置.

10.5.1 定制器配置

嵌入式Servlet容器自动配置类 EmbeddedServletContainerAutoConfiguration
导入BeanPostProcessorsRegistrar 给容器中导入一些组件;在其registerBeanDefinitions方法中注册了EmbeddedServletContainerCustomizerBeanPostProcessor后置处理器,用于bean初始化前后执行指定工作;

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class) // Bean后置处理器注册器
public class EmbeddedServletContainerAutoConfiguration {

	// ....
	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;
			}
            // 注册嵌入式Servlet容器定制器Bean的后置处理器, 在定制器实例化过程中进行初始化工作.
			registerSyntheticBeanIfMissing(registry,
					"embeddedServletContainerCustomizerBeanPostProcessor",
					EmbeddedServletContainerCustomizerBeanPostProcessor.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);
			}
		}

	}
}

嵌入式Servlet容器定制器后置处理器的初始化方法如下:

public class EmbeddedServletContainerCustomizerBeanPostProcessor
		implements BeanPostProcessor, BeanFactoryAware {

	private ListableBeanFactory beanFactory;

	private List<EmbeddedServletContainerCustomizer> customizers;

	@Override
	public void setBeanFactory(BeanFactory beanFactory) {
		Assert.isInstanceOf(ListableBeanFactory.class, beanFactory,
				"EmbeddedServletContainerCustomizerBeanPostProcessor can only be used "
						+ "with a ListableBeanFactory");
		this.beanFactory = (ListableBeanFactory) beanFactory;
	}

    // 之前处理
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName)
			throws BeansException {
        // 如果当前初始化的是一个ConfigurableEmbeddedServletContainer类型的组件
		if (bean instanceof ConfigurableEmbeddedServletContainer) {
            // 获取所有的定制器,调用每一个定制器的customize方法来给嵌入式Servlet容器进行属性赋值。
			postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
		}
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName)
			throws BeansException {
		return bean;
	}

	private void postProcessBeforeInitialization(
			ConfigurableEmbeddedServletContainer bean) {
        // 获取所有的定制器,调用每一个定制器的customize方法来给嵌入式Servlet容器进行属性赋值。
		for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
			customizer.customize(bean);
		}
	}

	private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
		if (this.customizers == null) {
			// Look up does not include the parent context
			this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
					this.beanFactory
                // 从容器中获取所有这种类型的组件:EmbeddedServletContainerCustomizer
                // 定制Servlet容器,可以给容器中添加一个EmbeddedServletContainerCustomizer类型的组件。
							.getBeansOfType(EmbeddedServletContainerCustomizer.class,
									false, false)
							.values());
			Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
			this.customizers = Collections.unmodifiableList(this.customizers);
		}
		return this.customizers;
	}

}

嵌入式servlet容器定制器接口提供了customize方法给我们进行定制化处理, 帮助我们修改Servlet容器的配置。

public interface EmbeddedServletContainerCustomizer {

	/**
	 * Customize the specified {@link ConfigurableEmbeddedServletContainer}.
	 * @param container the container to customize
	 */
	void customize(ConfigurableEmbeddedServletContainer container);

}

那么我们对嵌入Servlet式容器的配置修改是怎么生效呢?

嵌入式的Servlet容器配置修改通过ServerProperties实现,ServerProperties也是定制器(它实现了嵌入式Servlet容器定制器接口EmbeddedServletContainerCustomizer)。
在这里插入图片描述
在application.properties文件中修改server.xxx配置时,就是通过定制器的customize方法进行定制化处理,从而达到修改Servlet容器的配置效果。

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties
		implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {

	/**
	 * Server HTTP port.
	 */
	private Integer port;

	/**
	 * Network address to which the server should bind to.
	 */
	private InetAddress address;

	/**
	 * Context path of the application.
	 */
	private String contextPath;
    // ServerProperties#customize 修改servlet容器配置
    @Override
	public void customize(ConfigurableEmbeddedServletContainer container) {
		if (getPort() != null) {
			container.setPort(getPort());
		}
		if (getAddress() != null) {
			container.setAddress(getAddress());
		}
		if (getContextPath() != null) {
			container.setContextPath(getContextPath());
		}
		if (getDisplayName() != null) {
			container.setDisplayName(getDisplayName());
		}
		if (getSession().getTimeout() != null) {
			container.setSessionTimeout(getSession().getTimeout());
		}
		container.setPersistSession(getSession().isPersistent());
		container.setSessionStoreDir(getSession().getStoreDir());
		if (getSsl() != null) {
			container.setSsl(getSsl());
		}
		if (getJspServlet() != null) {
			container.setJspServlet(getJspServlet());
		}
		if (getCompression() != null) {
			container.setCompression(getCompression());
		}
		container.setServerHeader(getServerHeader());
		if (container instanceof TomcatEmbeddedServletContainerFactory) {
			getTomcat().customizeTomcat(this,
					(TomcatEmbeddedServletContainerFactory) container);
		}
		if (container instanceof JettyEmbeddedServletContainerFactory) {
			getJetty().customizeJetty(this,
					(JettyEmbeddedServletContainerFactory) container);
		}

		if (container instanceof UndertowEmbeddedServletContainerFactory) {
			getUndertow().customizeUndertow(this,
					(UndertowEmbeddedServletContainerFactory) container);
		}
		container.addInitializers(new SessionConfiguringInitializer(this.session));
		container.addInitializers(new InitParameterConfiguringServletContextInitializer(
				getContextParameters()));
	}
}

自定义嵌入式Servlet容器定制器配置

@Configuration
public class MyServerConfig {
	// 配置嵌入式的Servlet容器
    @Bean
    public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() {

        return new EmbeddedServletContainerCustomizer() {
            // 定制嵌入式的Servlet容器相关的规则
            @Override
            public void customize(ConfigurableEmbeddedServletContainer container) {
                // 修改Servlet容器http端口
                container.setPort(8082);
            }
        };
    }
}

10.5.2 嵌入式Servlet容器初始化(自动启动原理)

EmbeddedServletContainerFactory 嵌入式Servlet容器工厂提供了创建嵌入式Servlet容器的方法。

public interface EmbeddedServletContainerFactory {

	/**
	 * Gets a new fully configured but paused {@link EmbeddedServletContainer} instance.
	 * Clients should not be able to connect to the returned server until
	 * {@link EmbeddedServletContainer#start()} is called (which happens when the
	 * {@link ApplicationContext} has been fully refreshed).
	 * @param initializers {@link ServletContextInitializer}s that should be applied as
	 * the container starts
	 * @return a fully configured and started {@link EmbeddedServletContainer}
	 * @see EmbeddedServletContainer#stop()
	 *  获取嵌入式Servlet容器
	 */
	EmbeddedServletContainer getEmbeddedServletContainer(
			ServletContextInitializer... initializers);

}

嵌入式Servlet容器工厂接口提供了3个实现类,支持tomcat、Jetty和Undertow容器。
在这里插入图片描述
EmbeddedServletContainer 嵌入式Servlet容器
在这里插入图片描述
EmbeddedServletContainer容器提供了启动和停止Servlet容器的方法。

public interface EmbeddedServletContainer {

	/**
	 * Starts the embedded servlet container. Calling this method on an already started
	 * container has no effect.
	 * @throws EmbeddedServletContainerException if the container cannot be started
	 */
	void start() throws EmbeddedServletContainerException;

	/**
	 * Stops the embedded servlet container. Calling this method on an already stopped
	 * container has no effect.
	 * @throws EmbeddedServletContainerException if the container cannot be stopped
	 */
	void stop() throws EmbeddedServletContainerException;

	/**
	 * Return the port this server is listening on.
	 * @return the port (or -1 if none)
	 */
	int getPort();
}

思考: 什么时候创建嵌入式Servlet容器工厂?什么时候获取嵌入式Servlet容器并启动Tomcat?

嵌入式Servlet容器初始化步骤(以tomcat容器为例):

1)SpringBoot应用启动运行run方法, 创建IOC容器对象.

// org.springframework.boot.SpringApplication#run方法
public ConfigurableApplicationContext run(String... args) {
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	ConfigurableApplicationContext context = null;
	FailureAnalyzers analyzers = null;
	configureHeadlessProperty();
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.starting();
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(
				args);
		ConfigurableEnvironment environment = prepareEnvironment(listeners,
				applicationArguments);
		Banner printedBanner = printBanner(environment);
        // 创建IOC容器
		context = createApplicationContext();
		analyzers = new FailureAnalyzers(context);
		prepareContext(context, environment, listeners, applicationArguments,
				printedBanner);
        // 刷新IOC容器【初始化IOC容器,实例化容器中的每一个组件】
		refreshContext(context);
		afterRefresh(context, applicationArguments);
		listeners.finished(context, null);
		stopWatch.stop();
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass)
					.logStarted(getApplicationLog(), stopWatch);
		}
		return context;
	}
	catch (Throwable ex) {
		handleRunFailure(context, listeners, analyzers, ex);
		throw new IllegalStateException(ex);
	}
}

如果是Web应用创建AnnotationConfigEmbeddedWebApplicationContext容器;

如果不是Web应用创建AnnotationConfigApplicationContext容器。

public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
			+ "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";

public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
			+ "annotation.AnnotationConfigApplicationContext";
// org.springframework.boot.SpringApplication#createApplicationContext方法
protected ConfigurableApplicationContext createApplicationContext() {
	Class<?> contextClass = this.applicationContextClass;
	if (contextClass == null) {
		try {
            
			contextClass = Class.forName(this.webEnvironment
                      // AnnotationConfigEmbeddedWebApplicationContext
					? DEFAULT_WEB_CONTEXT_CLASS : 
					  // AnnotationConfigApplicationContext                              
                                         DEFAULT_CONTEXT_CLASS);
		}
		catch (ClassNotFoundException ex) {
			throw new IllegalStateException(
					"Unable create a default ApplicationContext, "
							+ "please specify an ApplicationContextClass",
					ex);
		}
	}
    //  返回IOC容器对象
	return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}

2)refreshContext(context): SpringBoot刷新IOC容器【初始化IOC容器,实例化容器中的每一个组件】;

// org.springframework.boot.SpringApplication#refreshContext方法
private void refreshContext(ConfigurableApplicationContext context) {
    // refresh(context) 刷新刚才创建好的IOC容器;
	refresh(context);
	if (this.registerShutdownHook) {
		try {
			context.registerShutdownHook();
		}
		catch (AccessControlException ex) {
			// Not allowed in some environments.
		}
	}
}

protected void refresh(ApplicationContext applicationContext) {
	Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    // AbstractApplicationContext#refresh方法
	((AbstractApplicationContext) applicationContext).refresh();
}

(3)AbstractApplicationContext#refresh()中, onRefresh()实现获取嵌入式Servlet容器。

// org.springframework.context.support.AbstractApplicationContext#refresh方法
@Override
public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		// Prepare this context for refreshing.
		prepareRefresh();

		// Tell the subclass to refresh the internal bean factory.
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		// Prepare the bean factory for use in this context.
		prepareBeanFactory(beanFactory);

		try {
			// Allows post-processing of the bean factory in context subclasses.
			postProcessBeanFactory(beanFactory);

			// Invoke factory processors registered as beans in the context.
			invokeBeanFactoryPostProcessors(beanFactory);

			// Register bean processors that intercept bean creation.
			registerBeanPostProcessors(beanFactory);

			// Initialize message source for this context.
			initMessageSource();

			// Initialize event multicaster for this context.
			initApplicationEventMulticaster();

			// Initialize other special beans in specific context subclasses.
            // AbstractApplicationContext#onRefresh中实现获取嵌入式Servlet容器
			onRefresh();

			// Check for listener beans and register them.
			registerListeners();

			// Instantiate all remaining (non-lazy-init) singletons.
            // 启动嵌入式Servlet容器后,再将IOC容器中剩下没有创建的组件进行bean实例化。
			finishBeanFactoryInitialization(beanFactory);

			// Last step: publish corresponding event.
			finishRefresh();
		}

		catch (BeansException ex) {
			if (logger.isWarnEnabled()) {
				logger.warn("Exception encountered during context initialization - " +
						"cancelling refresh attempt: " + ex);
			}

			// Destroy already created singletons to avoid dangling resources.
			destroyBeans();

			// Reset 'active' flag.
			cancelRefresh(ex);

			// Propagate exception to caller.
			throw ex;
		}

		finally {
			// Reset common introspection caches in Spring's core, since we
			// might not ever need metadata for singleton beans anymore...
			resetCommonCaches();
		}
	}
}

// AbstractApplicationContext#onRefresh()方法,在子类IOC容器中重写onRefresh()
protected void onRefresh() throws BeansException {
	// For subclasses: do nothing by default.
}

(4)onRefresh() , IOC容器EmbeddedWebApplicationContext重写了onRefresh()方法,实现创建嵌入式Servlet容器。

@Override
protected void onRefresh() {
	super.onRefresh();
	try {
        // 创建嵌入式Servlet容器
		createEmbeddedServletContainer();
	}
	catch (Throwable ex) {
		throw new ApplicationContextException("Unable to start embedded container",
				ex);
	}
}

5)web的IOC容器会创建嵌入式的Servlet容器工厂; 从IOC容器中获取EmbeddedServletContainerFactory容器工厂组件;

// EmbeddedWebApplicationContext#createEmbeddedServletContainer()方法
private void createEmbeddedServletContainer() {
	EmbeddedServletContainer localContainer = this.embeddedServletContainer;
	ServletContext localServletContext = getServletContext();
	if (localContainer == null && localServletContext == null) {
        // 获取嵌入式Servlet容器工厂
		EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
        // 获取嵌入式Servlet容器,并启动嵌入式Servlet容器(tomcat)
		this.embeddedServletContainer = containerFactory
				.getEmbeddedServletContainer(getSelfInitializer());
	}
	else if (localServletContext != null) {
		try {
			getSelfInitializer().onStartup(localServletContext);
		}
		catch (ServletException ex) {
			throw new ApplicationContextException("Cannot initialize servlet context",
					ex);
		}
	}
	initPropertySources();
}

getEmbeddedServletContainerFactory()方法获取IOC容器中的嵌入式Servlet容器工厂对象。

// EmbeddedWebApplicationContext#getEmbeddedServletContainerFactory()方法
protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
	// Use bean names so that we don't consider the hierarchy
	String[] beanNames = getBeanFactory()
        // 从Bean工厂中获取嵌入式servlet容器工厂bean名称
			.getBeanNamesForType(EmbeddedServletContainerFactory.class);
	if (beanNames.length == 0) {
		throw new ApplicationContextException(
				"Unable to start EmbeddedWebApplicationContext due to missing "
						+ "EmbeddedServletContainerFactory bean.");
	}
	if (beanNames.length > 1) {
		throw new ApplicationContextException(
				"Unable to start EmbeddedWebApplicationContext due to multiple "
						+ "EmbeddedServletContainerFactory beans : "
						+ StringUtils.arrayToCommaDelimitedString(beanNames));
	}
    //从IOC容器中获取并返回嵌入式Servlet容器工厂实例对象
	return getBeanFactory().getBean(beanNames[0],
			EmbeddedServletContainerFactory.class);
}

那么IOC容器中的嵌入式Servlet容器工厂实例对象是什么时候实例化的呢?

在Servlet容器自动配置类EmbeddedServletContainerAutoConfiguration中有实例化嵌入式Servlet容器工厂的方法,比如 TomcatEmbeddedServletContainerFactory

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {

	/**
	 * Nested configuration if Tomcat is being used.
	 */
	@Configuration
	@ConditionalOnClass({ Servlet.class, Tomcat.class })
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedTomcat {
		// 当容器中没有EmbeddedServletContainerFactory实例时就创建Servlet容器工厂。
		@Bean
		public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
			return new TomcatEmbeddedServletContainerFactory();
		}

	}
}	

(6)BeanPostProcessorsRegistrar注册后置处理器 EmbeddedServletContainerCustomizerBeanPostProcessor

TomcatEmbeddedServletContainerFactory容器工厂对象创建,后置处理器发现是嵌入式Servlet容器工厂,就获取所有的定制器先定制Servlet容器的相关配置。

// EmbeddedServletContainerAutoConfiguration.BeanPostProcessorsRegistrar
// BeanPostProcessorsRegistrar#registerBeanDefinitions
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
		BeanDefinitionRegistry registry) {
	if (this.beanFactory == null) {
		return;
	}
	registerSyntheticBeanIfMissing(registry,
			// 注册嵌入式servlet容器定制器后置处理器                                   
			"embeddedServletContainerCustomizerBeanPostProcessor",
			EmbeddedServletContainerCustomizerBeanPostProcessor.class);
	registerSyntheticBeanIfMissing(registry,
			"errorPageRegistrarBeanPostProcessor",
			ErrorPageRegistrarBeanPostProcessor.class);
}

在后置处理器的前置方法中判断,如果是嵌入式Servlet容器对象就初始化Servlet容器。通过获取所有Servlet容器定制器,调用其定制方法初始化Servlet容器属性配置。

public class EmbeddedServletContainerCustomizerBeanPostProcessor
		implements BeanPostProcessor, BeanFactoryAware {

	private ListableBeanFactory beanFactory;

	private List<EmbeddedServletContainerCustomizer> customizers;

	@Override
	public void setBeanFactory(BeanFactory beanFactory) {
		Assert.isInstanceOf(ListableBeanFactory.class, beanFactory,
				"EmbeddedServletContainerCustomizerBeanPostProcessor can only be used "
						+ "with a ListableBeanFactory");
		this.beanFactory = (ListableBeanFactory) beanFactory;
	}

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName)
			throws BeansException {
        // 如果当前初始化的是一个ConfigurableEmbeddedServletContainer类型的组件
		if (bean instanceof ConfigurableEmbeddedServletContainer) {
            // 获取所有的定制器,调用每一个定制器的customize方法来给嵌入式Servlet容器进行属性赋值。
			postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
		}
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName)
			throws BeansException {
		return bean;
	}

	private void postProcessBeforeInitialization(
			ConfigurableEmbeddedServletContainer bean) {
        // 获取所有的定制器,调用每一个定制器的customize方法来给嵌入式Servlet容器进行属性赋值。
		for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
			customizer.customize(bean);
		}
	}

	private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
		if (this.customizers == null) {
			// Look up does not include the parent context
			this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
					this.beanFactory
                // 从容器中获取所有这种类型的组件:EmbeddedServletContainerCustomizer
                // 定制Servlet容器,可以给容器中添加一个EmbeddedServletContainerCustomizer类型的组件。
							.getBeansOfType(EmbeddedServletContainerCustomizer.class,
									false, false)
							.values());
			Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
			this.customizers = Collections.unmodifiableList(this.customizers);
		}
		return this.customizers;
	}
}

(7) 通过嵌入式Servlet容器工厂获取嵌入式Servlet容器对象(默认 tomcat)

EmbeddedWebApplicationContext#createEmbeddedServletContainer()方法中containerFactory.getEmbeddedServletContainer(getSelfInitializer()); 实际调用的是子类的实现。
在这里插入图片描述
(8)获取tomcat容器并启动tomcat

在嵌入式Servlet容器工厂TomcatEmbeddedServletContainerFactory中,继承了EmbeddedServletContainerFactory接口的getEmbeddedServletContainer方法,可以获取tomcat容器。

public class TomcatEmbeddedServletContainerFactory
		extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
	// 获取tomcat容器工厂
	@Override
	public EmbeddedServletContainer getEmbeddedServletContainer(
			ServletContextInitializer... initializers) {
        // 创建一个tomcat对象
		Tomcat tomcat = new Tomcat();
		File baseDir = (this.baseDirectory != null ? this.baseDirectory
				: createTempDir("tomcat"));
        // 配置tomcat容器的基本信息
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		Connector connector = new Connector(this.protocol);
		tomcat.getService().addConnector(connector);
		customizeConnector(connector);
		tomcat.setConnector(connector);
		tomcat.getHost().setAutoDeploy(false);
		configureEngine(tomcat.getEngine());
		for (Connector additionalConnector : this.additionalTomcatConnectors) {
			tomcat.getService().addConnector(additionalConnector);
		}
		prepareContext(tomcat.getHost(), initializers);
        // 将配置好的tomcat传进去,返回一个嵌入式Servlet容器 (tomcat)
		return getTomcatEmbeddedServletContainer(tomcat);
	}
	
	protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
			Tomcat tomcat) {
        // 返回一个嵌入式Servlet容器 (tomcat)
		return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
	}
	
	public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
		Assert.notNull(tomcat, "Tomcat Server must not be null");
		this.tomcat = tomcat;
		this.autoStart = autoStart;
        // 初始化tomcat
		initialize();
	}
	
	private void initialize() throws EmbeddedServletContainerException {
		TomcatEmbeddedServletContainer.logger
				.info("Tomcat initialized with port(s): " + getPortsDescription(false));
		synchronized (this.monitor) {
			try {
				addInstanceIdToEngineName();
				try {
					// Remove service connectors to that protocol binding doesn't happen
					// yet
					removeServiceConnectors();

					// Start the server to trigger initialization listeners
                    // 启动tomcat服务器
					this.tomcat.start();

					// We can re-throw failure exception directly in the main thread
					rethrowDeferredStartupExceptions();

					Context context = findContext();
					try {
						ContextBindings.bindClassLoader(context, getNamingToken(context),
								getClass().getClassLoader());
					}
					catch (NamingException ex) {
						// Naming is not enabled. Continue
					}

					// Unlike Jetty, all Tomcat threads are daemon threads. We create a
					// blocking non-daemon to stop immediate shutdown
					startDaemonAwaitThread();
				}
				catch (Exception ex) {
					containerCounter.decrementAndGet();
					throw ex;
				}
			}
			catch (Exception ex) {
				throw new EmbeddedServletContainerException(
						"Unable to start embedded Tomcat", ex);
			}
		}
	}	
}

11. 使用外置的Servlet容器

  • 嵌入式Servlet容器:应用打成可执行的jar包

    优点:简单、便携;

    缺点:默认不支持Jsp、优化定制比较复杂(使用定制器【ServerProperties、自定义EmbeddedServletContainerCustomizer】,自己编写嵌入式Servlet容器的创建工厂【EmbeddedServletContainerFactory】);

  • 外置的Servlet容器,外面安装Tomcat:应用打成war包

11.1 步骤

1)必须创建一个web项目,war包方式
在这里插入图片描述
在这里插入图片描述

完善web应用目录,指定web.xml; Web Resource目录:

D:\Develops\IdeaProjects\study-spring-boot\spring-boot-atguigu\spring-boot-02-web-jsp\src\main\webapp

在这里插入图片描述

2)将嵌入式的Tomcat指定为provided;provided意味着打包的时候可以不用打包进去,外部容器(Web Container)会提供。该依赖理论上可以参与编译,测试,运行等周期。相当于compile,但是在打包阶段做了exclude的动作。

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-tomcat</artifactId>
	<scope>provided</scope>
</dependency>

3)必须编写一个SpringBootServletInitializer的子类,重写configure方法指定SpringBoot应用的主启动类。

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder applicationBuilder) {
        // 传入SpringBoot应用的主程序
        return applicationBuilder.sources(SpringBoot02WebJspApplication.class);
    }

}

4)如果要支持Jsp,在application.properties文件添加mvc相关配置。

# jsp视图: /webapp/WEB-INF/xxx.jsp
# jsp视图文件前缀
spring.mvc.view.prefix=/WEB-INF/
# jsp视图文件后缀 
spring.mvc.view.suffix=.jsp
# 静态资源目录 resources/static/
spring.mvc.static-path-pattern=/static/**

比如添加src/main/webapp/WEB-INF/success.jsp视图

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <title>来到success页面</title>
    </head>
    <body>
        <h1>${msg}</h1>
    </body>
</html>

添加handler

@Controller
public class HelloController {
    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("msg", "succeed");
        return "success";
    }
}

5)配置外部web容器(tomcat),启动服务器就可以使用。
在这里插入图片描述
6)测试,访问 /hello
在这里插入图片描述

11.2 原理

Jar包:执行SpringBoot主类的main方法,启动IOC容器,创建嵌入式的Servlet容器。

War包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动IOC容器。

外部Servlet容器启动原理 :

  • Servlet3.0标准,ServletContainerInitializer扫描所有jar包中META-INF/services/javax.servlet.ServletContainerInitializer文件指定的类并加载;
  • 加载spring-web-xxx包下的ServletContainerInitializer;
  • 扫描@HandlesTypes(WebApplicationInitializer.class)
  • 加载SpringBootServletInitializer并运行onStartup方法;
  • 加载@SpringBootApplication主类,启动IOC容器;

具体规则:

  • 服务器启动(Web应用启动)会创建当前Web应用里面每一个jar包里面ServletContainerInitializer实例;

  • ServletContainerInitializer的实现放在spring-web-xxx.jar包的/META-INF/services/javax.servlet.ServletContainerInitializer文件,内容就是ServletContainerInitializer的实现类的全限定名,比如:SpringServletContainerInitializer
    在这里插入图片描述

  • 应用启动的时候加载@HandlesTypes指定的类组件。

详细流程:

  • 启动配置的外部Tomcat

  • 加载org/springframework/spring-web/5.3.7/spring-web-5.3.7.jar!/META-INF/services/javax.servlet.ServletContainerInitializer文件中指定的ServletContainerInitializer实例 SpringServletContainerInitializer组件;

    public interface ServletContainerInitializer {
        void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;
    }
    

    SpringServletContainerInitializer将@HandlesTypes({WebApplicationInitializer.class})

    标注所有该类型的类都传入到onStartup方法的Set<Class<?>> webAppInitializerClasses中, 并

    为这些WebApplicationInitializer类型的类创建实例。最后依次调用WebApplicationInitializer#onStartup方法初始化;

    // 扫描所有WebApplicationInitializer组件
    @HandlesTypes(WebApplicationInitializer.class)
    public class SpringServletContainerInitializer implements ServletContainerInitializer {
    	@Override
    	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
    			throws ServletException {
    
    		List<WebApplicationInitializer> initializers = Collections.emptyList();
    
    		if (webAppInitializerClasses != null) {
    			initializers = new ArrayList<>(webAppInitializerClasses.size());
    			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 {
                            /* 将@HandlesTypes扫描到的WebApplicationInitializer组件实例化并添						  * 加到initializers中
                             */
    						initializers.add((WebApplicationInitializer)
                                             // 实例化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) {
                // 依次执行所有WebApplicationInitializer#onStartup方法
    			initializer.onStartup(servletContext);
    		}
    	}
    
    }
    
    // WebApplicationInitializer接口
    public interface WebApplicationInitializer {
    	void onStartup(ServletContext servletContext) throws ServletException;
    }
    

    执行到自定义WebApplicationInitializer组件,ServletInitializer#onStartup方法,其实是父类的SpringBootServletInitializer#onStartup;

    // 自定义WebApplicationInitializer组件
    public class ServletInitializer extends SpringBootServletInitializer {
    
        @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder applicationBuilder) {
            // 传入SpringBoot应用的主程序到SpringApplicationBuilder
            return applicationBuilder.sources(SpringBoot02WebJspApplication.class);
        }
    
    }
    

在这里插入图片描述

// WebApplicationInitializer组件
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
	// SpringBootServletInitializer#onStartup
	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		servletContext.setAttribute(LoggingApplicationListener.REGISTER_SHUTDOWN_HOOK_PROPERTY, false);
		// Logger initialization is deferred in case an ordered
		// LogServletContextInitializer is being used
		this.logger = LogFactory.getLog(getClass());
		// 创建web的IOC容器
		WebApplicationContext rootApplicationContext = createRootApplicationContext(servletContext);
		if (rootApplicationContext != null) {
			servletContext.addListener(new SpringBootContextLoaderListener(rootApplicationContext, servletContext));
		}
		else {
			this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not "
					+ "return an application context");
		}
	}
}
  • SpringServletContainerInitializer实例执行SpringBootServletInitializer#onStartup时会调用

    createRootApplicationContext方法创建IOC容器。

    并通过自定义WebApplicationInitializer组件重写的configure方法获取builder对象,再通过builder.build()获取SpringApplication对象。

    protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
    	SpringApplicationBuilder builder = createSpringApplicationBuilder();
    	builder.main(getClass());
    	ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
    	if (parent != null) {
    		this.logger.info("Root context already created (using as parent).");
    		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
    		builder.initializers(new ParentContextApplicationContextInitializer(parent));
    	}
    	builder.initializers(new ServletContextApplicationContextInitializer(servletContext));
    	builder.contextFactory((webApplicationType) -> new AnnotationConfigServletWebServerApplicationContext());
        // 执行的是子类的configure,将应用主启动类添加到builder并返回SpringApplicationBuilder
    	builder = configure(builder);
    	builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
        // 通过SpringApplicationBuilder#build创建应用实例 SpringApplication
    	SpringApplication application = builder.build();
    	// ...省略
        // 运行SpringApplication,application通过builder添加了主启动类,会启动主启动类
    	return run(application);
    }
    

在这里插入图片描述
最后执行run(application)启动Spring应用;后面就和SpringBoot应用主程序入口启动一样的步骤了。

// org.springframework.boot.web.servlet.support.SpringBootServletInitializer#run
protected WebApplicationContext run(SpringApplication application) {
	return (WebApplicationContext) application.run();
}
  • 先启动Servlet容器,再启动SpringBoot应用。
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/565064.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

依赖范围和编译classpath、测试classpath、运行classpath的关系

最近学习maven&#xff0c;这里看了下别人解释的区别原文&#xff0c;机翻一下&#xff0c;看的懵懵懂懂的 这其实应该是一个简单的区别&#xff0c;但我一直在Stackoverflow上回答一连串类似的问题&#xff0c;而人们往往会误解这个问题。 那么&#xff0c;什么是classpath&am…

[CF复盘] Codeforces Round 874 (Div. 3) 20230520】

[CF复盘] Codeforces Round 874 (Div. 3 20230520 总结A. Musical Puzzle![在这里插入图片描述](https://img-blog.csdnimg.cn/01ab8d835b4343659e8b80680dd9d639.png)2. 思路分析3. 代码实现 B. Restore the Weather1. 题目描述2. 思路分析3. 代码实现 C. Vlad Building Beaut…

FinClip | 2023 年 4 月产品大事记

我们的使命是使您&#xff08;业务专家和开发人员&#xff09;能够通过小程序解决您的关键业务流程挑战。不妨让我们看看在本月的产品与市场发布亮点&#xff0c;看看它们如何帮助您实现目标。 产品方面的相关动向&#x1f447;&#x1f447;&#x1f447; 全新版本的小程序统…

知识图谱实战应用12-食谱领域智能问答系统,实现菜谱问答

大家好,我是微学AI,今天给大家介绍一下知识图谱实战应用12-食谱领域智能问答系统,实现菜谱问答,本项目基于py2neo和neo4j图数据库,将知识图谱应用于菜谱领域。通过构建菜谱知识图谱,实现简单的菜谱食材问答系统。用户可以通过问答系统,快速获取简单的菜谱食材信息。 一…

Vivado综合属性系列之十一 GATED_CLOCK

目录 一、前言 二、GATED_CLOCK 2.1 属性说明 2.2 工程代码 2.3 综合结果 一、前言 在工程设计中&#xff0c;时钟信号通常来源于专用的时钟单元&#xff0c;如MMCM和PLL等。但也存在来自逻辑单元的信号作为时钟&#xff0c;这种时钟信号为门控时钟。门控时钟可以降低时…

Linux下V4l2框架编程_USB摄像头数据采集

Linux内核版本:3.5.0 1.1 V4L2简介 v4L2是针对uvc免驱usb设备的编程框架,主要用于采集usb摄像头等。 这篇文章介绍V4L2框架读取摄像头数据的流程,介绍ioctl常用的命令参数,以及各种摄像头相关的结构体成员含义,最终完成数据采集。 编程模式如下: V4l2支持多种设备,它可…

项目管理PMP好考吗,没有经验?

现在越来越多的产品经理和开发人员也投入到考PMP的大军中&#xff0c;在真实的项目中也会有很多产品经理兼任项目经理的职责&#xff0c;这点还是比较常见的&#xff0c;如果说产品或者开发人员考了PMP证书&#xff0c;本身也会让你在找工作的大军中更具有优势&#xff0c;俗话…

模电基础学习

模拟电路基础 计算机工作原理 用电去控制电,这是计算机工作的核心原理。 电学基础 软件编程更新迭代特别的快,而硬件的学习可能很多年都没有变化,越老越吃香。电路设计好比老中医,学会一个套路就可以用一辈子,因为电路设计是基于物理学原理一直都没有变化过,现在最常用…

SSM编程---Day 01

目录 一、Maven简介 &#xff08;一&#xff09;软件开发中的阶段 &#xff08;二&#xff09;Maven能做什么 &#xff08;三&#xff09;没有使用maven怎么管理依赖 &#xff08;四&#xff09;什么是maven &#xff08;五&#xff09;maven中的概念 二、Maven的核心概…

【面试题】计算机网络面试实战

version&#xff1a;1.0 文章目录 计算机网络网络分层模型&#x1f64e;‍♂️面试官&#xff1a;网络为什么要分层&#xff1f;&#x1f64e;‍♂️面试官&#xff1a;TCP/IP 各层的结构与功能&#xff1f;&#x1f64e;‍♂️面试官&#xff1a;OSI体系模型的结构和功能&…

AI歌手是否能够取代传统歌手,成为主流音乐的新宠?

⭐ “AI歌手”走红背后&#xff1a;谁在训练它&#xff1f;歌迷为何爱听&#xff1f;⭐ 这种新型演艺模式能否获得广泛的市场认可&#xff1f;⭐ AI歌手会取代流行歌手成为主流吗&#xff1f;⭐ AI还在哪些方面有应用呢&#xff1f; 你听过AI歌手吗&#xff1f;近日&#xff0c…

机器学习 | 降维:PCA主成分分析

本文整理自 长路漫漫2021的原创博客&#xff1a;sklearn基础篇&#xff08;九&#xff09;-- 主成分分析&#xff08;PCA&#xff09;李春春_的原创博客&#xff1a;主成分分析&#xff08;PCA&#xff09;原理详解bilibili视频&#xff1a;用最直观的方式告诉你&#xff1a;什…

结构体入门调试技巧

目录 前言&#xff1a; 一.结构体 1.1结构体的初始化和访问 二.结构体传参 三.调试技巧 3.1VS里的版本 3.2调试功能介绍 四.好代码 4.1const修饰指针的两种位置 五.错误分类 ❤博主CSDN:啊苏要学习 ▶专栏分类&#xff1a;C语言◀ C语言的学习&#xff0c;是为我们今…

蓝桥杯2019年省赛——扫地机器人

题目描述 小明公司的办公区有一条长长的走廊&#xff0c;由 NN 个方格区域组成&#xff0c;如下图所示。 走廊内部署了 K 台扫地机器人&#xff0c;其中第 i 台在第Ai​ 个方格区域中。已知扫地机器人每分钟可以移动到左右相邻的方格中&#xff0c;并将该区域清扫干净。 请你…

Java企业级开发学习笔记(4.1)Spring Boot入门程序

该文章主要为完成实训任务&#xff0c;详细实现过程及结果见【http://t.csdn.cn/Qh554】 文章目录 一、使用Maven方式构建Spring Boot项目1.1 创建Maven项目1.2 添加依赖1.3 创建入口类1.4 创建控制器1.5 运行入口类1.6 访问Web页面1.7 修改访问映射路径1.8 定制启动标语1.8.1 …

UE5.1.1C++从0开始(10.作业三)

这次作业量和之前的相比可能会有点大&#xff0c;我先整理一下这次的作业清单&#xff1a; 魔法子弹的飞行声音以及爆炸声音给玩家增加受击的闪亮的效果&#xff0c;和立方体相同的那种增加一个health max的变量&#xff0c;把widget里头的health节点更换为health max节点&…

PythonOCR识别扫描版纯图PDF提取汉字的10大方法,力推RapidOCRPDF 可识别纯图PDF 加密签名的PDF 重点是开源免费,某些方面准确度比百度OCR高

下面实例都以下面的测试样例PDF为实验对象 非纯图可复制pdf 纯图PDF TOP1&#xff1a;RapidOCRPDF 可识别纯图PDF也能识别加密签名的PDF 重点是开源免费 https://github.com/RapidAI/RapidOCRPDF # 基于rapidocr_onnxruntime pip install rapidocr_pdf[onnxruntime]# 基于ra…

多体动力学:哈密尔顿原理的使用

参考资料&#xff1a; HAMILTON’S PRINCIPLE AND HAMILTON’S FORMULATION 原理已经讲得很详细了。如果看不到可以见我的资源&#xff0c;不用积分就可以下载。 步骤&#xff1a; 1.写出拉格朗日表达式 L T − V LT-V LT−V, T T T是系统的动能&#xff0c; V V V是系统的…

二进制安装1.26版本k8s(docker)

文章目录 前言准备工作准备4台虚拟机说明下载对应的二进制包初始化操作CentOS7配置yum源配置免密、修改hostname、关闭防火墙、selinux、关闭swap分区(方便后面进行其它操作)下载软件包并批量安装配置时间同步配置打开文件描述符添加ipvs模块和内核模块 Ubuntu配置apt源配置免密…

【数据结构】---堆排序+TOP-K问题(了解游戏排行底层原理)

文章目录 前言&#x1f31f;一、建堆的两种方式&#xff1a;&#x1f30f;1.1 向上调整建堆(堆排序)&#xff1a;&#x1f4ab;1.1.1 完整代码&#xff1a;&#x1f4ab;1.1.2 流程图(以小堆为例)&#xff1a;升序&#xff1a;建大堆&#x1f4ab;1.1.3 流程图(以小堆为例)&…