特性

虽然Spring的组件代码是轻量级的,但它的配置却是重量级的。一开始,Spring用XML配置,而且是很多XML配置。Spring 2.5引入了基于注解的组件扫描,这消除了大量针对应用程序自身组件的显式XML配置。Spring 3.0引入了基于Java的配置,这是一种类型安全的可重构配置方式,可以代替XML。

所有这些配置都代表了开发时的损耗。因为在思考Spring特性配置和解决业务问题之间需要进行思维切换,所以编写配置挤占了编写应用程序逻辑的时间。和所有框架一样,Spring实用,但与此同时它要求的回报也不少。

除此之外,项目的依赖管理也是一件耗时耗力的事情。在环境搭建时,需要分析要导入哪些库的坐标,而且还需要分析导入与之有依赖关系的其他库的坐标,一旦选错了依赖的版本,随之而来的不兼容问题就会严重阻碍项目的开发进度。

  • 为基于Spring的开发提供更快的入门体验

  • 开箱即用,没有代码生成,也无需XML配置。同时也可以修改默认值来满足特定的需求

  • 提供了一些大型项目中常见的非功能性特性,如嵌入式服务器、安全、指标,健康检测、外部配置等

  • SpringBoot不是对Spring功能上的增强,而是提供了一种快速使用Spring的方式

  • 起步依赖
    起步依赖本质上是一个Maven项目对象模型(Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。
    简单的说,起步依赖就是将具备某种功能的坐标打包到一起,并提供一些默认的功能。

  • 自动配置
    Spring Boot的自动配置是一个运行时(更准确地说,是应用程序启动时)的过程,考虑了众多因素,才决定Spring配置应该用哪个,不该用哪个。该过程是Spring自动完成的。

依赖环境和版本新特性说明

1、依赖版本jdk8以上, Springboot2.x用JDK8, 因为底层是 Spring framework5,
2、安装maven最新版本,maven3.2以上版本,下载地址 :https://maven.apache.org/download.cgi
3、Eclipse或者IDE
4、新特性
5、翻译工具:https://translate.google.cn/
6、springbootGitHub地址:https://github.com/spring-projects/spring-boot
7、springboot官方文档:https://spring.io/guides/gs/spring-boot/

快速创建

手工创建web应用

简介:使用Maven手工创建SpringBoot2.x应用

  1. 手工创建:https://projects.spring.io/spring-boot/#quick-start
  2. 官方推荐包命名接口,不要使用默认 defaultPackage
  3. 官方文档: https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#using-boot-using-the-default-package
  4. 例子:
  5. com
  6. +- example
  7. +- myapplication
  8. +- Application.java
  9. |
  10. +- customer
  11. | +- Customer.java
  12. | +- CustomerController.java
  13. | +- CustomerService.java
  14. | +- CustomerRepository.java
  15. |
  16. +- order
  17. +- Order.java
  18. +- OrderController.java
  19. +- OrderService.java
  20. +- OrderRepository.java

自动创建web应用

工具自动创建:http://start.spring.io/

依赖默认Maven版本

[https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#appendix-dependency-versions](https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#appendix-dependency-versions)

HTTP请求

注解讲解和简化注解配置技巧


1、@RestController and @RequestMapping是springMVC的注解,不是springboot特有的    
2、@RestController = @Controller+@ResponseBody    
3、@SpringBootApplication = @Configuration+@EnableAutoConfiguration+@ComponentScan
    localhost:8080

GET,POST请求实战

springboot接口,http的get请求,各个注解使用

1、GET请求
1、单一参数@RequestMapping(path = "/{id}", method = RequestMethod.GET)
1) public String getUser(@PathVariable String id ) {}
2)@RequestMapping(path = "/{depid}/{userid}", method = RequestMethod.GET) 可以同时指定多个提交方法
getUser(@PathVariable("depid") String departmentID,@PathVariable("userid") String userid)

3)一个顶俩
@GetMapping = @RequestMapping(method = RequestMethod.GET)
@PostMapping = @RequestMapping(method = RequestMethod.POST)
@PutMapping = @RequestMapping(method = RequestMethod.PUT)
@DeleteMapping = @RequestMapping(method = RequestMethod.DELETE)

4)@RequestParam(value = "name", required = true)
可以设置默认值,比如分页 

4)@RequestBody 请求体映射实体类
需要指定http头为 content-type为application/json charset=utf-8

5)@RequestHeader 请求头,比如鉴权
@RequestHeader("access_token") String accessToken

6)HttpServletRequest request自动注入获取参数
package com.niliv.demo.controller;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.niliv.demo.domain.User;

//测试http协议的get请求
@RestController
public class GetController {

    private Map<String,Object> params = new HashMap<>();

    /**
     * 功能描述:测试restful协议,从路径中获取字段
     * @param cityId
     * @param userId
     * @return
     */
    @RequestMapping(path = "/{city_id}/{user_id}", method = RequestMethod.GET)
    public Object findUser(@PathVariable("city_id") String cityId,
            @PathVariable("user_id") String userId ){
        params.clear();

        params.put("cityId", cityId);
        params.put("userId", userId);

        return params;

    }


    /**
     * 功能描述:测试GetMapping
     * @param from
     * @param size
     * @return
     */
    @GetMapping(value="/v1/page_user1")
    public Object pageUser(int  from, int size ){
        params.clear();
        params.put("from", from);
        params.put("size", size);

        return params;

    }

    /**
     * 功能描述:默认值,是否必须的参数,如果没有page参数from则为0
     * @param from
     * @param size
     * @return
     */
    @GetMapping(value="/v1/page_user2")
    public Object pageUserV2(@RequestParam(defaultValue="0",name="page") int  from, int size ){

        params.clear();
        params.put("from", from);
        params.put("size", size);

        return params;
    }


    /**
     * 功能描述:bean对象传参
     * 注意:1、注意需要指定http头为 content-type为application/json
     *         2、使用body传输数据
     * @param user
     * @return
     */
    @RequestMapping("/v1/save_user")
    public Object saveUser(@RequestBody User user){
        params.clear();
        params.put("user", user);
        return params;    
    }

    /**
     * 功能描述:测试获取http头信息
     * @param accessToken
     * @param id
     * @return
     */
    @GetMapping("/v1/get_header")
    public Object getHeader(@RequestHeader("access_token") String accessToken, String id){
        params.clear();
        params.put("access_token", accessToken);
        params.put("id", id);
        return params;    
    }

    /**
     * 获取request
     * @param request
     * @return
     */
    @GetMapping("/v1/test_request")
    public Object testRequest(HttpServletRequest request){
        params.clear();
        String id = request.getParameter("id");
        params.put("id", id);
        return params;    
    }

}
    private Map<String,Object> params = new HashMap<>();

    /**
     * 功能描述:测试PostMapping
     * @param accessToken
     * @param id
     * @return
     */
    @PostMapping("/v1/login")
    public Object login(String id, String pwd){
        params.clear();
        params.put("id", id);
        params.put("pwd", pwd);
        return params;    
    }

    @PutMapping("/v1/put")
    public Object put(String id){
        params.clear();
        params.put("id", id);
        return params;    
    }

    @DeleteMapping("/v1/del")
    public Object del(String id){
        params.clear();
        params.put("id", id);
        return params;    
    }

json框架Jackson返回结果处理

1、常用框架 阿里 fastjson,谷歌gson等
    JavaBean序列化为Json,性能:Jackson > FastJson > Gson > Json-lib 同个结构
    Jackson、FastJson、Gson类库各有优点,各有自己的专长
    空间换时间,时间换空间

2、jackson处理相关自动
    指定字段不返回:@JsonIgnore
    指定日期格式:@JsonFormat(pattern="yyyy-MM-dd hh:mm:ss",locale="zh",timezone="GMT+8")
    空字段不返回:@JsonInclude(Include.NON_NUll)
    指定别名:@JsonProperty
package com.niliv.demo.domain;

import java.util.Date;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;

public class User {

    private int age;

    @JsonIgnore
    private String pwd;

    @JsonProperty("account")
    @JsonInclude(Include.NON_NULL)
    private String phone;

    @JsonFormat(pattern="yyyy-MM-dd hh:mm:ss",locale="zh",timezone="GMT+8")
    private Date createTime;

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public User() {
        super();
    }

    public User(int age, String pwd, String phone, Date createTime) {
        super();
        this.age = age;
        this.pwd = pwd;

        this.createTime = createTime;
    }

}
@GetMapping("/testjson")
    public Object testjson(){

        return new User(111, "abc123", "10001000", new Date());
    }

目录文件结构

1、目录讲解
     src/main/java:存放代码
     src/main/resources
         static: 存放静态文件,比如 css、js、image, (访问方式 http://localhost:8080/js/main.js)
         templates:存放静态页面jsp,html,tpl
         config:存放配置文件,application.properties
         resources:

2、引入依赖 Thymeleaf
     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    注意:如果不引人这个依赖包,html文件应该放在默认加载文件夹里面,
    比如resources、static、public这个几个文件夹,才可以访问

 3、同个文件的加载顺序,静态资源文件
 Spring Boot 默认会挨个从
 META/resources > resources > static > public  里面找是否存在相应的资源,如果有则直接返回。

 4、默认配置    
     1)官网地址:https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-web-applications.html#boot-features-spring-mvc-static-content

     2)spring.resources.static-locations = classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/ 

        5、静态资源文件存储在CDN

html放在templates下无法访问,可以加入

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

再写一个跳转controller

@Controller
public class FileController {

    @RequestMapping(value = "/api/v1/gopage")  
    public Object index() {

        return "index";
    }

resources,static,temploates等目录在url中不需要写,直接可以访问资源文件

自定义资源目录test

resources下新建application.properties

spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/test/

test中的文件可以直接访问

文件上传实战

1)静态页面直接访问:localhost:8080/index.html
注意点:
如果想要直接访问html页面,则需要把html放在springboot默认加载的文件夹下面
2)MultipartFile 对象的transferTo方法,用于文件保存(效率和操作比原先用FileOutStream方便和高效)

访问路径 http://localhost:8080/images/39020dbb-9253-41b9-8ff9-403309ff3f19.jpeg

private static final String filePath = "G:\\全新版本SpringBoot2.x全套视频零基础入门到高级实战SpringBoot教程\\SpringBoot资料csdn\\第2章\\第7课\\xdclass_springboot\\src\\main\\resources\\static\\images\\";


         @RequestMapping(value = "upload")
        @ResponseBody
        public JsonData upload(@RequestParam("head_img") MultipartFile file,HttpServletRequest request) {

             //file.isEmpty(); 判断图片是否为空
             //file.getSize(); 图片大小进行判断

             String name = request.getParameter("name");
             System.out.println("用户名:"+name);

             // 获取文件名
            String fileName = file.getOriginalFilename();            
            System.out.println("上传的文件名为:" + fileName);

            // 获取文件的后缀名,比如图片的jpeg,png
            String suffixName = fileName.substring(fileName.lastIndexOf("."));
            System.out.println("上传的后缀名为:" + suffixName);

            // 文件上传后的路径
            fileName = UUID.randomUUID() + suffixName;
            System.out.println("转换后的名称:"+fileName);

            File dest = new File(filePath + fileName);

            try {
                file.transferTo(dest);

                return new JsonData(0, fileName);
            } catch (IllegalStateException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return  new JsonData(-1, "fail to save ", null);
        }
 <form enctype="multipart/form-data" method="post" action="/upload">
        文件:<input type="file" name="head_img"/>
        姓名:<input type="text" name="name"/>
        <input type="submit" value="上传"/>
       </form>
package net.xdclass.demo.domain;

import java.io.Serializable;

public class JsonData implements Serializable {
    private static final long serialVersionUID = 1L;

    //状态码,0表示成功,-1表示失败
    private int code;

    //结果
    private Object data;

    //错误描述
    private String msg;

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public JsonData(int code, Object data) {
        super();
        this.code = code;
        this.data = data;
    }

    public JsonData(int code, String msg,Object data) {
        super();
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
}

在启动main文件中添加文件限制

@Bean  
public MultipartConfigElement multipartConfigElement() {  
    MultipartConfigFactory factory = new MultipartConfigFactory();  
    //单个文件最大  
    factory.setMaxFileSize("10240KB"); //KB,MB  
    /// 设置总上传数据总大小  
    factory.setMaxRequestSize("1024000KB");  
    return factory.createMultipartConfig();  
}

jar包方式运行

java -jar *.jar

1、文件大小配置,启动类里面配置

        @Bean  
        public MultipartConfigElement multipartConfigElement() {  
            MultipartConfigFactory factory = new MultipartConfigFactory();  
            //单个文件最大  
            factory.setMaxFileSize("10240KB"); //KB,MB  
            /// 设置总上传数据总大小  
            factory.setMaxRequestSize("1024000KB");  
            return factory.createMultipartConfig();  
        }  

2、打包成jar包,需要增加maven依赖
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    如果没加相关依赖,执行maven打包,运行后会报错:no main manifest attribute, in XXX.jar

    GUI:反编译工具,作用就是用于把class文件转换成java文件

3、jar包里的文件上传和访问需要指定磁盘路径
    application.properties中增加下面配置
        1) web.images-path=/Users/jack/Desktop
        2) spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/test/,file:${web.upload-path} 

4、文件服务器:fastdfs,阿里云oss,nginx搭建一个简单的文件服务器
web.upload-path=/Users/jack/Desktop
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/test/,file:${web.upload-path}

热部署 配置文件注入

热部署

官方地址:https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#using-boot-devtools
核心依赖包:
    <dependency>  
         <groupId>org.springframework.boot</groupId>  
         <artifactId>spring-boot-devtools</artifactId>  
         <optional>true</optional>  
     </dependency>
添加依赖后,在ide里面重启应用,后续修改后马上可以生效

