1 springboot介绍
1)springboot是什么?
Spring Boot是一个用于简化Java应用程序开发的框架。它基于Spring框架,继承了Spring框架原有的优秀特性,比如IOC、AOP等, 他并不是用来代替Spring的解决方案,而是和Spring框架紧密结合,进一步简化了Spring应用的整个搭建和开发过程通过自动配置和约定优于配置的原则,提供了一种快速构建独立、可部署的Spring应用程序的方式。
Spring Boot减少了开发人员在配置上的工作量,使得开发者能够更专注于业务逻辑的实现。它还提供了丰富的功能和插件,可以轻松集成到各种开发环境和工具中。
再来详细解释一下,如果我们基于SSM框架进行过开发,我们可以理解,Spring在集成SpringMVC、Mybatis时,需要做大量的xml文件配置,在集成其他框架或中间件时,也是同样的道理。而再对比一下SpringBoot开发,我们可以发现,我们只需要引入不同的Starters的maven依赖,就可以开箱即用的进行开发。这就是SpringBoot为我们做的:提供默认的配置方式让我们更容易使用。
2)springboot的发展史
- 2003年Rod Johnson成立Interface公司,产品是SpringFramework
- 2004年,Spring框架开源,公司改名为Spring Source
- 2008年,收购Apache Servlet、Tomcat,为SpringBoot内嵌Web容器奠定基础,整个生态自己掌握
- 2009年,公司被VMWare以4.6亿美金收购被收购后,Spring公司接连收购了很多优秀的开源中间件,比如RabbitMQ、Redis
- 2013年,VMWare、EMC、通用电气三者联合成立Pivotal公司,从这开始,Spring开始一路暴走
- 2014年,推出SpringBoot1.0,基于Spring4.0开发
- 2015年,推出SpringCloud
- 2018年,Pivotal公司上市
- 2018年3月,SpringBoot2.0发布,基于Spring5.0开发
3)为什么要用springboot
优点:
- 快速构建一个独立的 Spring 应用程序 ;
- 嵌入的 Tomcat 、 Jetty 或者 Undertow,无须部署 WAR 文件;
- 提供starter POMs来简化Maven配置和减少版本冲突所带来的问题;
- 对Spring和第三方库提供默认配置,也可修改默认值,简化框架配置;
- 提供生产就绪型功能,如指标、健康检查和外部配置;
- 无需配置XML,无代码生成,开箱即用;

2 springboot的基本使用
1)springboot项目创建
a)快速创建
b)maven项目创建
2) spring的配置文件
a)默认配置文件
application.properties:
mydatasources.user-name=root
mydatasources.pass-word=123456
mydatasources.url=mysql:jdbc://127.0.0.1:3306/goods
mydatasources.map.aaa=123
mydatasources.map.bbb=222
mydatasources.map.ccc=333
mydatasources.student.sno=${server.port}
application.yml:
mydatasources:
    password: 123456
    username: root
    url: mysql:jdbc://127.0.0.1:3306/goods
    nums: 1,3,5,6,8,8,9
b)多环境下配置文件
 配置文件命名: application-{profile}.properties
 默认的配置文件中指定运行环境: spring.profiles.active=pro
 profile的值和spring.profiles.active的值相同:
 pro: 生产环境 dev: 开发环境 test: 测试环境
3)常用的注解
@SpringBootApplication: 这个注解标记一个主要的Spring Boot应用程序类。它组合了@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan三个注解,使得应用程序能够自动配置并扫描所需的组件;
@EnableAutoConfiguration: 开启springboot的自动配置;
@ComponentScan: 扫描指定路径下的类上的注解, 交给spring容器管理; 等于spring中xml文件的: context:component-scan/>
@Configuration注解; 声明该类是一个配置类;
@Bean注解: 往spring容器中注入bean, 对应的是spring中xml文件中的bean标签;
@ConfigurationProperties: 只要将类交给spring容器管理, 那么spring容器会自动去配置文件中读取对应的prefix + 属性的值, 封装到对应的属性中; 所以通常搭配, @bean注解或者@Conponent注解,或者@EnableConfigurationProperties注解使用;

