【SpringBoot1.x】SpringBoot1.x Web 开发
SpringBoot1.x Web 开发
简介
SpringBoot 非常适合 Web 应用程序开发。可以使用嵌入式 Tomcat,Jetty 或 Undertow 轻松创建独立的 HTTP 服务器。
大多数Web应用程序将使用 spring-boot-starter-web
模块来快速启动和运行。
使用 SpringBoot 开发 Web 应用的流程:
- 创建 SpringBoot 应用,选择需要集成的模块
- SpringBoot 默认将这些模块场景配置好,只需要在配置文件指定相关属性即可
- 编写相关的业务代码
xxxxAutoConfigurartion
自动配置类,给容器中添加组件。xxxxProperties
封装配置文件中相关属性。
SpringBoot 对静态资源的映射规则
对于静态资源文件夹映射:
springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
Integer cachePeriod = this.resourceProperties.getCachePeriod();
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/").setCachePeriod(cachePeriod));
}
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
// 静态资源文件夹映射
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
.addResourceLocations(this.resourceProperties.getStaticLocations())
.setCachePeriod(cachePeriod));
}
}
可以看到:
- 所有以
/webjars/**
的方式访问项目的任何资源,都会去以下classpath:/META-INF/resources/webjars/
找资源。webjars 以 jar包 的方式引入静态资源,比如访问localhost:8080/webjars/jquery/3.3.1/jquery.js
。 - 所有以
/**
的方式访问当前项目的任何资源,都去静态资源的文件夹找资源。SpringBoot 默认的静态资源的文件夹有:"classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"
根目录
对于欢迎页映射:
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ResourceProperties resourceProperties) {
return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
}
可以看到:
静态资源文件夹下的所有 index.html 页面,被 "/**"
映射,比如访问 localhost:8080/
就会找 index 页面。
对于图标映射:
@Configuration
@ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)
public static class FaviconConfiguration {
private final ResourceProperties resourceProperties;
public FaviconConfiguration(ResourceProperties resourceProperties) {
this.resourceProperties = resourceProperties;
}
@Bean
public SimpleUrlHandlerMapping faviconHandlerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
// 所有的 **/favicon.ico
mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", faviconRequestHandler()));
return mapping;
}
@Bean
public ResourceHttpRequestHandler faviconRequestHandler() {
ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
requestHandler.setLocations(this.resourceProperties.getFaviconLocations());
return requestHandler;
}
}
可以看到:
所有的 **/favicon.ico
都是在静态资源文件下找。
这些都是 SpringBoot 默认配置的属性,ResourceProperties
可用于配置资源处理的属性。
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties implements ResourceLoaderAware, InitializingBean {
private static final String[] SERVLET_RESOURCE_LOCATIONS = { "/" };
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
"classpath:/resources/", "classpath:/static/", "classpath:/public/" };
private static final String[] RESOURCE_LOCATIONS;
// ...
// 静态资源的位置
private String[] staticLocations = RESOURCE_LOCATIONS;
// 资源处理程序所服务的资源的缓存周期(以秒为单位)
private Integer cachePeriod;
// ...
}
模版引擎
Template:
...
Hello ${user}
...
Data:
...
model.adddAttibute("user", "parzulpan")
...
通过 TemplateEngine 能得到:
...
Hello parzulpan
...
SpringBoot 推荐的模版引擎 有 Thymeleaf,它语法更简单,功能更强大。
引入 Thymeleaf
添加如下属性和依赖:
<properties>
<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
<!-- 布局功能的支持程序 thymeleaf3主程序 layout2以上版本 -->
<!-- thymeleaf2主程序 layout12以上版本 -->
<thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version>
</properties>
<!-- thymeleaf 模版引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Thymeleaf 使用
可以看到 ThymeleafProperties 为如下,可以根据它来配置使用:
@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING = Charset.forName("UTF-8");
private static final MimeType DEFAULT_CONTENT_TYPE = MimeType.valueOf("text/html");
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
}
所以只要把 HTML 页面放在 classpath:/templates/
,thymeleaf 就能自动渲染。
使用步骤:
导入 thymeleaf 的名称空间
<html lang="en" xmlns:th="http://www.thymeleaf.org">
使用 thymeleaf 语法
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF‐8">
<title>Title</title>
</head>
<body>
<h1>成功!</h1>
<!-- th:text 将div里面的文本内容设置为 -->
<div th:text="${hello}">这是显示欢迎信息</div>
</body>
</html>
语法规则
th:text
,改变当前元素里面的文本内容。
th:xx
,xx 可以是任意 html 属性,用来替换原生属性的值。
SpringMVC 自动配置
SpringBoot 为 SpringMVC 提供了自动配置,可与大多数应用程序完美配合。自动配置在 Spring 的默认值之上添加了以下功能:
- 包含 ContentNegotiatingViewResolver 和 BeanNameViewResolver
- 支持提供静态资源,包括对 WebJars 的支持
- 自动注册
Converter,GenericConverter,Formatter
beans - 支持 HttpMessageConverters
- 自动注册 MessageCodesResolver
- 静态 index.html 支持。
- 定制 Favicon 支持
- 自动使用
ConfigurableWebBindingInitializer
bean
如果您想保留 SpringBoot MVC功能,并且只想添加其他 MVC 配置(拦截器,格式化程序,视图控制器等),则可以添加自己 @Configuration
的配置类,继承自 WebMvcConfigurerAdapter,但不能添加 @EnableWebMvc
。这样的话,既保留了所有的自动配置,也能用我们扩展的配置。
package cn.parzulpan.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* @Author : parzulpan
* @Time : 2020-12
* @Desc : 自动以配置类,扩展 SpringMVC
*/
@Configuration
public class CustomMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 浏览器发送 /parzulpan 请求来到自定义 404 页面
registry.addViewController("/parzulpan").setViewName("404");
}
}
也可以全面接管 SpringMVC,使所有的 SpringMVC 的自动配置都失效,只使用自己的配置。只需要在配置类中添加 @EnableWebMvc
即可。
为什么会失效呢?
因为 @EnableWebMvc 会将 WebMvcConfigurationSupport 组件导入进来,而该组件导入进来后,WebMvcAutoConfiguration 配置类就会失效。
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
WebMvcConfigurerAdapter.class })
//容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {}
修改 SpringBoot 默认配置
SpringBoot 在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如果有就用用户配置的。如果没有,才自动配置。如果有些组件可以有多个(ViewResolver),可以将用户配置的和自己默
认的组合起来。
错误处理机制
配置嵌入式 Servlet 容器
SpringBoot 默认使用 Tomcat 作为嵌入式的 Servlet 容器。
定制和修改 Servlet 容器的相关配置
方式一:可以通过修改跟 server 有关的配置来定制和修改,能修改的配置可以参考 ServerProperties
类,这个类本质也是 EmbeddedServletContainerCustomizer
server.port=8081
server.context‐path=/crud
server.tomcat.uri‐encoding=UTF‐8
// 通用的 Servlet 容器设置
server.xxx
// Tomcat 的设置
server.tomcat.xxx
方式二:编写一个 EmbeddedServletContainerCustomizer
,即嵌入式的 Servlet 容器的定制器,用它来修改 Servlet 容器的配置
/**
* @Author : parzulpan
* @Time : 2020-12
* @Desc : 自定义服务配置类
*/
@Configuration
public class CustomServerConfig {
// 定制器 添加到容器中
@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
return new EmbeddedServletContainerCustomizer() {
// 定制嵌入式的Servlet容器相关的规则
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.setPort(8083);
}
};
}
}
注册 Servlet 三大组件
三大组件即 Servlet、Filter、Listener。
由于 SpringBoot 默认是以 jar包 的方式启动嵌入式的 Servlet 容器来启动 SpringBoot 的 Web 应用,即没有 web.xml 文件。所以注册三个组件可以使用以下方式:
ServletRegistrationBean
FilterRegistrationBean
ServletListenerRegistrationBean
/**
* @Author : parzulpan
* @Time : 2020-12
* @Desc : 自定义 Servlet
*/ public class CustomServlet extends HttpServlet { @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
} @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Hello CustomServlet...");
}
}
/**
* @Author : parzulpan
* @Time : 2020-12
* @Desc : 自定义 Filter
*/ public class CustomFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException { } @Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("CustomFilter process...");
filterChain.doFilter(servletRequest, servletResponse);
} @Override
public void destroy() { }
}
/**
* @Author : parzulpan
* @Time : 2020-12
* @Desc : 自定义 Listener
*/ public class CustomListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("CustomListener contextInitialized... ");
} @Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("CustomListener contextDestroyed... ");
}
}
自定义服务配置类
/**
* @Author : parzulpan
* @Time : 2020-12
* @Desc : 自定义服务配置类
*/ @Configuration
public class CustomServerConfig { // 自定义 Servlet 添加到容器中
@Bean
public ServletRegistrationBean customServlet() {
ServletRegistrationBean srb = new ServletRegistrationBean(new CustomServlet(), "/customServlet");
srb.setLoadOnStartup(1); // 可以设置各种属性
return srb;
} // 自定义 Filter 添加到容器中
@Bean
public FilterRegistrationBean customFilter() {
FilterRegistrationBean frb = new FilterRegistrationBean();
frb.setFilter(new CustomFilter());
frb.setUrlPatterns(Arrays.asList("/hello", "/customServlet"));
return frb;
} // 自定义 Listener 添加到容器中
@Bean
public ServletListenerRegistrationBean customListener() {
ServletListenerRegistrationBean<CustomListener> lrb = new ServletListenerRegistrationBean<>(new CustomListener());
return lrb;
}
比较典型的例子有,SpringBoot 帮我们自动 SpringMVC 的时候,自动的注册 SpringMVC 的前端控制器,即 DispatcherServlet。
更换嵌入式 Servlet 容器
SpringBoot 默认使用 Tomcat 作为嵌入式的 Servlet 容器,它也支持 Jetty 和 Undertow 容器。
Tomcat 是 apache 下的一款重量级的服务器,不用多说历史悠久,而且经得起实践的考验。而 Jetty 和 Undertow 都是基于 NIO 实现的高并发轻量级的服务器,支持 Servlet3.1 和 WebSocket。
ConfigurableEmbeddedServletContainer
继承关系为:
使用 Jetty:
<dependencies>
<!-- web 模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 排除默认的 Tomcat 容器 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入新的 Jetty 容器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependencies>
使用 Undertow:
<dependencies>
<!-- web 模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 排除默认的 Tomcat 容器 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入新的 Undertow 容器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependencies>
嵌入式 Servlet 容器 自动配置原理
嵌入式 Servlet 容器 怎么配置上去的,怎么工作的?
是因为存在 EmbeddedServletContainerAutoConfiguration
嵌入式的 Servlet 容器自动配置类。
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
//导入 BeanPostProcessorsRegistrar 给容器中导入一些组件
//导入了EmbeddedServletContainerCustomizerBeanPostProcessor,即后置处理器,在 bean 初始化前后(创建完对象,还没赋值赋值)执行初始化工作
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {
// 如果正在使用 Tomcat,则为嵌套配置。
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class })
// 判断当前容器没有用户自己定义 EmbeddedServletContainerFactory 嵌入式的 Servlet 容器工厂,它的作用是创建嵌入式的Servlet容器
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
// 返回一个 EmbeddedServletContainer,并且启动 Tomcat服务器,其他容器同理
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}
}
// 如果正在使用 Jetty,则为嵌套配置。
@Configuration
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedJetty {
@Bean
public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
return new JettyEmbeddedServletContainerFactory();
}
}
// 如果正在使用 Undertow,则为嵌套配置。
@Configuration
@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedUndertow {
@Bean
public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
return new UndertowEmbeddedServletContainerFactory();
}
}
}
那么对嵌入式容器的配置修改是怎么生效?
我们知道,对于配置修改有两种方式,一种是修改配置文件,本质是使用 ServerProperties 类,而一种是使用 EmbeddedServletContainerCustomizer 类。值得注意的是,ServerProperties 也是 Customizer,即也是一种定制器。
具体步骤为:
- SpringBoot 根据导入的依赖情况,给容器中添加相应的 EmbeddedServletContainerFactory,比如 TomcatEmbeddedServletContainerFactory 容器工厂
- 容器中某个组件要创建对象就会使用后置处理器 EmbeddedServletContainerCustomizerBeanPostProcessor,只要是嵌入式的 Servlet 容器工厂,后置处理器就工作。
- 后置处理器会从容器中获取所有的 EmbeddedServletContainerCustomizer 类,进而调用定制器的定制方法。
嵌入式 Servlet 容器 启动原理
什么时候创建嵌入式的 Servlet 容器工厂?什么时候获取嵌入式的 Servlet 容器并启动 Tomcat?
创建嵌入式的 Servlet 容器工厂过程:
- 第一点:SpringBoot 应用启动运行 run()
- 第二点:SpringBoot 刷新 IOC 容器,此时会创建 IOC 容器对象,并初始化容器,创建容器中的每一个组件。如果是 web app 则创建AnnotationConfigEmbeddedWebApplicationContext,否则创建 AnnotationConfigApplicationContext
- 第三点:刷新刚才创建好的 IOC 容器
- 第四点:web 的 IOC 容器 重写了 onRefresh()
- 第五点:web 的 IOC 容器 创建嵌入式 Servlet 容器
获取嵌入式的 Servlet 容器并启动 Tomcat 过程:
- 第一点:获取嵌入式的 Servlet 容器工厂
- 第二点:使用容器工厂获取嵌入式的 Servlet 容器
- 第三点:嵌入式的 Servlet 容器创建对象并启动 Servlet 容器
- 第四点:先启动嵌入式的 Servlet 容器,再将 IOC 容器中剩下没有创建出的对象获取出来
总结的话,就是 IOC容器启动时会创建嵌入式的 Servlet 容器。
使用外置 Servlet 容器
嵌入式 Servlet 容器,可将应用打成可执行的 jar包。
它的优点是:简单、便携;
它的缺点:默认不支持 JSP、优化定制比较复杂;
而外置的 Servlet 容器,就是在外面安装 Tomcat,应用 war 包的方式打包。
使用步骤:
- 必须创建一个 war 项目,然后利用 idea 创建好目录结构
- 必须将嵌入式的 Tomcat 指定为 provided
- 必须编写一个 SpringBootServletInitializer 的子类,并调用 configure 方法,服务器依靠它启动 SpringBoot 应用
- 配置启动 Tomcat 服务器就可以使用
jar包:执行 SpringBoot 主类的 main(),启动 IOC 容器并创建嵌入式的 Servlet 容器,启动 SpringBoot 应用。
war包:启动外置服务器,服务器通过 SpringBootServletInitializer 启动 SpringBoot 应用,启动 IOC 容器并创建Servlet 容器。
练习和总结
【SpringBoot1.x】SpringBoot1.x Web 开发的更多相关文章
- 为什么做java的web开发我们会使用struts2,springMVC和spring这样的框架?
今年我一直在思考web开发里的前后端分离的问题,到了现在也颇有点心得了,随着这个问题的深入,再加以现在公司很多web项目的控制层的技术框架由struts2迁移到springMVC,我突然有了一个新的疑 ...
- Go web开发初探
2017年的第一篇博客,也是第一次写博客,写的不好,请各位见谅. 本人之前一直学习java.java web,最近开始学习Go语言,所以也想了解一下Go语言中web的开发方式以及运行机制. 在< ...
- 【初码干货】使用阿里云对Web开发中的资源文件进行CDN加速的深入研究和实践
提示:阅读本文需提前了解的相关知识 1.阿里云(https://www.aliyun.com) 2.阿里云CDN(https://www.aliyun.com/product/cdn) 3.阿里云OS ...
- .NET Web开发技术简单整理
在最初学习一些编程语言.一些编程技术的时候,做的更多的是如何使用该技术,如何更好的使用该技术解决问题,而没有去关注它的相关性.关注它的理论支持,这种学习技术的方式是短平快.其实工作中有时候也是这样,公 ...
- web 开发自动化grunt
现在web开发自动化已很流行,如何进行压缩文件,如何进行测试js是否正确,如何进行 检测html文件是否规范等等都可以通过web自动化技术进行实现,只要打一个命令即可. 本文主要是通过grunt进行实 ...
- eclipse SE增加Web开发插件
最近接触了些java项目,之前安装了eclipse SE版本.没有Web开发插件,调试不了Web代码.点击“Window”--“Preference” 左边菜单栏是找不到“Server”项来配置服务器 ...
- Web 开发中很实用的10个效果【附源码下载】
在工作中,我们可能会用到各种交互效果.而这些效果在平常翻看文章的时候碰到很多,但是一时半会又想不起来在哪,所以养成知识整理的习惯是很有必要的.这篇文章给大家推荐10个在 Web 开发中很有用的效果,记 ...
- 12款简化 Web 开发的 JavaScript 开发框架
前端框架简化了开发过程中,像 Bootstrap 和 Foundation 就是前端框架的佼佼者.在这篇文章了,我们编制了一组新鲜的,实用的,可以帮助您建立高质量的 Web 应用程序的 JavaScr ...
- Golang Web开发时前端出现谜之空白换行的坑
在使用Golang做Web开发时,有时候渲染出来的模板在前台显示时会出现一些奇怪的空白换行,具体特征就是查看css样式表并没有相关定义的空白部分. 分析: 查看出现问题页面的网页源代码,复制空白换行部 ...
随机推荐
- 获取radio的值及重置radio
获取:$('input[name=age]:checked').val(); 重置:$('input:radio[name=age]').prop('checked',false);
- ARM架构安装Kubernetes集群
背景 类型 版本 操作系统 CentOS Linux release 7.6.1810 (AltArch) 内核 Linux master 4.18.0-80.7.2.el7.aarch64 硬件配置 ...
- 计算机语言与JAVA的发展
计算机语言与JAVA的发展 第一代语言 2进制 第二代语言 汇编语言 解决人类无法读懂的问题 指令替代二进制 目前应用 逆向工程 机器人 病毒 第三代语言 摩尔定律 性能提升愈来愈慢 高级语言 面向过 ...
- 微信小程序图片保存到本地
微信小程序图片保存到本地是一个常用功能: 这里讲解下完整实现思路: 因为微信官方的授权只弹一次,用户拒绝后再次调用,就需要结合button组件的微信开放能力来调起,以下方案在微信各种授权中可参考. w ...
- Nosql 和 Sql 简单概念介绍
Nosql (非关系数据库) 代表数据库:redis / hbase /mongoDB /CouchDB /Neo4J 存储数据使用的是数据结构化存储方法的集合,意味着数据的存储可以是文档.集合.键值 ...
- css 15-Sass入门
15-Sass入门 #Sass简介 大家都知道,js 中可以自定义发量,css 仅仅是一个标记语言,不是编程语言,因此不可以自定义发量.不可以引用等等. 面对这些问题,我们现在来引入 Sass,简单的 ...
- K8S构建1台master2台node+Harbor_笔记
部署环境: master.node:centos7虚拟机 网络使用本地网卡共享到VMnet1(仅主机),虚拟机使用VMnet1. 准备文件: CentOS-7-x86_64-Minimal-1810. ...
- 程序猿的浪漫:用python画动态爱心
初级画心 学Python,感觉你们的都好复杂,那我来个简单的,我是直接把心形看作是一个正方形+两个半圆:
- HTML 防盗链 用src引用网上图片显示 403 Forbidden
比如 <img class="toto" src="http://img5.imgtn.bdimg.com/it/u=152658425,3125530872&am ...
- Asp.Net WebApi使用Websocket
直接上代码 /// <summary> /// WebSocket Handler /// </summary> public class QWebSocketHandler ...