classloader
不被热部署的文件
    1、/META-INF/maven, /META-INF/resources, /resources, /static, /public, or /templates
    2、指定文件不进行热部署 spring.devtools.restart.exclude=static/**,public/**
    3、手工触发重启 spring.devtools.restart.trigger-file=trigger.txt
        改代码不重启,通过一个文本去控制

    https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#using-boot-devtools-restart-exclude

注意点:生产环境不要开启这个功能,如果用java -jar启动,springBoot是不会进行热部署的
web.upload-path=/Users/jack/Desktop
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/test/,file:${web.upload-path}

#指定某些文件不进行监听,即不会进行热加载
#spring.devtools.restart.exclude=application.properties

#通过触发器,去控制什么时候进行热加载部署新的文件
spring.devtools.restart.trigger-file=trigger.txt
//trigger.txt
version=2

配置文件介绍和注入

xml、properties、json、yaml
1、常见的配置文件 xx.yml, xx.properties,
1)YAML(Yet Another Markup Language)
写 YAML 要比写 XML 快得多(无需关注标签或引号)
使用空格 Space 缩进表示分层,不同层次之间的缩进可以使用不同的空格数目
注意:key后面的冒号,后面一定要跟一个空格,树状结构
application.properties示例
server.port=8090  
server.session-timeout=30  
server.tomcat.max-threads=0  
server.tomcat.uri-encoding=UTF-8 

application.yml示例
server:  
port: 8090  
session-timeout: 30  
tomcat.max-threads: 0  
tomcat.uri-encoding: UTF-8
2、默认示例文件仅作为指导。 不要将整个内容复制并粘贴到您的应用程序中,只挑选您需要的属性。

3、properties配置项参考:https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#common-application-properties

如果需要修改,直接复制对应的配置文件,加到application.properties里面
比如 server.port=8081 # Server HTTP port.

SpringBoot注解把配置文件自动映射到属性和实体类实战

1、配置文件加载
    方式一
        1、Controller上面配置
           @PropertySource({"classpath:resource.properties"})
        2、增加属性
             @Value("${test.name}")
              private String name;

    方式二:实体类配置文件
    步骤:
        1、添加 @Component 注解;
        2、使用 @PropertySource 注解指定配置文件位置;
        3、使用 @ConfigurationProperties 注解,设置相关属性;

        4、必须 通过注入IOC对象Resource 进来 , 才能在类中使用获取的配置文件值。
            @Autowired
            private ServerSettings serverSettings;

            例子:
                @Configuration
                @ConfigurationProperties(prefix="test")
                @PropertySource(value="classpath:resource.properties")
                public class ServerConstant {
常见问题:
1、配置文件注入失败,Could not resolve placeholder
解决:根据springboot启动流程,会有自动扫描包没有扫描到相关注解, 
默认Spring框架实现会从声明@ComponentScan所在的类的package进行扫描,来自动注入,
因此启动类最好放在根路径下面,或者指定扫描包范围
spring-boot扫描启动类对应的目录和子目录
2、注入bean的方式,属性名称和配置文件里面的key一一对应,就不用加@Value 这个注解
如果不一样,就要加@value("${XXX}")
web.upload-path=/Users/jack/Desktop
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/test/,file:${web.upload-path}

#指定某些文件不进行监听,即不会进行热加载
#spring.devtools.restart.exclude=application.properties

#通过触发器,去控制什么时候进行热加载部署新的文件
spring.devtools.restart.trigger-file=trigger.txt

server.port=8081

#文件上传路径配置
web.file.path=/Users/jack/Desktop/

#测试配置文件注入
test.domain=www.xdclass.net
test.name=springboot

实体类注入

package net.xdclass.demo.domain;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

//服务器配置

@Component
@PropertySource({"classpath:application.properties"})
//@ConfigurationProperties
@ConfigurationProperties(prefix="test")

public class ServerSettings {

    //名称

    //@Value("${appname}")
    private String name;

    //@Value("${domain}")
    private String domain;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDomain() {
        return domain;
    }

    public void setDomain(String domain) {
        this.domain = domain;
    }

}
#测试配置文件注入
test.domain=www.xdclass.net
test.name=springboot
//controll
@Autowired
    private ServerSettings serverSettings;

    @GetMapping("/v1/test_properties")
    public Object testPeroperties(){
        return serverSettings;    
    }

单元测试

1、引入相关依赖
<!--springboot程序测试依赖,如果是自动创建项目默认添加-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2、使用
@RunWith(SpringRunner.class)  //底层用junit  SpringJUnit4ClassRunner
@SpringBootTest(classes={XdclassApplication.class})//启动整个springboot工程
public class SpringBootTests { }
@RunWith(SpringRunner.class)  //底层用junit  SpringJUnit4ClassRunner
@SpringBootTest(classes={XdclassApplication.class})//启动整个springboot工程
public class SpringBootTestDemo {
    @Test
    public void testOne(){
        System.out.println("test hello 1");
        TestCase.assertEquals(1, 1);

    }

    @Test
    public void testTwo(){
        System.out.println("test hello 2");
        TestCase.assertEquals(1, 1);

    }

    @Before
    public void testBefore(){
        System.out.println("before");
    }

    @After
    public void testAfter(){
        System.out.println("after");
    }
}

SpringBoot测试进阶高级篇之MockMvc讲解

1、增加类注解 @AutoConfigureMockMvc
@SpringBootTest(classes={XdclassApplication.class})
2、相关API
perform:执行一个RequestBuilder请求
andExpect:添加ResultMatcher->MockMvcResultMatchers验证规则
andReturn:最后返回相应的MvcResult->Response
/**
 * 功能描述:测试mockmvc类
 *
 * <p> 创建时间:Apr 24, 2018 10:01:12 PM </p> 
 * <p> 作者:小D课堂</p>
 */
@RunWith(SpringRunner.class)  //底层用junit  SpringJUnit4ClassRunner
@SpringBootTest(classes={XdclassApplication.class}) //启动整个springboot工程
@AutoConfigureMockMvc 
public class MockMvcTestDemo {


    @Autowired
    private MockMvc mockMvc;



    @Test
    public void apiTest() throws Exception {

        MvcResult mvcResult =  mockMvc.perform( MockMvcRequestBuilders.get("/test/home") ).
                andExpect( MockMvcResultMatchers.status().isOk() ).andReturn();
        int status = mvcResult.getResponse().getStatus();
        System.out.println(status);

    }

}

banner和debug

1、启动获取更多信息 java -jar xxx.jar --debug

2、修改启动的banner信息
1)在类路径下增加一个banner.txt,里面是启动要输出的信息
2)在applicatoin.properties增加banner文件的路径地址 
spring.banner.location=banner.txt

3)官网地址 https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-banners

全局异常

1、默认异常测试  int i = 1/0,不友好

2、异常注解介绍
@ControllerAdvice 如果是返回json数据 则用 RestControllerAdvice,就可以不加 @ResponseBody

//捕获全局异常,处理所有不可知的异常
@ExceptionHandler(value=Exception.class)
@RestControllerAdvice
public class CustomExtHandler {

    private static final Logger LOG = LoggerFactory.getLogger(CustomExtHandler.class);

    //捕获全局异常,处理所有不可知的异常
    @ExceptionHandler(value=Exception.class)
    //@ResponseBody
    Object handleException(Exception e,HttpServletRequest request){
        LOG.error("url {}, msg {}",request.getRequestURL(), e.getMessage()); 
        Map<String, Object> map = new HashMap<>();
            map.put("code", 100);
            map.put("msg", e.getMessage());
            map.put("url", request.getRequestURL());
            return map;
    }

}

自定义异常,返回错误页面

1、返回自定义异常界面,需要引入thymeleaf依赖
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>


2、resource目录下新建templates,并新建error.html
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("error.html");
    modelAndView.addObject("msg", e.getMessage());
    return modelAndView;


    https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-error-handling
/**
 * 功能描述:异常测试
 *
 * <p> 创建时间:Apr 22, 2018 11:22:29 PM </p> 
 * <p> 作者:小D课堂</p>
 */
@RestController

public class ExcptionController {

    /**
     * 功能描述:模拟全局异常
     * @return
     */
    @RequestMapping(value = "/api/v1/test_ext")  
    public Object index() {
        int i= 1/0;
        return new User(11, "sfsfds", "1000000", new Date());
    }


    /**
     * 功能描述:模拟自定义异常
     * @return
     */
    @RequestMapping("/api/v1/myext")
    public Object myexc(){

        throw new MyException("499", "my ext异常");
    }
}
package net.xdclass.demo.domain;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.ModelAndView;


@RestControllerAdvice
public class CustomExtHandler {

    private static final Logger LOG = LoggerFactory.getLogger(CustomExtHandler.class);


    //捕获全局异常,处理所有不可知的异常
    @ExceptionHandler(value=Exception.class)
    Object handleException(Exception e,HttpServletRequest request){
        LOG.error("url {}, msg {}",request.getRequestURL(), e.getMessage()); 
        Map<String, Object> map = new HashMap<>();
            map.put("code", 100);
            map.put("msg", e.getMessage());
            map.put("url", request.getRequestURL());
            return map;
    }



    /**
     * 功能描述:处理自定义异常
     * @return
     */
    @ExceptionHandler(value=MyException.class)
    Object handleMyException(MyException e,HttpServletRequest request){
        //进行页面跳转
//        ModelAndView modelAndView = new ModelAndView();
//        modelAndView.setViewName("error.html");
//        modelAndView.addObject("msg", e.getMessage());
//        return modelAndView;

        //返回json数据,由前端去判断加载什么页面
        Map<String, Object> map = new HashMap<>();
        map.put("code", e.getCode());
        map.put("msg", e.getMsg());
        map.put("url", request.getRequestURL());
        return map;

    }

}

war tomcat9

1、ide启动
2、jar包方式启动
            maven插件:
            <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
            </build>
            如果没有加,则执行jar包 ,报错如下
                java -jar spring-boot-demo-0.0.1-SNAPSHOT.jar
                no main manifest attribute, in spring-boot-demo-0.0.1-SNAPSHOT.jar
            如果有安装maven 用 mvn spring-boot:run
    项目结构
        example.jar
                 |
                 +-META-INF
                 |  +-MANIFEST.MF
                 +-org
                 |  +-springframework
                 |     +-boot
                 |        +-loader
                 |           +-<spring boot loader classes>
                 +-BOOT-INF
                    +-classes
                    |  +-mycompany
                    |     +-project
                    |        +-YourClasses.class
                    +-lib
                       +-dependency1.jar
                       +-dependency2.jar
目录结构讲解
https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#executable-jar-jar-file-structure

3、war包方式启动
    1)在pom.xml中将打包形式 jar 修改为war  <packaging>war</packaging>

    构建项目名称 <finalName>xdclass_springboot</finalName>

    2)tocmat下载 https://tomcat.apache.org/download-90.cgi
    <br />
    3)修改启动类<br />
        public class XdclassApplication extends SpringBootServletInitializer {<br />
<br />
            [@Override ](/Override ) <br />
            protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {<br />
                return application.sources(XdclassApplication.class);<br />
            }<br />
<br />

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

        }

    4)打包项目,启动tomcat
    war放入webapps

4、启动容器介绍和第三方测试数据讲解

使用Jmter测试工具测试性能,QPS,TPS,RT

https://examples.javacodegeeks.com/enterprise-java/spring/tomcat-vs-jetty-vs-undertow-comparison-of-spring-boot-embedded-servlet-containers/
package net.xdclass.base_project;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

@SpringBootApplication //一个注解顶下面3个
public class XdclassApplication extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(XdclassApplication.class);
    }

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

}
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>net.xdclass</groupId>
  <artifactId>base_project</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <!-- 打包方式 -->
 <packaging>war</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
    </parent>

<properties>
</properties>

        <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

    </dependencies>


    <build>
    <!-- 打包的项目名称 -->
    <finalName>xdclass_springboot</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

过滤,监听,拦

过滤器

filter简单理解:人--->检票员(filter)---> 景点

1、SpringBoot启动默认加载的Filter 
        characterEncodingFilter
        hiddenHttpMethodFilter
        httpPutFormContentFilter
        requestContextFilter
2、Filter优先级

        Ordered.HIGHEST_PRECEDENCE
        Ordered.LOWEST_PRECEDENCE

        低位值意味着更高的优先级 Higher values are interpreted as lower priority
        自定义Filter,避免和默认的Filter优先级一样,不然会冲突

        注册Filter的bean FilterRegistrationBean
        同模块里面有相关默认Filter
            web->servlet->filter
3、自定义Filter
        1)使用Servlet3.0的注解进行配置
        2)启动类里面增加 @ServletComponentScan,进行扫描
        3)新建一个Filter类,implements Filter,并实现对应的接口
        4) @WebFilter 标记一个类为filter,被spring进行扫描 
            urlPatterns:拦截规则,支持正则

        6)控制chain.doFilter的方法的调用,来实现是否通过放行
           不放行,web应用resp.sendRedirect("/index.html");
场景:权限控制、用户登录(非前端后端分离场景)等
    1、官网地址:https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-embedded-container-servlets-filters-listeners
package net.xdclass.demo.Filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


@WebFilter(urlPatterns = "/api/*", filterName = "loginFilter")
public class LoginFilter  implements Filter{

     /**
      * 容器加载的时候调用
      */
      @Override
      public void init(FilterConfig filterConfig) throws ServletException {
          System.out.println("init loginFilter");
      }


      /**
       * 请求被拦截的时候进行调用
       */
      @Override
      public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
          System.out.println("doFilter loginFilter");

          HttpServletRequest req = (HttpServletRequest) servletRequest;
          HttpServletResponse resp = (HttpServletResponse) servletResponse;
          String username = req.getParameter("username");

          if ("xdclass".equals(username)) {
              filterChain.doFilter(servletRequest,servletResponse);
          } else {
              resp.sendRedirect("/index.html");
              return;
          }
      }

      /**
       * 容器被销毁的时候被调用
       */
      @Override
      public void destroy() {
          System.out.println("destroy loginFilter");
      }

}
@GetMapping(value="/api/v1/account")
    public Object account(){

        params.put("money", "1000");

        return params;

    }