出现以上警告, 导入对应依赖:
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
同样是从配置文件中取值, 对比之前学过的@Value注解:

复杂类型数据的注入方式;
随机数以及${xx}的使用:
my.secret=${random.value}
my.number=${random.int}
my.bignumber=${random.long}
my.uuid=${random.uuid}
my.number.less.than.ten=${random.int(10)}
my.number.in.range=${random.int[1024,65536]}
app.name=MyApp
 app.description=${app.name} is a Spring Boot application
@EnableConfigurationProperties: 可以将使用@ConfigurationProperties注解对应的类加入Spring容器;
 注意:@EnableConfigurationProperties与@Component不能同时使用
@PropertySource: 加载指定的非application.properties默认配置文件;
@profile 注解的作用是指定类或方法在特定的 Profile 环境生效,任何@Component或@Configuration注解的类都可以使用@Profile注解。在使用DI来依赖注入的时候,能够根据@profile标明的环境,将注入符合当前运行环境的相应的bean。
@ImportResource: 加载XML配置文件;
@ConditionalOn相关注解: 根据某些条件决定这个类或者这个方法是否交由spring的ioc去管理;

4)自动配置原理
1)运行启动类:

2)通过启动类上的注解, 开启自动配置

3)通过AutoConfigurationImportSelector,导入需要自动配置的类

AutoConfigurationImportSelector中的重要方法:
 process方法()【在该方法中调getAutoConfigurationEntry()方法来得到自动配置类放入autoConfigurationEntry对象中】
      public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
   
		// 【1】deferredImportSelector强转为具体的AutoConfigurationImportSelector类型,
// 并调用getAutoConfigurationEntry方法得到自动配置类放入autoConfigurationEntry对象中
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
					.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
 
//			【2】又将封装了自动配置类的autoConfigurationEntry对象撞见autoConfigurationEntries集合
			this.autoConfigurationEntries.add(autoConfigurationEntry);
 
//			【3】遍历刚刚获取的自动配置类
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
   
//				这里将符合条件的自动配置类作为key,annotationMetadata作为值存放金enrties集合
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
          
        }
getAutoConfigurationEntry()方法
 protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
   
//       【1】得到spring.factories文件配置的所有的自动配置类
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
 
//		利用LinkedHashSet移除重复的配置类【不同的spring.factories中配置了相同的自动配置类】
		configurations = removeDuplicates(configurations);
 
//		得到要排除的自动配置类,比如注解属性exclude的配置类
//		比如:@SpringBootApplication(exclude= FreeMarkerAutoConfiguration.class)
//		将会获取到exclude = FreeMarkerAutoConfiguration.class的元注解数据
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
 
//		检查要被排除的配置类,因为有些不是自动配置类,故要抛出异常
		checkExcludedClasses(configurations, exclusions);
 
//		【2】将要排除的配置类移除
		configurations.removeAll(exclusions);
 
//		【3】因为从spring.factories文件获取的自动配置类太多。如果有些不必要的自动配置类都加载进内存,会造成内存浪费。
//		因此将一些不必要的配置类移除
		configurations = filter(configurations, autoConfigurationMetadata);
 
//		【4】获取了符合条件的自动配置类后,此时触发AutoConfigurationImportEvent事件
//		目的是告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类
		fireAutoConfigurationImportEvents(configurations, exclusions);
 
//		【5】将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象
		return new AutoConfigurationEntry(configurations, exclusions);
     
    }
自动配置的入口方法: 调用了 process方法还有selectImport方法

process()方法上面已经详细讲解了,下面看一下selectImport()方法
selectImport()方法详解:
public Iterable<Entry> selectImports() {
   
			
//			这里得到所有要排除的自动配置类的set集合
			Set<String> allExclusions = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
 
//			这里得到经过滤后符合条件的自动配置类的set集合
			Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
					.collect(Collectors.toCollection(LinkedHashSet::new));
 
//			移除掉要排除的自动配置类【exclude】
			processedConfigurations.removeAll(allExclusions);
 
//			将标有@Order注解的自动配置类进行排序
			return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
					.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
					.collect(Collectors.toList());
		}
下面我们以一个springboot帮我自动配置的类,进行演示:
 使用过ssm框架的小伙伴们大概都有这样一个疑问,之前我们使用ssm框架的时候都要在web.xml文件中配置一个Filter用来处理post请求的乱码问题,但是使用springnboot却不会发生这种问题。
这是为什么呢?答案:因为springboot帮助我们自动配置了编码为UTF-8
HttpEncodingAutoConfiguration:这个类,用于帮助我们解决post请求的乱码问题。
 HttpEncodingAutoConfiguration源码【详解】:
//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件
@Configuration(proxyBeanMethods = false)
 
//启动指定类【HttpProperties】的ConfigurationProperties功能:将配置文件中对应的值和HttpEncodingProperties绑定起来
@EnableConfigurationProperties(HttpProperties.class)
 
//spring底层@Conditional注解,根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效。
//判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
 
//判断当前项目中有没有这个CharacterEncodingFilter:springmvc中解决乱码的乱码的过滤器
@ConditionalOnClass(CharacterEncodingFilter.class)
 
//判断配置文件中是否存在某个配置 spring.http.encoding.enabled   如果不存在,判断也是成立的
//matchIfMissing=true  表示即使我们配置文件中不配置spring.http.encoding.enabled=true  也是默认生效的
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
   
 
//	它已经和springboot配置文件中的值进行映射了
	private final HttpProperties.Encoding properties;
 
//	只有有参构造器的情况下,参数的值就会从容器中拿
	public HttpEncodingAutoConfiguration(HttpProperties properties) {
   
		this.properties = properties.getEncoding();
	}
 
	@Bean //给容器添加一个组件,这个组件中的某些值需要从properties中获取
	@ConditionalOnMissingBean //判断容器中有没有这个组件
	public CharacterEncodingFilter characterEncodingFilter() {
   
		CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
		filter.setEncoding(this.properties.getCharset().name());
		filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
		filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
		return filter;
	}
5)项目部署
jar包的方式部署
 a)添加Maven打包插件(不添加一定打包,但是找不到启动类和入口方法)
<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
 b)使用IDEA开发工具进行打包
 
 c)运行
 java -jar *****.jar
 war包方式部署
 a)手动声明打包方式
<packaging>war</packaging>
 b)声明使用外部的tomcat
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-tomcat</artifactId>
	<scope>provided</scope>
</dependency>
 c)提供springboot启动的servlet初始化器
@ServletComponentScan 
@SpringBootApplication
public class Chapter05Application extends SpringBootServletInitializer {
   
     @Override
      protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
   
		return builder.sources(Chapter05Application.class);
	}
      public static void main(String[] args) {
   
		SpringApplication.run(Chapter05Application.class, args);
	}
}
 d) 使用idea打包
 e) 将打包的war包发布到tomcat上
6)单元测试
 a)在pom文件中添加spring-boot-starter-test测试启动器
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-test</artifactId>
	<scope>test</scope>
</dependency>
 b)编写单元测试类
@RunWith(SpringRunner.class)     //加载spring相关的注解
@SpringBootTest                  //加载项目的上下文
public class App2 {
   
    
		@Autowired 
        private HelloController helloController;    //输入spring容器对象
    
        @Test
        public void helloControllerTest() {
   
            String hello = helloController.hello();
            System.out.println(hello);
        }    
}
7)springmvc功能扩展
 在Spring Boot项目中,一旦引入了Web依赖启动器spring-boot-starter-web,那么Spring Boot整合Spring MVC框架默认实现的一些XxxAutoConfiguration自动配置类就会自动生效,几乎可以在无任何额外配置的情况下进行Web开发。
 那么帮我们自动配置了哪些东西呢?
  1)  内置了两个视图解析器:ContentNegotiatingViewResolver和BeanNameViewResolver;
  2)  支持静态资源以及WebJars
  3)  自动注册了转换器和格式化器
  4)  支持Http消息转换器和消息代码解析器;
  5)  自动初始化Web数据绑定器ConfigurableWebBindingInitializer;
  6)  支持静态项目首页index.html
  7)  支持定制应用图标favicon.ico;