package net.xdclass.demo;

import javax.servlet.MultipartConfigElement;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;

//@SpringBootApplication //一个注解顶下面3个
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
@ServletComponentScan //扫描filter,servlet等
public class XdclassApplication {

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


    @Bean  
    public MultipartConfigElement multipartConfigElement() {  
        MultipartConfigFactory factory = new MultipartConfigFactory();  
        //单个文件最大  
        factory.setMaxFileSize("10240KB"); //KB,MB  
        /// 设置总上传数据总大小  
        factory.setMaxRequestSize("1024000KB");  
        return factory.createMultipartConfig();  
    }  



}

自定义原生Servlet

@WebServlet(name = "userServlet",urlPatterns = "/test/customs")
public class UserServlet extends HttpServlet{

@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().print("custom sevlet");
resp.getWriter().flush();
resp.getWriter().close();
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
}

监听器

(常用的监听器 servletContextListener、httpSessionListener、servletRequestListener)

@WebListener
public class RequestListener implements ServletRequestListener {

@Override
public void requestDestroyed(ServletRequestEvent sre) {
// TODO Auto-generated method stub
System.out.println("======requestDestroyed========");
}

@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("======requestInitialized========");

}
@WebListener
public class RequestListener implements ServletRequestListener {

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        // TODO Auto-generated method stub
        System.out.println("======requestDestroyed========");
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println("======requestInitialized========");

    }
}
@WebListener
public class CustomContextListener implements ServletContextListener{

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("======contextInitialized========");

    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("======contextDestroyed========");

    }

}

拦截器

1、@Configuration
    继承WebMvcConfigurationAdapter(SpringBoot2.X之前旧版本)

    SpringBoot2.X 新版本配置拦截器 implements WebMvcConfigurer

2、自定义拦截器 HandlerInterceptor
    preHandle:调用Controller某个方法之前
    postHandle:Controller之后调用,视图渲染之前,如果控制器Controller出现了异常,则不会执行此方法
    afterCompletion:不管有没有异常,这个afterCompletion都会被调用,用于资源清理

3、按照注册顺序进行拦截,先注册,先被拦截

拦截器不生效常见问题:
    1)是否有加@Configuration
    2)拦截路径是否有问题 **  和 * 
    3)拦截器最后路径一定要 “/**”, 如果是目录的话则是 /*/

Filter
    是基于函数回调 doFilter(),而Interceptor则是基于AOP思想
    Filter在只在Servlet前后起作用,而Interceptor够深入到方法前后、异常抛出前后等

    依赖于Servlet容器即web应用中,而Interceptor不依赖于Servlet容器所以可以运行在多种环境。

    在接口调用的生命周期里,Interceptor可以被多次调用,而Filter只能在容器初始化时调用一次。

    Filter和Interceptor的执行顺序

    过滤前->拦截前->action执行->拦截后->过滤后
package net.xdclass.demo.intecpter;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


@Configuration
public class CustomWebMvcConfigurer implements WebMvcConfigurer  {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(new LoginIntercepter()).addPathPatterns("/api2/*/**");
        registry.addInterceptor(new TwoIntercepter()).addPathPatterns("/api2/*/**");

        //.excludePathPatterns("/api2/xxx/**"); //拦截全部 /*/*/**

        WebMvcConfigurer.super.addInterceptors(registry);
    }
}
package net.xdclass.demo.intecpter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class LoginIntercepter implements HandlerInterceptor{

    /**
     * 进入controller方法之前
     */
    @Override
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {
        System.out.println("LoginIntercepter------->preHandle");

//        String token = request.getParameter("access_token");
//        
//        response.getWriter().print("fail");

        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    /**
     * 调用完controller之后,视图渲染之前
     */
    @Override
    public void postHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {

        System.out.println("LoginIntercepter------->postHandle");

        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    /**
     * 整个完成之后,通常用于资源清理
     */
    @Override
    public void afterCompletion(HttpServletRequest request,
            HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("LoginIntercepter------->afterCompletion");

        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }

}
package net.xdclass.demo.intecpter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class TwoIntercepter implements HandlerInterceptor{

    /**
     * 进入对应的controller方法之前
     */
    @Override
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {

        System.out.println("TwoIntercepter------>preHandle");
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    /**
     * controller处理之后,返回对应的视图之前
     */
    @Override
    public void postHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        System.out.println("TwoIntercepter------>postHandle");
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    /**
     * 整个请求结束后调用,视图渲染后,主要用于资源的清理
     */
    @Override
    public void afterCompletion(HttpServletRequest request,
            HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("TwoIntercepter------>afterCompletion");
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }


}

SpringBoot Starter

1、官网地址:https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#using-boot-starter

2、starter主要简化依赖用的
    spring-boot-starter-web    ->里面包含多种依赖

3、几个常用的starter
    spring-boot-starter-activemq
    spring-boot-starter-aop
    spring-boot-starter-data-redis
    spring-boot-starter-freemarker
    spring-boot-starter-thymeleaf
    spring-boot-starter-webflux

模板引擎

1、JSP(后端渲染,消耗性能)
Java Server Pages 动态网页技术,由应用服务器中的JSP引擎来编译和执行,再将生成的整个页面返回给客户端
可以写java代码
持表达式语言(el、jstl)
内建函数
JSP->Servlet(占用JVM内存)permSize
javaweb官方推荐
springboot不推荐 https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-jsp-limitations

2、Freemarker 
FreeMarker Template Language(FTL)  文件一般保存为 xxx.ftl
严格依赖MVC模式,不依赖Servlet容器(不占用JVM内存)
内建函数

3、Thymeleaf (主推)
轻量级的模板引擎(负责逻辑业务的不推荐,解析DOM或者XML会占用多的内存)
可以直接在浏览器中打开且正确显示模板页面

直接是html结尾,直接编辑
xdlcass.net/user/userinfo.html
社会工程学
伪装

freemarker

1、Freemarker相关maven依赖
    <!-- 引入freemarker模板引擎的依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-freemarker</artifactId>
    </dependency>

2、Freemarker基础配置
    # 是否开启thymeleaf缓存,本地为false,生产建议为true
    spring.freemarker.cache=false

    spring.freemarker.charset=UTF-8
    spring.freemarker.allow-request-override=false
    spring.freemarker.check-template-location=true

    #类型
    spring.freemarker.content-type=text/html

    spring.freemarker.expose-request-attributes=true
    spring.freemarker.expose-session-attributes=true

    #文件后缀
    spring.freemarker.suffix=.ftl
    #路径
    spring.freemarker.template-loader-path=classpath:/templates/


3、建立文件夹
    1)src/main/resources/templates/fm/user/
    2)建立一个index.ftl
    3)user文件夹下面建立一个user.html



4、简单测试代码编写和访问
@Controller
@RequestMapping("/freemaker")
public class FreemakerController {


    @Autowired
    private ServerSettings setting;


    @GetMapping("hello")
    public String index(ModelMap modelMap){

        modelMap.addAttribute("setting", setting);

        return "fm/index";  //不用加后缀,在配置文件里面已经指定了后缀
    }
}

thymeleaf

官网地址:https://www.thymeleaf.org/doc/articles/thymeleaf3migration.html
1、thymeleaf相关maven依赖
        <dependency>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

2、thymeleaf基础配置

    #开发时关闭缓存,不然没法看到实时页面
    spring.thymeleaf.cache=false
    spring.thymeleaf.mode=HTML5
    #前缀
    spring.thymeleaf.prefix=classpath:/templates/
    #编码
    spring.thymeleaf.encoding=UTF-8
    #类型
    spring.thymeleaf.content-type=text/html
    #名称的后缀
    spring.thymeleaf.suffix=.html

3、建立文件夹
    1)src/main/resources/templates/tl/
    2)建立一个index.html

4、简单测试代码编写和访问
    注意:$表达式只能写在th标签内部
    快速入门:https://www.thymeleaf.org/doc/articles/standarddialect5minutes.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
模板引擎整合thymeleaf  admin/info.html

<h1 >测试内容,未加th表达式</h1>
<h1 th:text="${setting.name}">测试内容</h1>
<h1>xdclass.net</h1>
</body>
</html>
package net.xdclass.demo.controller;

import net.xdclass.demo.domain.ServerSettings;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;


@Controller
@RequestMapping("/tyhmeleaf")
public class ThymeleafController {

    @Autowired
    private ServerSettings setting;

    @GetMapping("hello")
    public String index(){


        return "index";  //不用加后缀,在配置文件里面已经指定了后缀
    }

    @GetMapping("info")
    public String admin(ModelMap modelMap){

        modelMap.addAttribute("setting", setting);

        return "admin/info";  //不用加后缀,在配置文件里面已经指定了后缀
    }
}
web.upload-path=/Users/jack/Desktop
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/test/,file:${web.upload-path},classpath:/templates/

#指定某些文件不进行监听,即不会进行热加载
#spring.devtools.restart.exclude=application.properties

#通过触发器,去控制什么时候进行热加载部署新的文件
spring.devtools.restart.trigger-file=trigger.txt

server.port=8081

#文件上传路径配置
web.file.path=/Users/jack/Desktop/

#测试配置文件注入
test.domain=www.xdclass.net
test.name=springboot



#自定义启动banner文件的路径
spring.banner.location=banner.txt


##整合freemaker相关配置
## 是否开启thymeleaf缓存,本地为false,生产建议为true
#spring.freemarker.cache=false
#spring.freemarker.charset=UTF-8
#spring.freemarker.allow-request-override=false
#spring.freemarker.check-template-location=true
#        
##类型
#spring.freemarker.content-type=text/html
#spring.freemarker.expose-request-attributes=true
#spring.freemarker.expose-session-attributes=true
#        
##文件后缀
#spring.freemarker.suffix=.ftl
##路径
#spring.freemarker.template-loader-path=classpath:/templates/



#整合thymeleaf相关配置
#开发时关闭缓存,不然没法看到实时页面
spring.thymeleaf.cache=false
spring.thymeleaf.mode=HTML5
#前缀
spring.thymeleaf.prefix=classpath:/templates/tl/
#编码
spring.thymeleaf.encoding=UTF-8
#类型
spring.thymeleaf.content-type=text/html
#名称的后缀
spring.thymeleaf.suffix=.html

mybatis,事务

持久化数据方式

1、原始java访问数据库
    开发流程麻烦
    1、注册驱动/加载驱动
        Class.forName("com.mysql.jdbc.Driver")
    2、建立连接
        Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/dbname","root","root");
    3、创建Statement

    4、执行SQL语句

    5、处理结果集

    6、关闭连接,释放资源

2、apache dbutils框架
    比上一步简单点
    官网:https://commons.apache.org/proper/commons-dbutils/
3、jpa框架
    spring-data-jpa
    jpa在复杂查询的时候性能不是很好

4、Hiberante   解释:ORM:对象关系映射Object Relational Mapping
    企业大都喜欢使用hibernate

5、Mybatis框架   
    互联网行业通常使用mybatis
    不提供对象和关系模型的直接映射,半ORM