a) 注册视图控制器:
@Configuration
public class MyMVCconfig implements WebMvcConfigurer {
   
    
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
   
        registry.addViewController("/toLoginPage").setViewName("login");
        registry.addViewController("/login.html").setViewName("login");
    }
    
}
b)注册自定义拦截器,
实现HandlerInterceptor 接口,在该类中编写如下方法
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
   
    String uri = request.getRequestURI();
    Object loginUser = request.getSession().getAttribute("loginUser");
    if (uri.startsWith("/admin") && null == loginUser) {
   
        response.sendRedirect("/toLoginPage");
        return false;
    }
    return true;
}
在自定义配置类MyMVCconfig中,重写addInterceptors()方法注册自定义的拦截器
@Autowired
private MyInterceptor myInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
   
	registry.addInterceptor(myInterceptor)
	             .addPathPatterns("/**")
                             .excludePathPatterns("/login.html");
}
c) 注册servlet
@Component
public class MyServlet extends HttpServlet {
   
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
		throws ServletException, IOException {
   
        		this.doPost(req, resp); }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
		throws ServletException, IOException {
   
       		 resp.getWriter().write("hello MyServlet");
  }}
@Configuration
public class ServletConfig {
   
    @Bean
    public ServletRegistrationBean getServlet(MyServlet myServlet){
   
        ServletRegistrationBean registrationBean = 
                                     new ServletRegistrationBean(myServlet,"/myServlet");
        return registrationBean;
    }
}
d) 注册过滤器
@Component
public class MyFilter 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("hello MyFilter");
        filterChain.doFilter(servletRequest,servletResponse);}
    @Override
    public void destroy() {
   }
}
@Bean
public FilterRegistrationBean getFilter(MyFilter filter){
   
	FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
	registrationBean.setUrlPatterns(Arrays.asList("/toLoginPage","/myFilter"));
	return registrationBean;
}
e)注册监听器
@Component
public class MyListener implements ServletContextListener {
   
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
   
        System.out.println("contextInitialized ..."); }
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
   
        System.out.println("contextDestroyed ...");  }
}
@Bean
public ServletListenerRegistrationBean getServletListener(MyListener myListener){
   
	ServletListenerRegistrationBean registrationBean = 
	new ServletListenerRegistrationBean(myListener);
	return registrationBean;
}
f) 使用路径扫描注册三大件
在对应组件上分别使用@WebServlet(“/annotationServlet”)注解来映射“/annotationServlet”请求的Servlet类,
使用@WebFilter(value = {“/antionLogin”,“/antionMyFilter”})注解来映射“/antionLogin”和“/antionMyFilter”请求的Filter类,
使用@WebListener注解来标注Listener类。
@WebServlet("/annotationServlet")
public class MyServlet extends HttpServlet {
   
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
                                         throws ServletException, IOException {
   
        			this.doPost(req, resp); }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
                                          throws ServletException, IOException {
   
        			resp.getWriter().write("hello MyServlet"); }}
===================================================================
 @WebFilter(value = {
   "/antionLogin","/antionMyFilter"})
 public class MyFilter 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("hello MyFilter");
         filterChain.doFilter(servletRequest,servletResponse);}
     @Override
     public void destroy() {
      } }
===================================================================
@WebListener
public class MyListener implements ServletContextListener {
   
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
   
        System.out.println("contextInitialized ..."); }
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
   
        System.out.println("contextDestroyed ..."); }
}
@SpringBootApplication
@ServletComponentScan  // 开启基于注解方式的Servlet组件扫描支持
public class Chapter05Application {
   
	public static void main(String[] args) {
   
		SpringApplication.run(Chapter05Application.class, args);
	}
}
3 thymeleaf视图
1)thymeleaf的配置
spring.thymeleaf.cache = true
spring.thymeleaf.encoding = UTF-8   
spring.thymeleaf.mode = HTML5   
spring.thymeleaf.prefix = classpath:/templates/    
spring.thymeleaf.suffix = .html   
2)thymeleaf的基本使用
 ssm框架中已经学过
3)thymleaf国际化
a)准备国际化文件
login.properties, login_zh_CN.properties
login.tip=请登录
login.username=用户名
login.password=密码
login.rememberme=记住我
login.button=登录
login_en_US.properties
login.tip=Please sign in
login.username=Username
login.password=Password
login.rememberme=Remember me
login.button=Login
b) 配置国际化基础名
# 配置国际化文件基础名
spring.messages.basename=i18n.login
e) 定制区域化解析器
@Configuration
public class MyLocaleResovel implements LocaleResolver {
   
    @Override
    public Locale resolveLocale(HttpServletRequest httpServletRequest) {
   
        //获取请求的utl地址后拼接的l的值
        String l = httpServletRequest.getParameter("l");
        //获取到请求头中Accept-Language的值
        String header = httpServletRequest.getHeader("Accept-Language");
        Locale locale=null;
        //如果请求的url中带了l
        if(!StringUtils.isEmpty(l)){
   
            //使用_分割  第一个参数是语言  第二个参数是国家
            String[] split = l.split("_");
            locale=new Locale(split[0],split[1]);
        }else {
   
            //如果请求url地址中不带l 从请求头中分割出语言  和  国家
            String[] splits = header.split(",");
            String[] split = splits[0].split("-");
            locale=new Locale(split[0],split[1]);}
        return locale;
    }
    @Override
    public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
   
    }
    @Bean
    public LocaleResolver localeResolver(){
   
        return new MyLocaleResovel();
    }
}
f)国际化页面 login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1,shrink-to-fit=no">
    <title>用户登录界面</title>
    <link th:href="@{/login/css/bootstrap.min.css}" rel="stylesheet">
    <link th:href="@{/login/css/signin.css}" rel="stylesheet">
</head>
<body class="text-center">
<!--  用户登录form表单 -->
<form class="form-signin">
    <img class="mb-4" th:src="@{/login/img/login.jpg}" width="72" height="72">
    <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">请登录</h1>
    <input type="text" class="form-control"
           th:placeholder="#{login.username}" required="" autofocus="">
    <input type="password" class="form-control"
           th:placeholder="#{login.password}" required="">
    <div class="checkbox mb-3">
        <label>
            <input type="checkbox" value="remember-me"> [[#{login.rememberme}]]
        </label>
    </div>
    <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.button}">登录</button>
    <p class="mt-5 mb-3 text-muted">© <span th:text="${currentYear}">2018</span>-<span th:text="${currentYear}+1">2019</span></p>
    <a class="btn btn-sm" th:href="@{/login(l='zh_CN')}">中文</a>
    <a class="btn btn-sm" th:href="@{/login(l='en_US')}">English</a>
</form>
</body>
</html>
4)动态页面静态化
a)准备工作: 点击某个商品, 可以跳转到该商品对应的详情页面
b)修改controller中取到详情页面的控制器
 @GetMapping("/goods/{id}.html")
        public String goods(Model model,@PathVariable Integer id) throws Exception {
   
                //判断是否生成过该商品的静态页面
                if(!goodsService.isExists(id)){
     //如果没有生成  就直接去生成静态页面
                    Goods goods =   goodsService.createHTML(id);
                    model.addAttribute("goods",goods);
                    return "detail";
                }else {
     //如果已经生成过, 那么就直接返回静态页面
                        return id.toString();
         }
 }
 c)使用模板引擎生成静态页面
thymeleaf.static.path=E:\\wsj\\springboot\\springboot-thymeleaf\\src\\main\\resources\\templates\\
    @Value("${thymeleaf.static.path}")
    private String htmlPath;
    
    @Autowired
    
