整合Mybatis3.x

    1、使用starter, maven仓库地址:http://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter

    2、加入依赖(可以用 http://start.spring.io/ 下载)

        <!-- 引入starter-->
                <dependency>
                    <groupId>org.mybatis.spring.boot</groupId>
                    <artifactId>mybatis-spring-boot-starter</artifactId>
                    <version>1.3.2</version>
                    <scope>runtime</scope>                
                </dependency>

         <!-- MySQL的JDBC驱动包    -->    
                 <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <scope>runtime</scope>
                </dependency> 
        <!-- 引入第三方数据源 -->        
                <dependency>
                    <groupId>com.alibaba</groupId>
                    <artifactId>druid</artifactId>
                    <version>1.1.6</version>
                </dependency>

    3、加入配置文件
        #mybatis.type-aliases-package=net.xdclass.base_project.domain
        #可以自动识别
        #spring.datasource.driver-class-name =com.mysql.jdbc.Driver

        spring.datasource.url=jdbc:mysql://localhost:3306/movie?useUnicode=true&characterEncoding=utf-8
        spring.datasource.username =root
        spring.datasource.password =password
        #如果不使用默认的数据源 (com.zaxxer.hikari.HikariDataSource)
        spring.datasource.type =com.alibaba.druid.pool.DruidDataSource

    加载配置,注入到sqlSessionFactory等都是springBoot帮我们完成

    4、启动类增加mapper扫描
        @MapperScan("net.xdclass.base_project.mapper")

         技巧:保存对象,获取数据库自增id 
         @Options(useGeneratedKeys=true, keyProperty="id", keyColumn="id")

    4、开发mapper
        参考语法 http://www.mybatis.org/mybatis-3/zh/java-api.html

    5、sql脚本
        CREATE TABLE `user` (
          `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
          `name` varchar(128) DEFAULT NULL COMMENT '名称',
          `phone` varchar(16) DEFAULT NULL COMMENT '用户手机号',
          `create_time` datetime DEFAULT NULL COMMENT '创建时间',
          `age` int(4) DEFAULT NULL COMMENT '年龄',
          PRIMARY KEY (`id`)
        ) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;
    相关资料:
    http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/#Configuration

    https://github.com/mybatis/spring-boot-starter/tree/master/mybatis-spring-boot-samples

    整合问题集合:
        https://my.oschina.net/hxflar1314520/blog/1800035
        https://blog.csdn.net/tingxuetage/article/details/80179772
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>net.xdclass</groupId>
  <artifactId>base_project</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <!-- 打包方式 -->
 <packaging>war</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
    </parent>

<properties>
</properties>

        <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- 引入starter-->
        <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>

             <!-- MySQL的JDBC驱动包    -->    
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency> 
            <!-- 引入第三方数据源 -->        
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.6</version>
        </dependency>

    </dependencies>


    <build>
    <!-- 打包的项目名称 -->
    <finalName>xdclass_springboot</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
spring.datasource.url=jdbc:mysql://118.24.175.34:3306/java_test?useUnicode=true&characterEncoding=utf-8
spring.datasource.username =root
spring.datasource.password =p@ssw0rd
#如果不使用默认的数据源 (com.zaxxer.hikari.HikariDataSource)
spring.datasource.type =com.alibaba.druid.pool.DruidDataSource
@SpringBootApplication //一个注解顶下面3个
@MapperScan("net.xdclass.base_project.mapper")
public class XdclassApplication  {

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

}
public interface UserMapper {

    //推荐使用#{}取值,不要用${},因为存在注入的风险
     @Insert("INSERT INTO user(name,phone,create_time,age) VALUES(#{name}, #{phone}, #{createTime},#{age})")
     @Options(useGeneratedKeys=true, keyProperty="id", keyColumn="id")   //keyProperty java对象的属性;keyColumn表示数据库的字段
     int insert(User user);
}
@RestController
@RequestMapping("/api/v1/user")
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 功能描述: user 保存接口
     * @return
     */
    @GetMapping("add")
    public Object add(){

        User user = new User();
        user.setAge(11);
        user.setCreateTime(new Date());
        user.setName("xdclass");
        user.setPhone("10010000");
        int id = userService.add(user);

       return JsonData.buildSuccess(id);
    }

增删改查

1、控制台打印sql语句        
    #增加打印sql语句,一般用于本地开发测试
    mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

2、增加mapper代码        
    @Select("SELECT * FROM user")
    @Results({
        @Result(column = "create_time",property = "createTime")  //javaType = java.util.Date.class        
    })
    List<User> getAll();

    @Select("SELECT * FROM user WHERE id = #{id}")
    @Results({
         @Result(column = "create_time",property = "createTime")
    })
    User findById(Long id);

    @Update("UPDATE user SET name=#{name} WHERE id =#{id}")
    void update(User user);

    @Delete("DELETE FROM user WHERE id =#{userId}")
    void delete(Long userId);

 3、增加API

    @GetMapping("find_all")
    public Object findAll(){
       return JsonData.buildSuccess(userMapper.getAll());
    }

    @GetMapping("find_by_Id")
    public Object findById(long id){
       return JsonData.buildSuccess(userMapper.findById(id));
    }

    @GetMapping("del_by_id")
    public Object delById(long id){
    userMapper.delete(id);
       return JsonData.buildSuccess();
    }

    @GetMapping("update")
    public Object update(String name,int id){
        User user = new User();
        user.setName(name);
        user.setId(id);
        userMapper.update(user);
        return JsonData.buildSuccess();
    }

事务

简介:讲解什么是数据库事务,常见的隔离级别和传播行为

1、介绍什么是事务,单机事务,分布式事务处理等

2、讲解场景的隔离级别
    Serializable: 最严格,串行处理,消耗资源大
    Repeatable Read:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据
    Read Committed:大多数主流数据库的默认事务等级
    Read Uncommitted:保证了读取过程中不会读取到非法数据。
3、讲解常见的传播行为
    PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务,最常见的选择。

    PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。

    PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。

    PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起, 两个事务之间没有关系,一个异常,一个提交,不会同时回滚

    PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

    PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常

1、service逻辑引入事务 @Transantional(propagation=Propagation.REQUIRED)
2、service代码
    @Override
    @Transactional
    public int addAccount() {
        User user = new User();
        user.setAge(9);
        user.setCreateTime(new Date());
        user.setName("事务测试");
        user.setPhone("000121212");

        userMapper.insert(user);
        int a = 1/0;

        return user.getId();
    }
#可以自动识别
#spring.datasource.driver-class-name =com.mysql.jdbc.Driver

spring.datasource.url=jdbc:mysql://118.24.175.34:3306/java_test?useUnicode=true&characterEncoding=utf-8
spring.datasource.username =root
spring.datasource.password =p@ssw0rd

#使用阿里巴巴druid数据源,默认使用自带的
spring.datasource.type =com.alibaba.druid.pool.DruidDataSource

#开启控制台打印sql
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
package net.xdclass.base_project.service.impl;

import java.util.Date;

import net.xdclass.base_project.domain.User;
import net.xdclass.base_project.mapper.UserMapper;
import net.xdclass.base_project.service.UserService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserServiceImpl implements UserService{

     @Autowired
     private UserMapper userMapper;

    @Override
    public int add(User user) {
        userMapper.insert(user);
        int id = user.getId();
        return id;
    }

    @Override
    @Transactional(propagation=Propagation.REQUIRED)
    public int addAccount() {
        User user = new User();
        user.setAge(88);
        user.setCreateTime(new Date());
        user.setName("测试事务啦加入事务");
        user.setPhone("10010101010");
        userMapper.insert(user);
        int i = 19/0;

        return 0;
    }

}
//测试事务
@GetMapping("add_account")
public Object addAccount(){
    int id = userService.addAccount();
    return JsonData.buildSuccess(id);
}

redis

redis官网 https://redis.io/download
新手入门redis在线测试工具:http://try.redis.io/

1、快速安装  https://redis.io/download#installation
            wget http://download.redis.io/releases/redis-4.0.9.tar.gz
            tar xzf redis-4.0.9.tar.gz
            cd redis-4.0.9
            make        
        启动服务端:src/redis-server
        启动客户端:src/redis-cli

2、默认是本地访问的,需要开放外网访问
    1)打开redis.conf文件在NETWORK部分修改
       注释掉bind 127.0.0.1可以使所有的ip访问redis
       修改 protected-mode,值改为no
简介:使用springboot-starter整合reids实战

    1、官网:https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-redis
        集群文档:https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/#cluster

    2、springboot整合redis相关依赖引入
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

    3、相关配置文件配置
        #=========redis基础配置=========
        spring.redis.database=0
        spring.redis.host=127.0.0.1
        spring.redis.port=6390
        # 连接超时时间 单位 ms(毫秒)
        spring.redis.timeout=3000

        #=========redis线程池设置=========
        # 连接池中的最大空闲连接,默认值也是8。
        spring.redis.pool.max-idle=200

        #连接池中的最小空闲连接,默认值也是0。
        spring.redis.pool.min-idle=200

        # 如果赋值为-1,则表示不限制;pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
        spring.redis.pool.max-active=2000

        # 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时
        spring.redis.pool.max-wait=1000

    4、常见redistemplate种类讲解和缓存实操(使用自动注入)

        1、注入模板
        @Autowired
        private StirngRedisTemplate strTplRedis

        2、类型String,List,Hash,Set,ZSet
        对应的方法分别是opsForValue()、opsForList()、opsForHash()、opsForSet()、opsForZSet()
#通过触发器,去控制什么时候进行热加载部署新的文件
spring.devtools.restart.trigger-file=trigger.txt

#自定义启动banner文件的路径
spring.banner.location=banner.txt


#=========redis基础配置=========
    spring.redis.database=0
    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    # 连接超时时间 单位 ms(毫秒)
            spring.redis.timeout=3000

    #=========redis线程池设置=========
    # 连接池中的最大空闲连接,默认值也是8。
            spring.redis.pool.max-idle=200

    #连接池中的最小空闲连接,默认值也是0。
            spring.redis.pool.min-idle=200

    # 如果赋值为-1,则表示不限制;pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
            spring.redis.pool.max-active=2000

    # 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时
            spring.redis.pool.max-wait=1000
package net.xdclass.base_project.controller;

import net.xdclass.base_project.domain.JsonData;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/api/v1/redis")
public class RdisTestController {


    @Autowired
    private StringRedisTemplate redisTpl; //jdbcTemplate

    @GetMapping(value="add")
    public Object add(){

        //opsForValue : Returns the operations performed on simple values (or Strings in Redis terminology).

        redisTpl.opsForValue().set("name", "xdclass2018");

        return JsonData.buildSuccess();

    }

    @GetMapping(value="get")
    public Object get(){

        String value = redisTpl.opsForValue().get("name");        
        return JsonData.buildSuccess(value);

    }

}

常用客户端 https://redisdesktop.com/download
封装redis工具类并操作

package net.xdclass.base_project.utils;

import java.io.IOException;

import org.springframework.util.StringUtils;

import com.fasterxml.jackson.databind.ObjectMapper;

public class JsonUtils {

    private static ObjectMapper objectMapper = new ObjectMapper();

    //对象转字符串
    public static <T> String obj2String(T obj){
        if (obj == null){
            return null;
        }
        try {
            return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    //字符串转对象
    public static <T> T string2Obj(String str,Class<T> clazz){
        if (StringUtils.isEmpty(str) || clazz == null){
            return null;
        }
        try {
            return clazz.equals(String.class)? (T) str :objectMapper.readValue(str,clazz);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}
package net.xdclass.base_project.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

/**
 * 功能描述:redis工具类
 *
 * <p> 创建时间:Apr 29, 2018 10:07:30 PM </p> 
 *
 *@作者 小D课堂  小D
 */
@Component
public class RedisClient {



    @Autowired
    private StringRedisTemplate redisTpl; //jdbcTemplate



    /**
     * 功能描述:设置key-value到redis中
     * @param key
     * @param value
     * @return
     */
    public boolean set(String key ,String value){
        try{
            redisTpl.opsForValue().set(key, value);
            return true;
        }catch(Exception e){
            e.printStackTrace();
            return false;
        }

    }


    /**
     * 功能描述:通过key获取缓存里面的值
     * @param key
     * @return
     */
    public String get(String key){
        return redisTpl.opsForValue().get(key);
    }



//    @Autowired
//    private StringRedisTemplate redisTemplate;
//    
//    
//     /** 
//     * 通过字符串key获取值 
//     * @param key 键 
//     * @return 值 
//     */  
//    public String get(String key){  
//        return key==null?null:redisTemplate.opsForValue().get(key);  
//    } 
//    
//    
//    /** 
//     * 普通缓存放入 
//     * @param key 键 
//     * @param value 值 
//     * @return true成功 false失败 
//     */  
//    public boolean set(String key,String value) {  
//         try {  
//            redisTemplate.opsForValue().set(key, value);  
//            return true;  
//        } catch (Exception e) {  
//            e.printStackTrace();  
//            return false;  
//        }  
//          
//    }  
//    


//    
//    /**
//     * 功能描述:设置某个key过期时间
//     * @param key
//     * @param time
//     * @return
//     */
//      public boolean expire(String key,long time){  
//            try {  
//                if(time>0){  
//                    redisTemplate.expire(key, time, TimeUnit.SECONDS);  
//                }  
//                return true;  
//            } catch (Exception e) {  
//                e.printStackTrace();  
//                return false;  
//            }  
//        }  
//
//          
//      
//      
//      /**
//       * 功能描述:根据key 获取过期时间 
//       * @param key
//       * @return
//       */
//      public long getExpire(String key){  
//            return redisTemplate.getExpire(key,TimeUnit.SECONDS);  
//        }  
//      
//      
//          /** 
//         * 递增 
//         * @param key 键 
//         * @return 
//         */  
//        public long incr(String key, long delta){    
//            return redisTemplate.opsForValue().increment(key, delta);  
//        }  
//        
//        
//        /** 
//         * 递减 
//         * @param key 键 
//         * @param delta 要减少几
//         * @return 
//         */  
//        public long decr(String key, long delta){    
//            return redisTemplate.opsForValue().increment(key, -delta);    
//        }    
//        
//        //==============Map结构=====================
//        
//        
//        //==============List结构=====================
//        
//        
//        

}
package net.xdclass.base_project.domain;

import java.util.Date;

public class User {

    private int age;

    private String pwd;

    private String phone;

    private Date createTime;



    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public User() {
        super();
    }

    public User(int age, String pwd, String phone, Date createTime) {
        super();
        this.age = age;
        this.pwd = pwd;
        this.phone = phone;
        this.createTime = createTime;
    }

}
package net.xdclass.base_project.domain;

import java.io.Serializable;

/**
 * 功能描述:响应结果类
 *
 * <p>
 * 创建时间:Apr 29, 2018 4:08:36 PM
 * </p>
 *
 * @作者 小D课堂 小D
 */
public class JsonData implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    private Integer code; // 状态码 0 表示成功,1表示处理中,-1表示失败
    private Object data; // 数据
    private String msg;// 描述

    public JsonData() {
    }

    public JsonData(Integer code, Object data, String msg) {
        this.code = code;
        this.data = data;
        this.msg = msg;
    }

    // 成功,传入数据
    public static JsonData buildSuccess() {
        return new JsonData(0, null, null);
    }

    // 成功,传入数据
    public static JsonData buildSuccess(Object data) {
        return new JsonData(0, data, null);
    }

    // 失败,传入描述信息
    public static JsonData buildError(String msg) {
        return new JsonData(-1, null, msg);
    }

    // 失败,传入描述信息,状态码
    public static JsonData buildError(String msg, Integer code) {
        return new JsonData(code, null, msg);
    }

    // 成功,传入数据,及描述信息
    public static JsonData buildSuccess(Object data, String msg) {
        return new JsonData(0, data, msg);
    }

    // 成功,传入数据,及状态码
    public static JsonData buildSuccess(Object data, int code) {
        return new JsonData(code, data, null);
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    @Override
    public String toString() {
        return "JsonData [code=" + code + ", data=" + data + ", msg=" + msg
                + "]";
    }

}
package net.xdclass.base_project.controller;

import java.util.Date;

import net.xdclass.base_project.domain.JsonData;
import net.xdclass.base_project.domain.User;
import net.xdclass.base_project.utils.JsonUtils;
import net.xdclass.base_project.utils.RedisClient;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/api/v1/redis")
public class RdisTestController {


    @Autowired
    private StringRedisTemplate redisTpl; //jdbcTemplate

    @Autowired
    private RedisClient redis;

    @GetMapping(value="add")
    public Object add(){

        //redisTpl.opsForValue().set("name", "xdclass2018");
        redis.set("username", "xddddddd");
        return JsonData.buildSuccess();

    }

    @GetMapping(value="get")
    public Object get(){

        //String value = redisTpl.opsForValue().get("name");
        String value = redis.get("username");
        return JsonData.buildSuccess(value);

    }


    @GetMapping(value="save_user")
    public Object saveUser(){
        User user = new User(1, "abc", "11", new Date());
        String userStr = JsonUtils.obj2String(user);
        boolean flag = redis.set("base:user:11", userStr);
        return JsonData.buildSuccess(flag);

    }

    @GetMapping(value="find_user")
    public Object findUser(){

        String userStr = redis.get("base:user:11");
        User user = JsonUtils.string2Obj(userStr, User.class);

        return JsonData.buildSuccess(user);

    }


}

任务

    1、常见定时任务 Java自带的java.util.Timer类
        timer:配置比较麻烦,时间延后问题
        timertask:不推荐

    2、Quartz框架
        配置更简单
        xml或者注解

    3、SpringBoot使用注解方式开启定时任务
        1)启动类里面 @EnableScheduling开启定时任务,自动扫描
        2)定时任务业务类 加注解 @Component被容器扫描
        3)定时执行的方法加上注解 @Scheduled(fixedRate=2000) 定期执行一次

@SpringBootApplication //一个注解顶下面3个
@EnableScheduling    //开启定时任务
public class XdclassApplication {

    public static void main(String[] args) {
        SpringApplication.run(XdclassApplication.class, args);
    }
}
/**
 * 功能描述:定时任务业务类
 *
 * <p> 创建时间:Apr 30, 2018 10:21:48 AM </p> 
 *
 *@作者 小D课堂  小D
 */
@Component
public class TestTask {


    @Scheduled(fixedRate=2000) //两秒执行一次
    public void sum(){
        System.out.println("当前时间:"+new Date());
    }


}
    1、cron 定时任务表达式 @Scheduled(cron="*/1 * * * * *") 表示每秒
        1)crontab 工具  https://tool.lu/crontab/
    2、fixedRate: 定时多久执行一次(上一次开始执行时间点后xx秒再次执行;)
    3、fixedDelay: 上一次执行结束时间点后xx秒再次执行
    4、fixedDelayString:  字符串形式,可以通过配置文件指定
package net.xdclass.base_project.task;

import java.util.Date;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

/**
 * 功能描述:定时任务业务类
 *
 * <p> 创建时间:Apr 30, 2018 10:21:48 AM </p> 
 *
 *@作者 小D课堂  小D
 */
@Component
public class TestTask {


    @Scheduled(fixedRateString="2000")//两秒执行一次
    //@Scheduled(cron="*/2 * * * * *")
    public void sum() throws InterruptedException{

        Thread.sleep(4000L);
        System.out.println("结束 当前时间:"+new Date());

    }


    //@Scheduled(cron="*/1 * * * * *")
    public void sum2(){
        System.out.println("cron 每秒 当前时间:"+new Date());
    }



}

异步任务

    2、启动类里面使用@EnableAsync注解开启功能,自动扫描

    3、定义异步任务类并使用@Component标记组件被容器扫描,异步方法加上@Async
        注意点:
            1)要把异步任务封装到类里面,不能直接写到Controller
            2)增加Future<String> 返回结果 AsyncResult<String>("task执行完成");  
            3)如果需要拿到结果 需要判断全部的 task.isDone()
    4、通过注入方式,注入到controller里面,如果测试前后区别则改为同步则把Async注释掉
package net.xdclass.base_project;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication //一个注解顶下面3个
@EnableScheduling    //开启定时任务
@EnableAsync   //开启异步任务
public class XdclassApplication {

    public static void main(String[] args) {
        SpringApplication.run(XdclassApplication.class, args);
    }
}
package net.xdclass.base_project.task;

import java.util.concurrent.Future;

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;

/**
 * 功能描述:异步任务业务类
 *
 * <p> 创建时间:Apr 30, 2018 11:25:15 PM </p> 
 *
 *@作者 小D课堂  小D
 */
@Component
@Async
public class AsyncTask {


    public void task1() throws InterruptedException{
        long begin = System.currentTimeMillis();
        Thread.sleep(1000L);
        long end = System.currentTimeMillis();
        System.out.println("任务1耗时="+(end-begin));
    }


    public void task2() throws InterruptedException{
        long begin = System.currentTimeMillis();
        Thread.sleep(2000L);
        long end = System.currentTimeMillis();
        System.out.println("任务2耗时="+(end-begin));
    }


    public void task3() throws InterruptedException{
        long begin = System.currentTimeMillis();
        Thread.sleep(3000L);
        long end = System.currentTimeMillis();
        System.out.println("任务3耗时="+(end-begin));
    }


    //获取异步结果


    public Future<String> task4() throws InterruptedException{
        long begin = System.currentTimeMillis();
        Thread.sleep(2000L);
        long end = System.currentTimeMillis();
        System.out.println("任务4耗时="+(end-begin));
        return new AsyncResult<String>("任务4");
    }


    public Future<String> task5() throws InterruptedException{
        long begin = System.currentTimeMillis();
        Thread.sleep(3000L);
        long end = System.currentTimeMillis();
        System.out.println("任务5耗时="+(end-begin));
        return new AsyncResult<String>("任务5");
    }

    public Future<String> task6() throws InterruptedException{
        long begin = System.currentTimeMillis();
        Thread.sleep(1000L);
        long end = System.currentTimeMillis();
        System.out.println("任务6耗时="+(end-begin));
        return new AsyncResult<String>("任务6");
    }

}
package net.xdclass.base_project.controller;

import java.util.concurrent.Future;

import net.xdclass.base_project.domain.JsonData;
import net.xdclass.base_project.task.AsyncTask;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/api/v1")
public class UserController {


    @Autowired
    private AsyncTask task;

    @GetMapping("async_task")
    public JsonData exeTask() throws InterruptedException{

        long begin = System.currentTimeMillis();

//        task.task1();
//        task.task2();
//        task.task3();
//        long end = System.currentTimeMillis();
//        long total = end-begin;
//        System.out.println("执行总耗时="+total);
//        return JsonData.buildSuccess(total);

        Future<String> task4 = task.task4();
        Future<String> task5 = task.task5();
        Future<String> task6 = task.task6();
        for(;;){
            if (task4.isDone() && task5.isDone() && task6.isDone()) {
                break;
            }
        }


        long end = System.currentTimeMillis();

        long total = end-begin;
        System.out.println("执行总耗时="+total);
        return JsonData.buildSuccess(total);
    }


}

日志

1.常用处理java的日志组件 slf4j,log4j,logback,common-logging 等

2、logback介绍:基于Log4j基础上大量改良,不能单独使用,推荐配合日志框架SLF4J来使用
    logback当前分成三个模块:logback-core,logback-classic和logback-access;
    logback-core是其它两个模块的基础模块

3、Logback的核心对象:
    Logger:日志记录器
    Appender:指定日志输出的目的地,目的地可以是控制台,文件
    Layout:日志布局 格式化日志信息的输出
    <br />
4、日志级别:DEBUG < INFO < WARN < ERROR<br />
<br />
    =log4j示例=        <br />
     ### 设置###<br />
    log4j.rootLogger = debug,stdout,D,E<br />
<br />

输出信息到控制抬 ###

log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n

    ### 输出DEBUG 级别以上的日志到=D://logs/error.log ###
    log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
    log4j.appender.D.File = D://logs/log.log
    log4j.appender.D.Append = true
    log4j.appender.D.Threshold = DEBUG 
    log4j.appender.D.layout = org.apache.log4j.PatternLayout
    log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

    ### 输出ERROR 级别以上的日志到=D://logs/error.log ###
    log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
    log4j.appender.E.File =E://logs/error.log 
    log4j.appender.E.Append = true
    log4j.appender.E.Threshold = ERROR 
    log4j.appender.E.layout = org.apache.log4j.PatternLayout
    log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n 

    ===========logback============

4、Log4j日志转换为logback在线工具(支持log4j.properties转换为logback.xml,不支持 log4j.xml转换为logback.xml)
 https://logback.qos.ch/translator/

自定义Logback配置实战

    1、官网介绍:https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-logging

       各个组件案例:https://logback.qos.ch/manual/index.html

    2、分析SpringBoot启动日志
        1)默认情况下,Spring Boot将日志输出到控制台

    3、整合Logback实战
        1)创建 日志文件logback-spring.xml,官方推荐 -spring.xml结尾
            默认加载加载配置顺序 logback-spring.xml, logback-spring.groovy, logback.xml, or logback.groovy

        注释:
            <configuration> 子节点
            <appender></appender>                       
            <logger></logger>
            <root></root>(要加在最后)
//logback-spring.xml

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>

     <appender name="consoleApp" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>
                %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
            </pattern>
        </layout>
    </appender>

    <appender name="fileInfoApp" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
             <level>ERROR</level>
            <onMatch>DENY</onMatch>
            <onMismatch>ACCEPT</onMismatch>
        </filter>
        <encoder>
            <pattern>
                %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
            </pattern>
        </encoder>
        <!-- 滚动策略 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 路径 -->
            <fileNamePattern>app_log/log/app.info.%d.log</fileNamePattern>
        </rollingPolicy>
    </appender>

    <appender name="fileErrorApp" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <encoder>
            <pattern>
                %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
            </pattern>
        </encoder>

        <!-- 设置滚动策略 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 路径 -->
            <fileNamePattern>app_log/log/app.err.%d.log</fileNamePattern>

            <!-- 控制保留的归档文件的最大数量,超出数量就删除旧文件,假设设置每个月滚动,
            且<maxHistory> 是1,则只保存最近1个月的文件,删除之前的旧文件 -->
             <MaxHistory>1</MaxHistory>

        </rollingPolicy>
    </appender>
   <root level="INFO">  
        <appender-ref ref="consoleApp"/>
        <appender-ref ref="fileInfoApp"/>
        <appender-ref ref="fileErrorApp"/>
    </root>
</configuration>
package net.xdclass.base_project.controller;

import net.xdclass.base_project.domain.JsonData;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/api/v1")
public class UserController {



    private Logger logger = LoggerFactory.getLogger(this.getClass());


    @GetMapping("log")
    public Object testLog(){

        logger.debug("this is debug level");
        logger.info("this is info level ");
        logger.warn("this is warn level ");
        logger.error("this is error level");
        return JsonData.buildSuccess();
    }

}

ElasticSearch

    前言:介绍ES的主要特点和使用场景,新特性讲解
    mysql:like 模糊,性能问题,

    solr:针对企业,Lucene
    elasticsearch:针对数据量特别大,PB,TB
      纯java开发,springboot使用,5.6版本
      es升级4->5版本,改动大,但是5版本后,改动不大


elasticSearch主要特点

    1、特点:全文检索,结构化检索,数据统计、分析,接近实时处理,分布式搜索(可部署数百台服务器),处理PB级别的数据
        搜索纠错,自动完成
    2、使用场景:日志搜索,数据聚合,数据监控,报表统计分析

    3、国内外使用者:维基百科,Stack Overflow,GitHub

新特性讲解

    1、6.2.x版本基于Lucene 7.x,更快,性能进一步提升,对应的序列化组件,升级到Jackson 2.8
        mysql:database   table   rocord
        es   : index      type(只能存在一个)    document

    2、推荐使用5.0版本推出的Java REST/HTTP客户端,依赖少,比Transport使用更方便,在基准测试中,性能并不输于Transport客户端,

    在5.0到6.0版本中,每次有对应的API更新, 文档中也说明,推荐使用这种方式进行开发使用,所有可用节点间的负载均衡
    在节点故障和特定响应代码的情况下进行故障转移,失败的连接处罚(失败的节点是否重试取决于失败的连续次数;失败的失败次数越多,客户端在再次尝试同一节点之前等待的时间越长)

    3、(重要)不再支持一个索引库里面多个type,6.x版本已经禁止一个index里面多个type,所以一个index索引库只能存在1个type

    官方文档:
    1、6.0更新特性
     https://www.elastic.co/guide/en/elasticsearch/reference/6.0/release-notes-6.0.0.html#breaking-java-6.0.0
    2、6.1更新特性 
    https://www.elastic.co/guide/en/elasticsearch/reference/6.1/release-notes-6.1.0.html

快熟部署ElastcSearch5.6.x

配置JDK1.8
    使用wget 下载elasticsearch安装包
    wget  https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.6.8.tar.gz
解压
    tar -zxvf elasticsearch-5.6.8.tar.gz
官网:https://www.elastic.co/products/elasticsearch

外网访问配置:    
    config目录下面elasticsearch.yml
    修改为 network.host: 0.0.0.0


配置es出现相关问题处理(阿里云、腾讯云,亚马逊云安装问题集合):
    1、问题一
        Java HotSpot(TM) 64-Bit Server VM warning: INFO: os::commit_memory(0x00000000c5330000, 986513408, 0) failed; error='Cannot allocate memory' (errno=12)
        #
        # There is insufficient memory for the Java Runtime Environment to continue.
        # Native memory allocation (mmap) failed to map 986513408 bytes for committing reserved memory.
        # An error report file with more information is saved as:
        # /usr/local/software/temp/elasticsearch-6.2.2/hs_err_pid1912.log
    解决:内存不够,购买阿里云的机器可以动态增加内存

    2、问题二
        [root@iZwz95j86y235aroi85ht0Z bin]# ./elasticsearch
        [2018-02-22T20:14:04,870][WARN ][o.e.b.ElasticsearchUncaughtExceptionHandler] [] uncaught exception in thread [main]
        org.elasticsearch.bootstrap.StartupException: java.lang.RuntimeException: can not run elasticsearch as root
        at org.elasticsearch.bootstrap.Elasticsearch.init(Elasticsearch.java:125) ~[elasticsearch-6.2.2.jar:6.2.2]
        at org.elasticsearch.bootstrap.Elasticsearch.execute(Elasticsearch.java:112) ~[elasticsearch-6.2.2.jar:6.2.2]
        at org.elasticsearch.cli.EnvironmentAwareCommand.execute(EnvironmentAwareCommand.java:86) ~[elasticsearch-6.2.2.jar:6.2.2]
        at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:124) ~[elasticsearch-cli-6.2.2.jar:6.2.2]
    解决:用非root用户
        添加用户:useradd -m 用户名  然后设置密码  passwd 用户名


    3、问题三
        ./elasticsearch
        Exception in thread "main" java.nio.file.AccessDeniedException: /usr/local/software/temp/elasticsearch-6.2.2/config/jvm.options
       解决:权限不够 chmod 777 -R 当前es目录

    常见配置问题资料:https://www.jianshu.com/p/c5d6ec0f35e0

lasticSearch5.6.8测试数据准备
简介: ElasticSearch5.6.x简单测试
1、步骤 https://www.elastic.co/guide/en/elasticsearch/reference/5.6/index.html
2、使用POSTMAN 工具

    基础
        查看集群状态:localhost:9200/_cat/health?v
        查看索引列表:localhost:9200/_cat/indices?v

4、SpringBoot2.x整合elasticsearch5.6.x
简介:SpringBoot2.x整合elasticSearch5.6.8实战

    Spring Data Elasticsearch文档地址
    https://docs.spring.io/spring-data/elasticsearch/docs/3.0.6.RELEASE/reference/html/

    版本说明:SpringBoot整合elasticsearch
        https://github.com/spring-projects/spring-data-elasticsearch/wiki/Spring-Data-Elasticsearch---Spring-Boot---version-matrix

    1、添加maven依赖                    
        <dependency>  
           <groupId>org.springframework.boot</groupId>  
           <artifactId>spring-boot-starter-data-elasticsearch</artifactId>  
       </dependency>  

    2、接口继承ElasticSearchRepository,里面有很多默认实现
        注意点:
            索引名称记得小写,类属性名称也要小写
        新建实体对象article
        加上类注解 @Document(indexName = "blog", type = "article")


    3、配置文件:

ELASTICSEARCH (ElasticsearchProperties)

        spring.data.elasticsearch.cluster-name=elasticsearch # Elasticsearch cluster name.
        spring.data.elasticsearch.cluster-nodes=localhost:9300 # Comma-separated list of cluster node addresses.
        spring.data.elasticsearch.repositories.enabled=true # Whether to enable Elasticsearch repositories.

    4、QueryBuilder使用
    https://www.elastic.co/guide/en/elasticsearch/client/java-api/1.3/query-dsl-queries.html

    //单个匹配,搜索name为jack的文档  
    QueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "搜"); 

    4、查看es数据

        查看索引信息:http://localhost:9200/_cat/indices?v
        查看某个索引库结构:http://localhost:9200/blog
        查看某个对象:http://localhost:9200/blog/article/1
package net.xdclass.base_project.repository;


import net.xdclass.base_project.domain.Article;

import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;


@Component 
//@Repository
public interface ArticleRepository extends ElasticsearchRepository<Article, Long> {


}
package net.xdclass.base_project.controller;

import net.xdclass.base_project.domain.Article;
import net.xdclass.base_project.domain.JsonData;
import net.xdclass.base_project.repository.ArticleRepository;

import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/api/v1/article")
public class ArticleController {

    @Autowired
    private ArticleRepository articleRepository;

    @GetMapping("save")
    public Object save(long id,String title){

        Article article = new Article();
        article.setId(id);
        article.setPv(123);
        article.setContent("springboot整合elasticsearch,这个是新版本 2018年录制");
        article.setTitle(title);
        article.setSummary("搜索框架整合");

        articleRepository.save(article);

        return JsonData.buildSuccess();
    }

    @GetMapping("search")
    public Object search(String title){

        //QueryBuilder queryBuilder = QueryBuilders.matchAllQuery(); //搜索全部文档
        QueryBuilder queryBuilder = QueryBuilders.matchQuery("title", title); 

        Iterable<Article> list =  articleRepository.search(queryBuilder);

        return JsonData.buildSuccess(list);
    }

}

消息服务

介绍

JMS介绍和使用场景及基础编程模型
简介:讲解什么是小写队列,JMS的基础知识和使用场景
1、什么是JMS: Java消息服务(Java Message Service),Java平台中关于面向消息中间件的接口

2、JMS是一种与厂商无关的 API,用来访问消息收发系统消息,它类似于JDBC(Java Database Connectivity)。这里,JDBC 是可以用来访问许多不同关系数据库的 API

3、使用场景:
    1)跨平台 
    2)多语言 
    3)多项目
    4)解耦
    5)分布式事务

    6)流量控制
    7)最终一致性
    8)RPC调用
        上下游对接,数据源变动->通知下属
4、概念    
    JMS提供者:Apache ActiveMQ、RabbitMQ、Kafka、Notify、MetaQ、RocketMQ
    JMS生产者(Message Producer)
    JMS消费者(Message Consumer)
    JMS消息
    JMS队列
    JMS主题

    JMS消息通常有两种类型:点对点(Point-to-Point)、发布/订阅(Publish/Subscribe)

5、编程模型
    MQ中需要用的一些类
    ConnectionFactory :连接工厂,JMS 用它创建连接
    Connection :JMS 客户端到JMS Provider 的连接
    Session: 一个发送或接收消息的线程
    Destination :消息的目的地;消息发送给谁.
    MessageConsumer / MessageProducer: 消息接收者,消费者

springboot - 图1

springboot - 图2

springboot - 图3

ActiveMQ

腾讯云服务器安装aq的问题解决方案

https://blog.csdn.net/qq_38880340/article/details/85147033

http://activemq.2283324.n4.nabble.com/Connection-refused-with-embeded-AMQ-in-Tomcat-td2363352.html

客户端访问 http://118.24.175.34:8161/admin/

简介:介绍ActiveMQ5.x消息队列基础特性和本地快速安装
    特点:
        1)支持来自Java,C,C ++,C#,Ruby,Perl,Python,PHP的各种跨语言客户端和协议
        2)支持许多高级功能,如消息组,虚拟目标,通配符和复合目标
        3) 完全支持JMS 1.1和J2EE 1.4,支持瞬态,持久,事务和XA消息
        4) Spring支持,ActiveMQ可以轻松嵌入到Spring应用程序中,并使用Spring的XML配置机制进行配置
        5) 支持在流行的J2EE服务器(如TomEE,Geronimo,JBoss,GlassFish和WebLogic)中进行测试
        6) 使用JDBC和高性能日志支持非常快速的持久化
        ...

    1、下载地址:http://activemq.apache.org/activemq-5153-release.html
    2、快速开始:http://activemq.apache.org/getting-started.html
    3、如果我们是32位的机器,就双击win32目录下的activemq.bat,如果是64位机器,则双击win64目录下的activemq.bat
    4、bin目录里面启动 选择对应的系统版本和位数,activeMQ start 启动
    5、启动后访问路径http://127.0.0.1:8161/

    6、用户名和密码默认都是admin
    7、官方案例集合
        https://github.com/spring-projects/spring-boot/tree/master/spring-boot-samples
    面板:    
    Name:队列名称。
    Number Of Pending Messages:等待消费的消息个数。
    Number Of Consumers:当前连接的消费者数目
    Messages Enqueued:进入队列的消息总个数,包括出队列的和待消费的,这个数量只增不减。
    Messages Dequeued:已经消费的消息数量。

点对点消息(p2p)
简介:SpringBoot2.x整合ActiveMQ实战之点对点消息

1、官网地址:https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-activemq

2、加入依赖
    <!-- 整合消息队列ActiveMQ -->
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-activemq</artifactId>  
    </dependency>  

    <!-- 如果配置线程池则加入 -->
    <dependency>  
        <groupId>org.apache.activemq</groupId>  
        <artifactId>activemq-pool</artifactId>  
    </dependency>

 3、application.properties配置文件配置
     #整合jms测试,安装在别的机器,防火墙和端口号记得开放
    spring.activemq.broker-url=tcp://127.0.0.1:61616

    #集群配置
    #spring.activemq.broker-url=failover:(tcp://localhost:61616,tcp://localhost:61617)

    spring.activemq.user=admin
    spring.activemq.password=admin
    #下列配置要增加依赖
    spring.activemq.pool.enabled=true
    spring.activemq.pool.max-connections=100

  4、springboot启动类 @EnableJms,开启支持jms

  5、模拟请求
      localhost:8080/api/v1/order?msg=12312321321312

  6、消费者:实时监听对应的队列
      @JmsListener(destination = "order.queue")

订阅模式(pub/sub)

    1、需要加入配置文件,支持发布订阅模型,默认只支持点对点
        #default point to point
        spring.jms.pub-sub-domain=true

注意点:

    1、默认消费者并不会消费订阅发布类型的消息,这是由于springboot默认采用的是p2p模式进行消息的监听
        修改配置:spring.jms.pub-sub-domain=true

    2、@JmsListener如果不指定独立的containerFactory的话是只能消费queue消息
        修改订阅者container:containerFactory="jmsListenerContainerTopic"

        //需要给topic定义独立的JmsListenerContainer
        @Bean
        public JmsListenerContainerFactory<?> jmsListenerContainerTopic(ConnectionFactory activeMQConnectionFactory) {
            DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
            bean.setPubSubDomain(true);
            bean.setConnectionFactory(activeMQConnectionFactory);
            return bean;
        }

        在配置文件里面,注释掉 #spring.jms.pub-sub-domain=true
#通过触发器,去控制什么时候进行热加载部署新的文件
spring.devtools.restart.trigger-file=trigger.txt

#自定义启动banner文件的路径
spring.banner.location=banner.txt

#整合jms测试,安装在别的机器,防火墙和端口号记得开放
spring.activemq.broker-url=tcp://118.24.175.34:61616

#集群配置
#spring.activemq.broker-url=failover:(tcp://localhost:61616,tcp://localhost:61617)

spring.activemq.user=admin
spring.activemq.password=admin
#下列配置要增加依赖
spring.activemq.pool.enabled=true
spring.activemq.pool.max-connections=100


#default point to point
# spring.jms.pub-sub-domain=true
package net.xdclass.base_project;

import javax.jms.ConnectionFactory;
import javax.jms.Queue;
import javax.jms.Topic;

import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;

@SpringBootApplication //一个注解顶下面3个
@EnableJms
public class XdclassApplication {

    @Bean
    public Topic topic(){
        return new ActiveMQTopic("video.topic");
    }

    @Bean
    public Queue queue(){
        return new ActiveMQQueue("common.queue");
    }

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


     @Bean
        public JmsListenerContainerFactory<?> jmsListenerContainerTopic(ConnectionFactory activeMQConnectionFactory) {
            DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
            bean.setPubSubDomain(true);
            bean.setConnectionFactory(activeMQConnectionFactory);
            return bean;
        }
}
package net.xdclass.base_project.service.impl;

import javax.jms.Destination;
import javax.jms.Queue;
import javax.jms.Topic;

import net.xdclass.base_project.service.ProducerService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Service;

/**
 * 功能描述:消息生产者
 *
 * <p> 创建时间:May 2, 2018 11:29:47 PM </p> 
 *
 *@作者 小D课堂  小D
 */
@Service
public class ProducerServiceImpl implements ProducerService{

    @Autowired
    private Queue queue;

    @Autowired
    private JmsMessagingTemplate jmsTemplate; //用来发送消息到broker的对象

    //发送消息,destination是发送到的队列,message是待发送的消息
    @Override
    public void sendMessage(Destination destination, String message) {

        jmsTemplate.convertAndSend(destination, message);

    }


    //发送消息,destination是发送到的队列,message是待发送的消息
    @Override
    public void sendMessage(final String message) {
        jmsTemplate.convertAndSend( message);

    }

    //=======发布订阅相关代码=========

    @Autowired
    private Topic topic;


     @Override
    public void publish(String msg) {
        this.jmsTemplate.convertAndSend(this.topic, msg);

    }


}
package net.xdclass.base_project.jms;

import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
@Component

public class OrderConsumer {

    @JmsListener(destination="order.queue")
    public void receiveQueue(String text){
        System.out.println("OrderConsumer收到的报文为:"+text);
    }
}
package net.xdclass.base_project.jms;

import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;

@Component
public class TopicSub {


    @JmsListener(destination="video.topic", containerFactory="jmsListenerContainerTopic")
    public void receive1(String text){
        System.out.println("video.topic 消费者:receive1="+text);
    }


    @JmsListener(destination="video.topic", containerFactory="jmsListenerContainerTopic")
    public void receive2(String text){
        System.out.println("video.topic 消费者:receive2="+text);
    }


    @JmsListener(destination="video.topic", containerFactory="jmsListenerContainerTopic")
    public void receive3(String text){
        System.out.println("video.topic 消费者:receive3="+text);
    }


}
package net.xdclass.base_project.jms;

import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;

@Component
public class CommonConsumer {


    @JmsListener(destination="common.queue")
    public void receiveQueue(String text){
        System.out.println("CommonConsumer收到的报文为:"+text);
    }

}
package net.xdclass.base_project.controller;

import javax.jms.Destination;

import net.xdclass.base_project.domain.JsonData;
import net.xdclass.base_project.service.ProducerService;

import org.apache.activemq.command.ActiveMQQueue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 功能描述:模拟微信支付回调
 *
 * <p> 创建时间:May 3, 2018 9:53:14 PM </p> 
 *
 *@作者 小D课堂  小D
 */
@RestController
@RequestMapping("/api/v1")
public class OrderController {

    @Autowired
    private ProducerService producerService;
    /**
     * 功能描述:微信支付回调接口
     * @param msg 支付信息
     * @return
     */
    @GetMapping("order")
    public Object order(String msg){

        Destination destination = new ActiveMQQueue("order.queue");

        producerService.sendMessage(destination, msg);

       return JsonData.buildSuccess();
    }



    @GetMapping("common")
    public Object common(String msg){
        producerService.sendMessage(msg);    
       return JsonData.buildSuccess();
    }

    @GetMapping("topic")
    public Object topic(String msg) {
        producerService.publish(msg);
        return JsonData.buildSuccess();
    }

//    
//    /**
//     * 功能描述:微信支付回调接口
//     * @param msg 支付信息
//     * @return
//     */
//    @GetMapping("comment")
//    public Object comment(String msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException, UnsupportedEncodingException{
//      
//        /**
//        * 创建一个消息实例,包含 topic、tag 和 消息体           
//       */
//       Message message = new Message("commentTopic","add", msg.getBytes(RemotingHelper.DEFAULT_CHARSET));
//       
//       //同步的方式,会有返回结果,发送的是普通消息
//       SendResult result = msgProducer.getProducer().send(message);
//       
//       System.out.println("发送响应:MsgId:" + result.getMsgId() + ",发送状态:" + result.getSendStatus());
//     
//       return JsonData.buildSuccess();
//    }
//    
//    
//    


}

RocketMQ

1、Apache RocketMQ作为阿里开源的一款高性能、高吞吐量的分布式消息中间件
2、特点
    1)在高压下1毫秒内响应延迟超过99.6%。
    2)适合金融类业务,高可用性跟踪和审计功能。
    3)支持发布订阅模型,和点对点
    4)支持拉pull和推push两种消息模式
    5)单一队列百万消息
    6)支持单master节点,多master节点,多master多slave节点
    ...
3、概念
    Producer:消息生产者
    Producer Group:消息生产者组,发送同类消息的一个消息生产组

    Consumer:消费者
    Consumer Group:消费同个消息的多个实例

    Tag:标签,子主题(二级分类),用于区分同一个主题下的不同业务的消息

    Topic:主题
    Message:消息
    Broker:MQ程序,接收生产的消息,提供给消费者消费的程序
    Name Server:给生产和消费者提供路由信息,提供轻量级的服务发现和路由        

3、官网地址:http://rocketmq.apache.org/

学习资源:
    1)http://jm.taobao.org/2017/01/12/rocketmq-quick-start-in-10-minutes/    
    2)https://www.jianshu.com/p/453c6e7ff81c

RocketMQ4.x本地快速部署
简介:RocketMQ4.x本地快速部署

1、安装前提条件(推荐)
    64bit OS, Linux/Unix/Mac
    64bit JDK 1.8+;

2、快速开始 http://rocketmq.apache.org/docs/quick-start/
   下载安装包:https://www.apache.org/dyn/closer.cgi?path=rocketmq/4.2.0/rocketmq-all-4.2.0-bin-release.zip

   路径:/Users/jack/Desktop/person/springboot/资料/第13章/第5课/rocketmq-all-4.2.0-bin-release/bin

3、解压压缩包 
    1)进入bin目录,启动namesrv
         nohup sh mqnamesrv & 

    2) 查看日志 tail -f nohup.out
    结尾:The Name Server boot success. serializeType=JSON 表示启动成功

    3、启动broker   
        nohup sh mqbroker -n 127.0.0.1:9876 &

    4)、关闭nameserver broker执行的命令
        sh mqshutdown namesrv
        sh mqshutdown broker

7、RoekerMQ4.x可视化控制台讲解
简介:RoekerMQ4.x可视化控制台讲解

    1、下载 https://github.com/apache/rocketmq-externals
    2、编译打包  mvn clean package -Dmaven.test.skip=true
    3、target目录 通过java -jar的方式运行

    4、无法连接获取broker信息
        1)修改配置文件,名称路由地址为 namesrvAddr,例如我本机为
        2)src/main/resources/application.properties
            rocketmq.config.namesrvAddr=192.168.0.101:9876

    5、默认端口 localhost:8080

    6、注意:
        在阿里云,腾讯云或者虚拟机,记得检查端口号和防火墙是否启动

Springboot2.x整合RocketMQ4.x实战上集
简介:Springboot2.x整合RocketMQ4.x实战,加入相关依赖,开发生产者代码

启动nameser和broker

1、加入相关依赖
    <dependency>  
        <groupId>org.apache.rocketmq</groupId>  
        <artifactId>rocketmq-client</artifactId>  
        <version>${rocketmq.version}</version>  
    </dependency>  
    <dependency>  
        <groupId>org.apache.rocketmq</groupId>  
        <artifactId>rocketmq-common</artifactId>  
        <version>${rocketmq.version}</version>  
    </dependency>  


2、application.properties加入配置文件        
    # 消费者的组名
    apache.rocketmq.consumer.PushConsumer=orderConsumer
    # 生产者的组名
    apache.rocketmq.producer.producerGroup=Producer
    # NameServer地址
    apache.rocketmq.namesrvAddr=127.0.0.1:9876

3、开发MsgProducer
     /**
     * 生产者的组名
     */
    @Value("${apache.rocketmq.producer.producerGroup}")
    private String producerGroup;

    /**
     * NameServer 地址
     */
    @Value("${apache.rocketmq.namesrvAddr}")
    private String namesrvAddr;

    private  DefaultMQProducer producer ;
        <br />
    public DefaultMQProducer getProducer(){<br />
        return this.producer;<br />
    }<br />
<br />
    [@PostConstruct ](/PostConstruct ) <br />
    public void defaultMQProducer() {<br />
        //生产者的组名<br />
        producer = new DefaultMQProducer(producerGroup);<br />
        //指定NameServer地址,多个地址以 ; 隔开<br />
        //如 producer.setNamesrvAddr("192.168.100.141:9876;192.168.100.142:9876;192.168.100.149:9876");<br />
        producer.setNamesrvAddr(namesrvAddr);<br />
        producer.setVipChannelEnabled(false);<br />
<br />

try {
/**

  • Producer对象在使用之前必须要调用start初始化,只能初始化一次
    */
    producer.start();
        } catch (Exception e) {
            e.printStackTrace();
        } 

        // producer.shutdown();  一般在应用上下文,关闭的时候进行关闭,用上下文监听器

    }

9、Springboot2.x整合RocketMQ4.x实战下集
简介:Springboot2.x整合RocketMQ4.x实战,开发消费者代码,常见问题处理

1、创建消费者


问题:
    1、Caused by: org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to <172.17.42.1:10911> failed 

    2、com.alibaba.rocketmq.client.exception.MQClientException: Send [1] times, still failed, cost [1647]ms, Topic: TopicTest1, BrokersSent: [broker-a, null, null]

    3、org.apache.rocketmq.client.exception.MQClientException: Send [3] times, still failed, cost [497]ms, Topic: TopicTest, BrokersSent: [chenyaowudeMacBook-Air.local,     chenyaowudeMacBook-Air.local, chenyaowudeMacBook-Air.local]
    解决:多网卡问题处理
    1、设置producer:  producer.setVipChannelEnabled(false);
    2、编辑ROCKETMQ 配置文件:broker.conf(下列ip为自己的ip)
        namesrvAddr = 192.168.0.101:9876
        brokerIP1 = 192.168.0.101



    4、DESC: service not available now, maybe disk full, CL:
    解决:修改启动脚本runbroker.sh,在里面增加一句话即可:        
    JAVA_OPT="${JAVA_OPT} -Drocketmq.broker.diskSpaceWarningLevelRatio=0.98"
    (磁盘保护的百分比设置成98%,只有磁盘空间使用率达到98%时才拒绝接收producer消息)


    常见问题处理:
        https://blog.csdn.net/sqzhao/article/details/54834761
        https://blog.csdn.net/mayifan0/article/details/67633729
        https://blog.csdn.net/a906423355/article/details/78192828
#通过触发器,去控制什么时候进行热加载部署新的文件
spring.devtools.restart.trigger-file=trigger.txt

#自定义启动banner文件的路径
spring.banner.location=banner.txt



# 消费者的组名
apache.rocketmq.consumer.PushConsumer=orderConsumer
# 生产者的组名
apache.rocketmq.producer.producerGroup=Producer
# NameServer地址
apache.rocketmq.namesrvAddr=118.24.175.34:9876
package net.xdclass.base_project.jms;

import javax.annotation.PostConstruct;

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class MsgProducer {
     /**
     * 生产者的组名
     */
    @Value("${apache.rocketmq.producer.producerGroup}")
    private String producerGroup;

    /**
     * NameServer 地址
     */
    @Value("${apache.rocketmq.namesrvAddr}")
    private String namesrvAddr;

    private  DefaultMQProducer producer ;


    public DefaultMQProducer getProducer(){
        return this.producer;
    }




    @PostConstruct
    public void init() {
        //生产者的组名
        producer = new DefaultMQProducer(producerGroup);
        //指定NameServer地址,多个地址以 ; 隔开
        //如 producer.setNamesrvAddr("192.168.100.141:9876;192.168.100.142:9876;192.168.100.149:9876"); 
        producer.setNamesrvAddr(namesrvAddr);

        producer.setVipChannelEnabled(false);

        try {
            /**
             * Producer对象在使用之前必须要调用start初始化,只能初始化一次
             */
            producer.start();

        } catch (Exception e) {
            e.printStackTrace();
        } 

        // producer.shutdown();  一般在应用上下文,关闭的时候进行关闭,用上下文监听器

    }


}
package net.xdclass.base_project.jms;

import javax.annotation.PostConstruct;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class MsgConsumer {
    /**
     * 消费者的组名
     */
    @Value("${apache.rocketmq.consumer.PushConsumer}")
    private String consumerGroup;

    /**
     * NameServer 地址
     */
    @Value("${apache.rocketmq.namesrvAddr}")
    private String namesrvAddr;





    @PostConstruct
    public void defaultMQPushConsumer() {
        //消费者的组名
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(consumerGroup);
        //指定NameServer地址,多个地址以 ; 隔开
        consumer.setNamesrvAddr(namesrvAddr);

        try {
            //设置consumer所订阅的Topic和Tag,*代表全部的Tag
            consumer.subscribe("testTopic", "*");

            //CONSUME_FROM_LAST_OFFSET 默认策略,从该队列最尾开始消费,跳过历史消息
            //CONSUME_FROM_FIRST_OFFSET 从队列最开始开始消费,即历史消息(还储存在broker的)全部消费一遍
            consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);


            //MessageListenerOrderly 这个是有序的
            //MessageListenerConcurrently 这个是无序的,并行的方式处理,效率高很多
            consumer.registerMessageListener((MessageListenerConcurrently) (list, context) -> {
                try {
                    for (MessageExt messageExt : list) {

                        System.out.println("messageExt: " + messageExt);//输出消息内容

                        String messageBody = new String(messageExt.getBody(), RemotingHelper.DEFAULT_CHARSET);

                        System.out.println("消费响应:msgId : " + messageExt.getMsgId() + ",  msgBody : " + messageBody);//输出消息内容
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    return ConsumeConcurrentlyStatus.RECONSUME_LATER; //稍后再试
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; //消费成功
            });


            consumer.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
package net.xdclass.base_project.controller;

import java.io.UnsupportedEncodingException;

import net.xdclass.base_project.domain.JsonData;
import net.xdclass.base_project.jms.MsgProducer;

import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 功能描述:模拟微信支付回调
 *
 * <p> 创建时间:May 3, 2018 9:53:14 PM </p> 
 *
 *@作者 小D课堂  小D
 */
@RestController
@RequestMapping("/api/v1")
public class OrderController {


    @Autowired
    private MsgProducer msgProducer;

    /**
     * 功能描述:微信支付回调接口
     * @param msg 支付信息
     * @param tag 消息二级分类
     * @return
     */
    @GetMapping("order")
    public Object order(String msg, String tag) throws MQClientException, RemotingException, MQBrokerException, InterruptedException, UnsupportedEncodingException{

        /**
        * 创建一个消息实例,包含 topic、tag 和 消息体           
       */
       Message message = new Message("testTopic",tag, msg.getBytes(RemotingHelper.DEFAULT_CHARSET));

       SendResult result = msgProducer.getProducer().send(message);

       System.out.println("发送响应:MsgId:" + result.getMsgId() + ",发送状态:" + result.getSendStatus());

       return JsonData.buildSuccess();
    }




//    
//    /**
//     * 功能描述:微信支付回调接口
//     * @param msg 支付信息
//     * @return
//     */
//    @GetMapping("comment")
//    public Object comment(String msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException, UnsupportedEncodingException{
//      
//        /**
//        * 创建一个消息实例,包含 topic、tag 和 消息体           
//       */
//       Message message = new Message("commentTopic","add", msg.getBytes(RemotingHelper.DEFAULT_CHARSET));
//       
//       //同步的方式,会有返回结果,发送的是普通消息
//       SendResult result = msgProducer.getProducer().send(message);
//       
//       System.out.println("发送响应:MsgId:" + result.getMsgId() + ",发送状态:" + result.getSendStatus());
//     
//       return JsonData.buildSuccess();
//    }
//    
//    
//    



}

多环境

1、SpringBoot多环境配置介绍和项目实战(核心知识)
简介:SpringBoot介绍多环境配置和使用场景

1、不同环境使用不同配置
    例如数据库配置,在开发的时候,我们一般用开发数据库,而在生产环境的时候,我们是用正式的数据
2、配置文件存放路径
    classpath根目录的“/config”包下
    classpath的根目录下
3、spring boot允许通过命名约定按照一定的格式(application-{profile}.properties)来定义多个配置文件
//application-dev.properties
test.url=dev.com
//application-test.properties
test.url=test.com
//application.properties

#springboot多环境配置======begin
test.url=local


#指定哪个profile
spring.profiles.active=dev
#springboot多环境配置======end
package net.xdclass.base_project.controller;


import net.xdclass.base_project.domain.JsonData;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 *@作者 小D课堂  小D
 */
@RestController
@RequestMapping("/api/v1")
public class OrderController {

    @Value("${test.url}")
    private String domain;

    /**
     * 功能描述:微信支付回调接口
     * @param msg 支付信息
     * @return
     */
    @GetMapping("order")
    public Object order(String msg){

       return JsonData.buildSuccess(domain);
    }

}

响应式编程

SprinBoot2.x响应式编程简介
简介:讲解什么是reactive响应式编程和使用的好处

1、基础理解:
    依赖于事件,事件驱动(Event-driven)
    一系列事件称为“流”
    异步
    非阻塞

    观察者模式

网上的一个例子:
    int b= 2;
    int c=3
    int a = b+c  //命令式编程后续b和c变化,都不影响a
    b=5;

    int b= 2;
    int c= 3
    int a = b+c  //响应式编程中,a的变化,会和b、c的变化而变化(事件驱动)
    b=5;

2、官网:https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-webflux
    SpingBoot2底层是用spring5,开始支持响应式编程,Spring又是基于Reactor试下响应式。
学习资料
    1、reactive-streams学习资料:http://www.reactive-streams.org/
    2、web-flux相关资料:https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#spring-webflux

SpringBoot2.x响应式编程webflux介绍
简介:讲解SpringBoot2.x响应式编程介绍 Mono、Flux对象和优缺点

1、Spring WebFlux是Spring Framework 5.0中引入的新的反应式Web框架
与Spring MVC不同,它不需要Servlet API,完全异步和非阻塞,并 通过Reactor项目实现Reactive Streams规范。
RxJava



2、Flux和Mono  User List<User>
    1)简单业务而言:和其他普通对象差别不大,复杂请求业务,就可以提升性能
    2)通俗理解:
        Mono 表示的是包含 0 或者 1 个元素的异步序列
            mono->单一对象 User     redis->用户ID-》唯一的用户Mono<User>  

        Flux 表示的是包含 0 到 N 个元素的异步序列
            flux->数组列表对象 List<User>   redis->男性用户->Flux<User>
        Flux 和 Mono 之间可以进行转换


3、Spring WebFlux有两种风格:基于功能和基于注解的。基于注解非常接近Spring MVC模型,如以下示例所示:
    第一种:
        @RestController 
        @RequestMapping(“/ users”)
         public  class MyRestController {

            @GetMapping(“/ {user}”)
             public Mono <User> getUser( @PathVariable Long user){
                 // ...
            }

            @GetMapping(“/ {user} / customers”)
             public Flux <Customer> getUserCustomers( @PathVariable Long user){
                 // ...
            }

            @DeleteMapping(“/ {user}”)
             public Mono <User> deleteUser( @PathVariable Long user){
                 // ...
            }

        }
    第二种: 路由配置与请求的实际处理分开
        @Configuration
         public  class RoutingConfiguration {

            @Bean
             public RouterFunction <ServerResponse> monoRouterFunction(UserHandler userHandler){
                 return route(GET( “/ {user}”).and(accept(APPLICATION_JSON)),userHandler :: getUser)
                        .andRoute(GET(“/ {user} / customers”).and(accept(APPLICATION_JSON)),userHandler :: getUserCustomers)
                        .andRoute(DELETE(“/ {user}”).and(accept(APPLICATION_JSON)),userHandler :: deleteUser);
            }

        }

        @Component
        public class UserHandler {

            公共 Mono <ServerResponse> getUser(ServerRequest请求){
                 // ...
            }

            public Mono <ServerResponse> getUserCustomers(ServerRequest request){
                 // ...
            }

            公共 Mono <ServerResponse> deleteUser(ServerRequest请求){
                 // ...
            }
        }



4、Spring WebFlux应用程序不严格依赖于Servlet API,因此它们不能作为war文件部署,也不能使用src/main/webapp目录

5、可以整合多个模板引擎
    除了REST Web服务外,您还可以使用Spring WebFlux提供动态HTML内容。Spring WebFlux支持各种模板技术,包括Thymeleaf,FreeMarker

SpringBoot2.x webflux实战
简介:webflux响应式编程实战

1、WebFlux中,请求和响应不再是WebMVC中的ServletRequest和ServletResponse,而是ServerRequest和ServerResponse

2、加入依赖,如果同时存在spring-boot-starter-web,则会优先用spring-boot-starter-web
    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>

    测试
    localhost:8080/api/v1/user/test

3、启动方式默认是Netty,8080端口
4、参考:https://spring.io/blog/2016/04/19/understanding-reactive-types
package net.xdclass.base_project.service;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import net.xdclass.base_project.domain.User;

import org.springframework.stereotype.Service;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Service
public class UserService {


    private static final Map<String, User> dataMap = new HashMap<>();

    static{
        dataMap.put("1", new User("1", "小X老师"));
        dataMap.put("2", new User("2", "小D老师"));
        dataMap.put("3", new User("3", "小C老师"));
        dataMap.put("4", new User("4", "小L老师"));
        dataMap.put("5", new User("5", "小A老师"));
        dataMap.put("6", new User("6", "小S老师"));
        dataMap.put("7", new User("7", "小S老师"));
    }

    /**
     * 功能描述:返回用户列表
     * @return
     */
    public Flux<User> list(){
        Collection<User> list = UserService.dataMap.values();

        return Flux.fromIterable(list);
    }


    /**
     * 功能描述:根据id查找用户
     * @param id
     * @return
     */
    public Mono<User> getById(final String id){
        return Mono.justOrEmpty(UserService.dataMap.get(id));
    }

   /**
    * 功能描述:根据id删除用户
    * @param id
    * @return
    */
    public Mono<User> del(final String id){
        return Mono.justOrEmpty(UserService.dataMap.remove(id));
    }



}
package net.xdclass.base_project.controller;


import java.time.Duration;

import net.xdclass.base_project.domain.User;
import net.xdclass.base_project.service.UserService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 *@作者 小D课堂  小D
 */
@RestController
@RequestMapping("/api/v1/user")
public class UserController {


    //@Autowired
    //private UserService userService;

    private final UserService userService;

     public UserController(final UserService userService) {
        this.userService = userService;
    }


    @GetMapping("/test")
    public Mono<String> test(){
        return Mono.just("hello 小D课堂");
    }


    /**
     * 功能描述:根据id找用户
     * @param id
     * @return
     */
    @GetMapping("find")
    public Mono<User> findByid(final String id){
        return userService.getById(id);
    }


    /**
     * 功能描述:删除用户
     * @param id
     * @return
     */
    @GetMapping("del")
    public Mono<User> del(final String id){
        return userService.del(id);
    }

    /**
     * 功能描述:列表
     * @return
     */
    @GetMapping(value="list",produces=MediaType.APPLICATION_STREAM_JSON_VALUE)
    public Flux<User> list(){
        return userService.list().delayElements(Duration.ofSeconds(2));
    }

}
package base_project.base;

import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;

import reactor.core.publisher.Mono;

//@RunWith(SpringRunner.class)  //底层用junit  SpringJUnit4ClassRunner
//@SpringBootTest(classes={XdclassApplication.class})//启动整个springboot工程
public class WebClientTest {



    @Test
    public void testBase(){

        Mono<String> bodyMono = WebClient.create().get()
        .uri("http://localhost:8080/api/v1/user/find?id=1")
        .accept(MediaType.APPLICATION_JSON)
        .retrieve().bodyToMono(String.class);

        System.out.println(bodyMono.block());

    }


    @Test
    public void testBase2(){

        Mono<String> bodyMono = WebClient.create().get()
        .uri("http://localhost:8080/api/v1/user/find?id={id}",2)
        .accept(MediaType.APPLICATION_JSON)
        .retrieve().bodyToMono(String.class);

        System.out.println(bodyMono.block());

    }


}

推送

服务端推送常用技术介绍

推荐使用websocket,sse只能服务器往浏览器推

    1、客户端轮询:ajax定时拉取
    2、服务端主动推送:WebSocket
        全双工的,本质上是一个额外的tcp连接,建立和关闭时握手使用http协议,其他数据传输不使用http协议
        更加复杂一些,适用于需要进行复杂双向数据通讯的场景

    3、服务端主动推送:SSE (Server Send Event)
        html5新标准,用来从服务端实时推送数据到浏览器端,
        直接建立在当前http连接上,本质上是保持一个http长连接,轻量协议
        简单的服务器数据推送的场景,使用服务器推送事件    
        学习资料:http://www.w3school.com.cn/html5/html_5_serversentevents.asp
package net.xdclass.base_project.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/sse")
public class SSEController {

    @RequestMapping(value = "/get_data", produces = "text/event-stream;charset=UTF-8")
    public String push() {

          try {
              Thread.sleep(1000); 
              //第三方数据源调用
          } catch (InterruptedException e) {
              e.printStackTrace();
          }

          return "data:xdclass 行情" + Math.random() + "\n\n";
    }
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">  
//需要判断浏览器支不支持,可以去w3c进行查看

var source = new EventSource('sse/get_data');
source.onmessage = function (event) {
  console.info(event.data);
  document.getElementById('result').innerText = event.data
};

</script>  
</head>

<body>
模拟股票行情
 <div>xdclass test</div>  
    <div id="result"></div>  
</body>


</html>

部署

发布

阿里云Linux服务器部署JDK8实战
简介:在阿里云服务器上安装JDK8和配置环境变量

lnux下使用wget下载jdk8:
进到目录/usr/local/software

配置环境变量
vim /etc/profile
加入
export JAVA_HOME=/usr/local/software/jdk8
export PATH=$PATH:$JAVA_HOME/bin
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export JAVA_HOME PATH CLASSPATH

使用 source /etc/profile   让配置立刻生效

3、阿里云服务器SpringBoot2.x生产环境部署实战
简介:讲解SpringBoot生产环境部署和常见注意事项

1、去除相关生产环境没用的jar
    比如热部署dev-tool

2、本地maven打包成jar包 
    mvn clean package  -Dmaven.test.skip=true 跳过测试

3、服务器安装jdk,上传Jar包
    上传工具:
        windows:
            winscp
            securtyCRT
        mac:
            filezilla
    ssh root@120.79.160.143
    访问路径 http://120.79.160.143:8080/api/v1/user/find

    java -jar xxxx.jar

    守护进程、系统服务、shell脚本
    https://blog.csdn.net/weixin_41574643/article/details/79716052

    nohup java -jar shareniu.jar >temp.txt &

    打包指定配置文件
        1、使用maven的profiles
        2、使用springboot的profile=active

访问不了
    1、阿里云防火墙是否开启,可以选择关闭,关闭是不安全的,可以选择开放端口
    2、阿里云的安全访问组,开启对应的端口,如果应用是以80端口启动,则默认可以访问



4、成熟的互联网公司应该有的架构
    本地提交生产代码->gitlab仓库->Jenkins自动化构建->运维或者开发人员发布

监控

SpringBoot2.x监控Actuator实战上集
简介:讲解SpringBoot使用actuator监控配置和使用

可用性:100%,99.9%

1、介绍什么是actuator
    官方介绍:
        Spring Boot包含许多附加功能,可帮助您在将应用程序投入生产时监视和管理应用程序。 可以选择使用HTTP端点或JMX来管理和监控您的应用程序,自动应用于审计,健康和指标收集;

    一句话:springboot提供用于监控和管理生产环境的模块
    官方文档:https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#production-ready
2、加入依赖
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-actuator</artifactId>  
    </dependency> 

3、加入上述依赖后,访问几个url
        /actuator/health
        /actuator/info
        /actuator

SpringBoot2.x监控Actuator实战下集及生产环境建议(核心知识)
简介:SpringBoot2.x监控Actuator实战下集及生产环境建议,SpringBoot新旧版本区别

注意点: 网上的资料大多数没有讲到访问的前缀
端点基础路径由 / 调整到 /actuator
        如:/info调整为/actuator/info 
            /actuator/xxx

1、只能访问几个url
    1)需要在配置文件中加入下列配置
        management.endpoints.web.exposure.include=*

    2)官网说明:https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-security-actuator

        原因:
            出于安全考虑,除/ health和/ info之外的所有执行器默认都是禁用的。 management.endpoints.web.exposure.include属性可用于启用执行器
2、建议
    在设置management.endpoints.web.exposure.include之前,请确保暴露的执行器不包含敏感信息和/
    或通过将其放置在防火墙进行控制,不对外进行使用

    禁用的端点将从应用程序上下文中完全删除。如果您只想更改端点所暴露的技术,请改用 include和exclude属性 。
    例子:
        开启全部:management.endpoints.web.exposure.include=*
        开启某个:management.endpoints.web.exposure.include=metrics
        关闭某个:management.endpoints.web.exposure.exclude=metrics

    或者用springadmin进行管理
        相关资料:https://www.cnblogs.com/ityouknow/p/8440455.html

    或者用自己编写脚本监控
    CPU、内存、磁盘、nginx的http响应状态码200,404,5xx 

3、介绍常用的几个
    /health     查看应用健康指标
    /actuator/metrics    查看应用基本指标列表
    /actuator/metrics/{name}        通过上述列表,查看具体 查看具体指标
    /actuator/env        显示来自Spring的 ConfigurableEnvironment的属性    ,不能暴露
#可以自动识别
#spring.datasource.driver-class-name =com.mysql.jdbc.Driver

spring.datasource.url=jdbc:mysql://118.24.175.34:3306/java_test?useUnicode=true&characterEncoding=utf-8
spring.datasource.username =root
spring.datasource.password =p@ssw0rd

#使用阿里巴巴druid数据源,默认使用自带的
spring.datasource.type =com.alibaba.druid.pool.DruidDataSource

#开启控制台打印sql
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl


#开启监控端点
management.endpoints.web.exposure.include=*
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>net.xdclass</groupId>
  <artifactId>base_project</artifactId>
  <version>0.0.1-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
    </parent>

<properties>


</properties>

        <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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


<!-- 热部署 -->
        <dependency>  
             <groupId>org.springframework.boot</groupId>  
             <artifactId>spring-boot-devtools</artifactId>  
             <optional>true</optional>  
         </dependency>

    <!-- 引入starter-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.2</version>            
            </dependency>

     <!-- MySQL的JDBC驱动包    -->    
             <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency> 
    <!-- 引入第三方数据源 -->        
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.6</version>
            </dependency>


<!-- springboot监控依赖 -->
<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-actuator</artifactId>  
</dependency> 


    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

springboot - 图4