美文网首页Java
Spring 全家桶学习笔记

Spring 全家桶学习笔记

作者: Vchar_Fred | 来源:发表于2019-07-09 00:13 被阅读0次

    简要说明

    使用的是springboot2.x

    项目包说明:

    |---springboot-demo1                 springboot启动类,使用不使用spring-boot-starter-parent作为父级依赖
    |---springboot-demo2                 springboot启动类,使用使用spring-boot-starter-parent作为父级依赖
    |---springboot-demo3                 springboot的controller相关注解说明
    |---springboot-dev-demo              springboot 热部署
    |---springboot-test-demo             springboot 测试类demo
    |---springboot-customer-banner-demo  自定义springboot启动样式  
    |---springboot-thymeleaf-demo        使用thymeleaf做页面渲染引擎
    |---springboot-freemarker-demo       使用freemarker做页面渲染引擎
    |---springboot-filter-demo           filter、listener、interceptor的使用
    |---springboot-war-demo              springboot 打war包
    |---springboot-timer-demo            springboot 定时任务
    |---springboot-exception-demo        springboot 异常出来demo
    |---springboot-mybatis-demo          springboot 中使用mybatis
    |---springboot-redis-demo            springboot 中使用redis
    |---springboot-elasticsearch-demo    springboot 中使用elasticsearch
    |---springboot-mq-demo               springboot 中使用activemq
    |---springboot-rocketMQ-demo         springboot 中使用rocketMQ
    |---springboot-more-environment      springboot 多环境配置
    |---springboot-webflux-demo          springboot webflux响应式编程
    |---springboot-actuator-demo         springboot 监控
    

    项目源代码: https://github.com/vcharfred/spring-demo.git


    一、SpringMvc

    TODO


    二、Springboot

    1、 简单Demo

    1.1 添加maven依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <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>
    
        <!--首先依赖spring boot 父级maven 包-->
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.6.RELEASE</version>
        </parent>
    
        <artifactId>springboot-demo2</artifactId>
        <groupId>top.vchar.demo.spring</groupId>
        <version>1.0-SNAPSHOT</version>
        <packaging>jar</packaging>
        <description>使用springboot作为父级</description>
    
        <dependencies>
            <!--spring boot web 包-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
        </dependencies>
        <build>
            <plugins>
                <!--spring boot 打包工具-->
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    

    当项目中有自己的父级依赖时,就不能使用spring boot的父级依赖了,则使用下面这种

    <?xml version="1.0" encoding="UTF-8"?>
    <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">
        <parent>
            <artifactId>spring-demo</artifactId>
            <groupId>top.vchar.demo.spring</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>springboot-demo1</artifactId>
        <packaging>jar</packaging>
        <description>使用自己的作为父级</description>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <!-- 从Spring Boot导入依赖关系管理 -->
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                    <version>2.1.6.RELEASE</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
        <dependencies>
            <!--spring boot web 包-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
        </dependencies>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    

    1.2 编写启动类

    package top.vchar.demo.spring;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * <p> springboot启动类,使用@SpringBootApplication注入相关配置 </p>
     *
     * @author vchar fred
     * @version 1.0
     * @create_date 2019/7/7 23:14
     */
    @SpringBootApplication
    @RestController
    public class StartDemo1Application {
    
        public static void main(String[] args){
            SpringApplication.run(StartDemo1Application.class);
        }
    
        @RequestMapping("/")
        public String home(){
            return "hello word";
        }
    
    }
    

    或(推荐使用上面那种)

    package top.vchar.demo.spring;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * <p>springboot启动类,不使用@SpringBootApplication注入相关配置; 建议使用@SpringBootApplication,它已经包含这些注解了 </p>
     *
     * @author vchar fred
     * @version 1.0
     * @create_date 2019/7/7 23:17
     */
    @RestController //相当于Controller+ResponseBody;即自动是ajax的请求
    @EnableAutoConfiguration //spring 会自动装配相关的配置,这个是必须有的
    @ComponentScan //根据spring的相关注解注入bean
    public class StartDemo2Application {
    
        public static void main(String[] args){
            SpringApplication.run(StartDemo1Application.class);
        }
    
        @RequestMapping("/demo2")
        public String home(){
            return "hello word demo2";
        }
    
    }    
    

    @EnableAutoConfiguration注解主要用于告诉spring boot根据当前引用的配置和jar包,自动启用相关的配置。
    这个注解只能扫描到它所属的类的相关注解配置。因此需要添加@ComponentScan注解。当不用注入过多的信息时会使用到这种方式

    @SpringBootApplication包含了EnableAutoConfiguration;更多的配置可以查看源码

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(
        excludeFilters = {@Filter(
        type = FilterType.CUSTOM,
        classes = {TypeExcludeFilter.class}
    ), @Filter(
        type = FilterType.CUSTOM,
        classes = {AutoConfigurationExcludeFilter.class}
    )}
    )
    public @interface SpringBootApplication {
    
        //排除自启动项
        @AliasFor(annotation = EnableAutoConfiguration.class)
        Class<?>[] exclude() default {};
        
        //排除自动启动的beanName
        @AliasFor(annotation = EnableAutoConfiguration.class)
        String[] excludeName() default {};
    
        //扫描包
        @AliasFor(annotation = ComponentScan.class,attribute = "basePackages")
        String[] scanBasePackages() default {};
    
        //扫描类
        @AliasFor(annotation = ComponentScan.class,attribute = "basePackageClasses")
        Class<?>[] scanBasePackageClasses() default {};
    }  
    

    运行main方法即可,访问 http://localhost:8080/


    2、springboot的controller相关注解说明

    2.1 @RestController

    这个注解相比于@Controller可以让我们在返回json等数据类型时,不用再方法上加@ResponseBody注解

    package top.vchar.demo.spring.controller;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * <p> @RestController使用 </p>
     *
     * @author vchar fred
     * @version 1.0
     * @create_date 2019/7/7 23:43
     */
    @RestController
    public class DemoController {
        
        //返回String
        @RequestMapping("/")
        public String home(){
            return "hello word";
        }
        
        /**
         * 使用bean对象传参
         * @param member 用户信息
         * @return
         */
        @RequestMapping("/v1/save_user")
        public String saveUser1(Member member){
            if(null==member){
                return "save fail";
            }
            System.out.println(member.toString());
            return "save success";
        }
        
    } 
    

    2.2 获取POST请求的body信息 @RequestBody

    注意事项:

    a.需要指定http头为Content-Type为application/json;
    b.必须使用body传送参数
    
    
    /**
     * 使用bean对象传参
     * 注意:a.需要指定http头为Content-Type为application/json
     *      b.使用body传送参数
     * @param member 用户信息
     * @return
     */
    @RequestMapping("/v2/save_user")
    public String saveUser2(@RequestBody Member member){
        if(null==member){
            return "save fail";
        }
        System.out.println(member.toString());
        return "save success";
    }    
    

    2.3 @GetMapping和@PostMapping

    package top.vchar.demo.spring.controller;
    
    import org.springframework.web.bind.annotation.*;
    
    /**
     * <p> 只允许GET请求 </p>
     *
     * @author vchar fred
     * @version 1.0
     * @create_date 2019/7/7 23:48
     */
    @RestController
    public class GetController {
        /**
         * 只允许GET请求
         */
        @RequestMapping(path = "/get/v1/user/{id}", method = RequestMethod.GET)
        public String findUserById1(@PathVariable("id") String uid){
            if("123456".equals(uid)){
                return "find it";
            }
            return "no this user";
        }
    
        /**
         * 只允许GET请求
         */
        @GetMapping(path = "/get/v2/user/{id}")
        public String findUserById2(@PathVariable("id") String uid){
            if("123456".equals(uid)){
                return "find it";
            }
            return "no this user";
        }
    }    
    

    @PathVariable可以将使其接收到url上动态拼接的参数,默认是必传

    其他类似的

    @GetMapping(path = "/get/v2/user/{id}")==@RequestMapping(path = "/get/v1/user/{id}", method = RequestMethod.GET)
    @PostMapping(path = "/post/v2/user/{id}")==@RequestMapping(path = "/post/v1/user/{id}", method = RequestMethod.POST)
    @PutMapping(path = "/put/v2/user/{id}")==@RequestMapping(path = "/put/v1/user/{id}", method = RequestMethod.PUT)
    ...
    

    2.4 @RequestHeader获取http头信息

    使用这个注解可以获取请求头信息

    /**
     * 获取http头信息
     */
    @RequestMapping("/get_header")
    public String saveUser3(@RequestHeader(value = "access_token", required = false) String accessToken){
        return accessToken;
    }
    

    3、springboot官方推荐的目录规范

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

    同个文件的加载顺序,静态资源文件
    Spring Boot 默认会挨个从
    META/resources > resources > static > public 里面找是否存在相应的资源,如果有则直接返回。
    可以配置resources.static-locations修改,多个使用英文逗号分隔

    • 页面模板引擎 Thymeleaf加入如下maven依赖

        <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
      
    • 页面模板引擎 freemarker加入如下maven依赖

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

    4、热部署

    加入如下依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional>
    </dependency>
    
    • 指定文件不进行热部署 spring.devtools.restart.exclude=static/,public/
    • 手工触发重启 spring.devtools.restart.trigger-file=trigger.txt 改代码不重启,通过一个文本去控制,里面可以填写版本号等配置

    5、注入配置文件

    5.1 直接注入属性值

    • 首先需要在类上加上 @PropertySource({"classpath:resource.properties"})指定需要加载的配置文件

    • 定义一个属性值,在上面加上@Value注解

        @RestController
        @PropertySource({"classpath:resource.properties"})
        public class AutoConfigController {
        
            @Value("${soft.version}")
            private String version;
        
            @GetMapping("/get/soft_version")
            public String getSoftVersion(){
                System.out.println(version);
                return version;
            }
        }
      

    5.2 注入实体类配置文件

    直接注入

    @Configuration
    @PropertySource(value="classpath:resource.properties")
    public class ServerConfig2 {
    
        @Value("${soft.domain}")
        private String domain;
        @Value("${soft.name}")
        private String name;
    
        public String getDomain() {
            return domain;
        }
    
        public void setDomain(String domain) {
            this.domain = domain;
        }
    
        public String getSoftname() {
            return name;
        }
    
        public void setSoftname(String softname) {
            this.name = softname;
        }
    
        @Override
        public String toString() {
            return "ServerConfig{" +
                    "domain='" + domain + '\'' +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
    

    使用前缀注入;需要引入如下依赖

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

    列子:

    @Configuration
    @PropertySource(value="classpath:resource.properties")
    @ConfigurationProperties(prefix = "soft")
    public class ServerConfig {
    
        private String domain;
    
        private String name;
    
        public String getDomain() {
            return domain;
        }
    
        public void setDomain(String domain) {
            this.domain = domain;
        }
    
        public String getName() {
            return name;
        }
        
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "ServerConfig{" +
                    "domain='" + domain + '\'' +
                    ", name='" + name + '\'' +
                    '}';
        }
    }  
    

    6、springboot测试

    6.1 单元测试

    引入如下依赖

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

    列子:

    @RunWith(SpringRunner.class)  //底层用junit  SpringJUnit4ClassRunner
    @SpringBootTest(classes={StartApplication.class})//启动整个springboot工程
    public class TestDemo {
        
        //测试方法运行前执行
        @Before
        public void beforeTest(){
            System.out.println("=======before====");
        }
    
        @Test
        public void demo(){
            System.out.println("========OK=====");
            TestCase.assertEquals(1, 1);
        }
    
        //测试方法运行后执行
        @After
        public void afterTest(){
            System.out.println("=======after====");
        }
    }   
    

    6.2 MockMvc测试,模拟请求

    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = StartApplication.class)
    @AutoConfigureMockMvc
    public class MockMvcTest {
    
        @Autowired
        private MockMvc mockMvc;
    
        @Test
        public void apiGETest() throws Exception {
            MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/get/version"))
                    .andExpect(MockMvcResultMatchers.status().isOk())
                    .andReturn();
    
            System.out.println(mvcResult.getResponse().getContentAsString());
        }
    
    } 
    

    7、使用servlet3.0注解配置Filter

    主要用于权限控制、用户登录等; Filter优先级

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

    SpringBoot启动默认加载的Filter
    characterEncodingFilter
    hiddenHttpMethodFilter
    httpPutFormContentFilter
    requestContextFilter

    7.1 使用servlet3.0注解配置自定义Filter

    • 使用Servlet3.0的注解进行配置
    • 启动类里面增加 @ServletComponentScan,进行扫描
    • 新建一个Filter类,implements Filter,并实现对应的接口
    • @WebFilter 标记一个类为filter,被spring进行扫描;urlPatterns:拦截规则,支持正则
    • 控制chain.doFilter的方法的调用,来实现是否通过放行;不放行,web应用resp.sendRedirect("/index.html");
    import javax.servlet.*;
    import javax.servlet.annotation.WebFilter;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * <p> 过滤器 </p>
     *
     *  拦截以api开头的请求
     * @author vchar fred
     * @version 1.0
     * @create_date 2019/7/17 21:09
     */
    @WebFilter(urlPatterns = "/api/*", filterName = "loginFilter")
    public class LoginFilter implements Filter {
    
        //初始化: 容器加载时调用
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("初始化 LoginFilter");
        }
    
        //请求被拦截的时候进行调用
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            System.out.println("执行 LoginFilter");
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            HttpServletResponse response = (HttpServletResponse) servletResponse;
    
            String token = request.getParameter("token");
            if("1234".equals(token)){
                filterChain.doFilter(servletRequest, servletResponse);
            }else{
                System.out.println("LoginFilter 拦截请求");
                response.sendRedirect("/no_auth");
            }
    
        }
    
        //容器被销毁时调用
        @Override
        public void destroy() {
            System.out.println("销毁 LoginFilter");
        }
    }
    

    8、 使用servlet3.0注解配置自定义原生的Servlet

    现在都是使用基本都是spring等框架,都是自动将参数处理后直接传入给我们的controller;

    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * <p> 自定义Servlet </p>
     *
     * @author vchar fred
     * @version 1.0
     * @create_date 2019/7/17 21:32
     */
    @WebServlet(name = "userServlet", urlPatterns = "/v1/*")
    public class UserServlet extends HttpServlet {
    
        @Override
        public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            System.out.println("执行userServlet Get");
            resp.getWriter().println("custom servlet");
            resp.getWriter().flush();
            resp.getWriter().close();
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            System.out.println("执行userServlet Post");
            this.doGet(req, resp);
        }
    }
    

    9、 使用servlet3.0注解配置自定义Listener

    执行顺序:

    Listener.requestInitialized-->>Controller--->>Listener.requestDestroyed
    

    代码实现:

    import javax.servlet.ServletRequestEvent;
    import javax.servlet.ServletRequestListener;
    import javax.servlet.annotation.WebListener;
    
    /**
     * <p> 自定义ServletRequestListener监听器 </p>
     *
     * @author vchar fred
     * @version 1.0
     * @create_date 2019/7/17 21:42
     */
    @WebListener
    public class RequestListener implements ServletRequestListener {
        
        @Override
        public void requestInitialized(ServletRequestEvent sre) {
            System.out.println("=====请求初始化 RequestListener====");
        }
    
        @Override
        public void requestDestroyed(ServletRequestEvent sre) {
            System.out.println("====请求销毁 RequestListener=====");
        }
    }
    

    10、SpringBoot拦截器Interceptor

    2.x以后注册Interceptor

    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    /**
     * <p> 2.x以后注册Interceptor </p>
     *
     * @author vchar fred
     * @version 1.0
     * @create_date 2019/7/17 21:59
     */
    @Configuration
    public class CustomWebMvcConfigurer implements WebMvcConfigurer {
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/v2/*");
            WebMvcConfigurer.super.addInterceptors(registry);
        }
    }
    

    2.x以前注册Interceptor

    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
    
    /**
     * <p> 2.x以前注册Interceptor  </p>
     *
     * @author vchar fred
     * @version 1.0
     * @create_date 2019/7/17 22:00
     */
    @Configuration
    public class CustomOldWebMvcConfigurer extends WebMvcConfigurerAdapter {
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/v2/*");
    
        }
    }    
    

    拦截器实现

    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * <p> 拦截器 </p>
     *
     * @author vchar fred
     * @version 1.0
     * @create_date 2019/7/17 22:09
     */
    public class LoginInterceptor implements HandlerInterceptor {
    
        //进入controller方法前
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("=====LoginInterceptor preHandle======");
            return HandlerInterceptor.super.preHandle(request, response, handler);
        }
    
        //调用完Controller之后,视图渲染之前,如果控制器Controller出现了异常,则不会执行此方法
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("=====LoginInterceptor 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("=====LoginInterceptor afterCompletion======");
            HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
        }
    } 
    

    Filter是基于函数回调 doFilter(),而Interceptor则是基于AOP思想
    Filter在只在Servlet前后起作用,而Interceptor够深入到方法前后、异常抛出前后等
    依赖于Servlet容器即web应用中,而Interceptor不依赖于Servlet容器所以可以运行在多种环境。
    在接口调用的生命周期里,Interceptor可以被多次调用,而Filter只能在容器初始化时调用一次。
    Filter和Interceptor的执行顺序
    过滤前->拦截前->action执行->拦截后->过滤后

    11、使用freemarker做页面渲染引擎

    添加依赖

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

    配置

    spring:
      freemarker:
        # 编码
        charset: UTF-8
        # 本地开发关闭缓存
        cache: false
        content-type: text/html
        # 文件后缀
        suffix: .ftl
        # 文件目录,这个是默认的
        template-loader-path: classpath:/templates/
    

    12、使用thymeleaf做页面渲染引擎

    添加依赖

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

    配置

    spring:
      thymeleaf:
        # 本地开发关闭缓存
        cache: false
        encoding: UTF-8
        suffix: .html
        prefix: classpath:/templates/  
    

    13、mybatis配置

    添加maven依赖

    <!--mybatis-boot-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.0</version>
    </dependency>
    <!--MySQL的JDBC驱动包-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <!--第三方数据源druid-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.10</version>
    </dependency>
    

    或使用druid-spring-boot-starter可以简化druid配置

        <!--这个可以简化druid配置-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
    

    在启动类上加入 @MapperScan("包名")设置扫描dao的包;如:

    @MapperScan("top.vchar.demo.spring.mapper")
    # 有多个不同路径时
    @MapperScan(value = {"top.vchar.demo.spring.mapper", "com.xx.dao"})
    

    dao使用示例,不用再在xml文件中写sql了, 直接在方法上写:

    public interface DemoMapper {

    //keyProperty--Java对象属性,keyColumn--数据库字段
    @Insert("INSERT INTO user(name, age) VALUES(#{name}, #{age})")
    @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
    int insert(User user);  
    

    推荐使用#{}; 不建议使用${},因为存在sql注入风险

    开启事务:在service的实现方法上加入如下注解:

    @Transactional(propagation = Propagation.REQUIRED)//开启事务,设置事务级别(若不配置propagation则使用数据库默认的级别)
    public int addUser(User user) {
        //TODO something
    
    }    
    

    14、redis使用

    添加依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!--由于不再使用jedis,而使用lettuce,因此需要加入此依赖-->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
    

    配置:

    spring:
      redis:
        # 链接地址
        host: 127.0.0.1
        # 认证密码
        password: 123456
        # 数据库序号
        database: 0
        # 链接超时,单位毫秒
        timeout: 2000
        # 这个比jedis性能更好
        lettuce:
          pool:
            # 池中最大活跃数,-1表示不限制
            max-active: 10
            # 池中最小空闲链接
            min-idle: 1
            # 池中最大空闲链接
            max-idle: 8
            # 最大等待时间,单位毫秒,-1表示不限
            max-wait: 1000
    

    简单使用:

    @RestController
    public class IndexController {
         @Autowired
         private StringRedisTemplate redisTemplate;
     
         @GetMapping("/redis_test")
         public String redisTest() {
             String val = redisTemplate.opsForValue().get("test:demo");
             //key建议都使用一个前缀加上 : 这样在查看redis库时会方便许多,同时也便于管理
             redisTemplate.opsForValue().set("test:demo", "this is test");
             return val;
         }
    }
    

    关于jedis跟lettuce的区别:

    • Lettuce 和 Jedis 的定位都是Redis的client,所以他们当然可以直接连接redis server。
    • Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接
    • Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,应为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例

    15、springboot 使用定时任务和异步执行

    在启动类上加上开启定时的注解

    @EnableScheduling 
    

    使用示例:

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.Future;
    
    /**
     * <p> 定时任务 </p>
     *
     * @author vchar fred
     * @version 1.0
     * @create_date 2019/7/16 23:31
     */
    @Component
    public class TestScheduling {
    
        @Autowired
        private DmoTask dmoTask;
    
        //每隔2s执行一次
        @Scheduled(fixedDelay = 2000)//支持core表达式
        public void demo() throws ExecutionException, InterruptedException {
            //TODO 
            System.out.println("开始执行定时任务");
        }
    }
    

    @Scheduled配置说明:

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

    在启动类上加上开启异步执行的的注解;当某几个方法执行顺序无关联影响,又比较耗时时,则可以使用异步执行,这样也就可以提高系统速度

    @EnableAsync
    

    示例:异步执行该方法,并等待结果返回。

    //有这个注解的即表示异步执行方法(放在类上则类中所有方法都是异步方法),若不配做线程池,则springboot会使用默认的 SimpleThreadPoolTaskExecutor (每调用一次异步方法,都会创建一个线程去执行)
    @Async 
    public Future<List<Integer>> pullInfo(int pageIndex, int total){
        List<Integer> list = new ArrayList<>();
        do{
            List<Integer> ids = demoService.demo(pageIndex);
            if(ids.size()>0){
                list.addAll(ids);
            }
            pageIndex++;
        }while (pageIndex<=total);
        return AsyncResult.forValue(list);
    }
    

    16、elasticsearch使用

    查看es数据

    查看索引信息:http://localhost:9200/_cat/indices?v
    查看某个索引库结构:http://localhost:9200/blog
    查看某个对象:http://localhost:9200/{indexName}/{type}/1  
    

    elasticsearch官网地址

    安装问题:

    1. elasticsearch不能以root用户运行;
    #添加一个组
    groupadd -g 1024 bigdata
    # 在组中添加一个用户
    useradd -g bigdata es
    # 进入elasticsearch安装目录修改创建用户的权限
    chown -R es:bigdata .
    
    1. 提示vm.max_map_count太小,需要修改系统配置
    #打开系统配置文件
    vi  /etc/sysctl.conf
    

    在里面添加如下配置:

    vm.max_map_count=262144
    
    1. 开启外网访问:

    将elasticsearch安装目录下的config目录下面elasticsearch.yml修改为 network.host: 0.0.0.0

    添加maven依赖

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

    注意:请注意当前引入的spring-boot-starter-data-elasticsearch支持的elasticsearch的版本,若版本不一样可能会出现一些问题,不要出现大版本的不同。

    配置:

    spring:
      data:
        elasticsearch:
          cluster-name: elasticsearch
          cluster-nodes: 127.0.0.1:9300
          repositories:
            enabled: true
    

    使用:

    定义文档映射:

    import org.springframework.data.elasticsearch.annotations.Document;
    
    import java.io.Serializable;
    
    /**
     * <p> 实体类(表映射):火车站点 </p>
     *
     * @author vchar fred
     * @version 1.0
     * @create_date 2019/7/22 23:41
     */
    //indexName==db_name, type=table
    @Document(indexName = "train", type = "station")
    public class TrainStation implements Serializable {
    
        private String id;
        private String stationName;
        private int hot;
        private int priority;
        private String match;
        private String stationCode;
        //TODO GET SET method...
        
    }
    

    定义文档访问接口

    import org.springframework.data.elasticsearch.annotations.Document;
    import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
    import org.springframework.stereotype.Component;
    import top.vchar.demo.spring.pojo.TrainStation;
    
    /**
     * <p>  文档访问接口(相当于dao数据库访问) </p>
     *
     * @author vchar fred
     * @version 1.0
     * @create_date 2019/7/22 23:47
     */
    @Component
    @Document(indexName = "train", type = "station", shards = 1, replicas = 0)
    public interface TrainStationRepository extends ElasticsearchRepository<TrainStation, String> {
    }    
    

    调用:

    @RestController
    public class IndexController {
    
      @Autowired
      private TrainStationRepository trainStationRepository;
        
        //保存
      @PostMapping("/save_train")
      public String esSaveDemo(String stationStr){
          if(null!=stationStr){
              TrainStation trainStation = JSONObject.parseObject(stationStr, TrainStation.class);
              if(trainStation.getId()!=null){
                  trainStationRepository.save(trainStation);
                  System.out.println("保存成功");
                  return "200";
              }
          }
          System.out.println("保持失败");//INSTANCE
          return "401";
      }
    
       //搜索
      @GetMapping("/search_train")
      public String esSearchDemo(String key){
          QueryBuilder queryBuilder = QueryBuilders.matchQuery("stationName", key);
          List<TrainStation> list = new ArrayList<>();
          Iterable<TrainStation> search = trainStationRepository.search(queryBuilder);
          search.forEach(list::add);
          return JSONObject.toJSONString(list);
      }   
    

    17、activemq使用

    添加maven依赖:

    <!-- 整合消息队列ActiveMQ -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-activemq</artifactId>
        </dependency>
    

    使用的springboot2.1.x以上的使用这个连接池

    <!-- 如果配置线程池则加入: -->
        <dependency>
            <groupId>org.messaginghub</groupId>
            <artifactId>pooled-jms</artifactId>
        </dependency>
    

    使用的springboot2.0.x以下的使用这个连接池

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

    消息队列使用--点对点模式

    点对点模式:可以有多个接收方和发送方,消息会根据发送时间进行排队,先发送的先处理;每条消息只会有一个消息接收者收到。同时当没有消息接收者监听消息,那么这些消息会存储在队列里面,
    直到有消息接收者启动

    示例:

    在springboot启动类上添加 `````@EnableJms```注解开启jms支持

    //注入队列,交给spring管理;也可以不做这个操作,只是后面发消息时每次都需要创建
    @Bean
    public Queue queue(){
        //指定队列名称
        return new ActiveMQQueue("common.queue");
    }
    

    消息生产者:

    @Service
    public class ProducerServiceImpl implements ProducerService {
        
        @Autowired
        private JmsMessagingTemplate jmsMessagingTemplate;
        
        //注入上面注入的bean
        @Autowired
        private Queue queue;
        
        /**
         * 发送消息
         * @param destination 指定发送到的队列,手动创建
         * @param message 待发送的消息
         */
        @Override
        public void sendMessage(Destination destination, String message) {
            jmsMessagingTemplate.convertAndSend(destination, message);
        }
    
        /**
         * 发送消息, 使用注入的队列发消息
         * @param message 待发送的消息
         */
        @Override
        public void sendMessage(String message) {
            jmsMessagingTemplate.convertAndSend(this.queue, message);
        }
    }
    

    调用:

    @RestController
    @RequestMapping("/api/v1")
    public class OrderController {
    
        @Autowired
        private ProducerService producerService;
    
        @GetMapping("/order")
        public String order(String msg){
            //生成队列地址;这里手动创建
            Destination destination = new ActiveMQQueue("order.queue");
            producerService.sendMessage(destination, msg);
            return "ok";
        }
    
        @GetMapping("/common")
        public String common(String msg){
            System.out.println("common: "+msg);
            producerService.sendMessage(msg);
            return "ok";
        }
    }    
    

    消息接收者

    @Component
    public class OrderConsumer {
    
        @JmsListener(destination = "order.queue")
        public void orderConsumer(String text){
            System.out.println("order.queue接收到消息:"+text);
        }
    
    }
    

    消息队列使用--发布订阅模式

    发布订阅模式:可以有多个接收方和发送方,每条消息会被所有的接收方收到,若没有接收方那么这条消息也会被认为是发送成功的,后面将不会再收到该消息。

    修改配置文件,开启发布订阅模式

    jms:
        #开启发布订阅模式
        pub-sub-domain: true
    

    消息发布者:

    @Autowired
    private Topic topic;
    @Override
    public void publish(String msg){
        jmsMessagingTemplate.convertAndSend(this.topic, msg);
    }
    

    消息订阅者:

    @Component
    public class VideoTopicSub {
        @JmsListener(destination = "video.topic")
        public void video(String text){
            System.out.println("video.topic接收到消息:"+text);
        }
    }
    

    注意:springboot只能同时支持点对点模式或发布订阅模式中的一个,若要都支持需要指定链接工厂。

    自己手动注入Topic工厂

    @Bean
    public JmsListenerContainerFactory<?> jmsListenerContainerTopic(ConnectionFactory activeMQConnectionFactory) {//idea可能会报找不到activeMQConnectionFactory的bean错误提升。不用管这是可以正常使用的
        DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
        bean.setPubSubDomain(true);//开启订阅发布模式
        bean.setConnectionFactory(activeMQConnectionFactory);
        return bean;
    }
    

    同时需要关闭配置文件中

        jms:
            #关闭发布订阅模式,默认就是关闭状态
            pub-sub-domain: false    
    

    消息订阅者需要设置监听的链接工厂,使其不和点对点的消息队列冲突

    @Component
    public class VideoTopicSub {
        @JmsListener(destination = "video.topic", containerFactory="jmsListenerContainerTopic")
        public void video(String text){
            System.out.println("video.topic接收到消息:"+text);
        }
    }            
    

    17、RocketMQ使用

    RocketMQ是一款高性能、高吞吐量的分布式消息中间件的阿里开源中间件;
    特点:

    1)在高压下1毫秒内响应延迟超过99.6%。
    2)适合金融类业务,高可用性跟踪和审计功能。
    3)支持发布订阅模型,和点对点
    4)支持拉pull和推push两种消息模式
    5)单一队列百万消息
    6)支持单master节点,多master节点,多master多slave节点
    ...
    

    概念:

    Producer:消息生产者
    Producer Group:消息生产者组,发送同类消息的一个消息生产组
    Consumer:消费者
    Consumer Group:消费同个消息的多个实例
    Tag:标签,子主题(二级分类),用于区分同一个主题下的不同业务的消息
    Topic:主题
    Message:消息
    Broker:MQ程序,接收生产的消息,提供给消费者消费的程序
    Name Server:给生产和消费者提供路由信息,提供轻量级的服务发现和路由
    

    安装RocketMQ

    官网提供了源码(带source文件名的)安装和二进制安装,源码安装需要自己去编码;这里直接下载二进制安装包http://rocketmq.apache.org/release_notes/release-notes-4.4.0/

    1. 将下载的压缩解压即可;
    2. 进入解压包中中bin目录下,修改runserver.sh和runbroker.sh这个2文件;
    #将Java虚拟机参数设置小点,避免启动时报内存不足的错误;参考如下:
    JAVA_OPT=”${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn125m....
    
    1. 启动服务,执行如下命令:
    nohup sh mqnamesrv & 
    

    启动后按Ctrl+c退出后,输入如下命令查看是否启动成功

    tail -f nohup.out
    

    结尾:The Name Server boot success. serializeType=JSON 表示启动成功

    1. 启动broker; 这里由于我的不是本地服务器而是阿里云的服务,因此需要开启外网访问。
      修改conf下的配置文件broker.conf
    vi conf/broker.conf
    

    在里面添加如下配置:

    namesrvAddr=外网IP:9876
    brokerIP1=外网IP
    

    保存后执行启动命令:

    nohup sh bin/mqbroker -c conf/broker.conf &
    

    springboot使用

    添加maven依赖

    <dependency>
        <groupId>org.apache.rocketmq</groupId>
        <artifactId>rocketmq-client</artifactId>
        <version>4.5.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.rocketmq</groupId>
        <artifactId>rocketmq-common</artifactId>
        <version>4.5.1</version>
    </dependency>
    

    添加配置

    server:
      # 设置端口号
      port: 8080
      servlet:
        # 配置部署的路径
        context-path: /
    apache:
      rocketmq:
        consumer:
        #消费者
          push-consumer: orderConsumer
        producer:
        # 生产者
          producer-group: orderProducer
        # 服务地址  
        namesrv-addr: 127.0.0.1:9876   
    

    生产者:

    /**
     * <p> 消息生产者 </p>
     *
     * @author vchar fred
     * @version 1.0
     * @create_date 2019/7/29 22:43
     */
    @Component
    public class MsgProducer {
    
    
        /**
         * 生产者的组名
         */
        @Value(value = "${apache.rocketmq.producer.producer-group}")
        private String producerGroup;
    
        /**
         * NameServer 地址
         */
        @Value(value = "${apache.rocketmq.namesrv-addr}")
        private String namesrvAddr;
    
        private DefaultMQProducer producer ;
    
    
        public DefaultMQProducer getProducer(){
            return this.producer;
        }
    
        @PostConstruct
        public void defaultMQProducer() {
            //生产者的组名
            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();  一般在应用上下文,关闭的时候进行关闭,用上下文监听器
    
        }
    
    }
    

    消费者:

    /**
     * <p> 消息消费者 </p>
     *
     * @author vchar fred
     * @version 1.0
     * @create_date 2019/7/29 22:43
     */
    @Component
    public class MsgConsumer {
    
        /**
         * 消息消费者
         */
        @Value("${apache.rocketmq.consumer.push-consumer}")
        private String pushConsumer;
    
        /**
         * NameServer 地址
         */
        @Value("${apache.rocketmq.namesrv-addr}")
        private String namesrvAddr;
    
        @PostConstruct
        public void defaultMQPushConsumer(){
            DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(pushConsumer);
            consumer.setNamesrvAddr(namesrvAddr);
            try {
                //设置consumer所订阅的Topic和Tag, *代表所有的Tag
                consumer.subscribe("testTopic", "*");
    
                // CONSUME_FROM_LAST_OFFSET 默认策略。从该队列最尾开始消费,跳过历史消息
                // CONSUME_FROM_FIRST_OFFSET, 从队列最开始开始消费,即历史消息(还存在broker的)全部消费一遍
                // CONSUME_FROM_TIMESTAMP;//根据时间消费
                consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_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();
            }
        }
    
    }
    

    测试发送消息:

    @RestController
    public class OrderController {
    
        @Autowired
        private MsgProducer msgProducer;
    
        @GetMapping("/order")
        public String order(String msg, String tag) throws InterruptedException, RemotingException, MQClientException, MQBrokerException {
            Message message = new Message("testTopic", tag, msg.getBytes(StandardCharsets.UTF_8));
            SendResult result = msgProducer.getProducer().send(message);
            System.out.println("发送响应:MsgId: "+result.getMsgId()+", 发送状态:"+result.getSendStatus());
            return result.toString();
        }    
    }    
    

    18、多环境配置

    在resource目录下的config目录;分别创建各个环境的配置文件

    application.yml # 主配置文件,里面指定环境
    

    方式如下:

    spring:
      profiles:
        active: test
    
    application-dev.yml # 测试环境
    application-local.yml # 本地环境
    ....
    

    19、webFlux响应式编程

    reactive-streams学习资料
    web-flux spring官方介绍

    响应式编程特点:

    1. 是Spring Framework 5.0中引入的新的反应式Web框架
    2. 应用程序不严格依赖于Servlet API,因此它们不能作为war文件部署,也不能使用src/main/webapp目录
    3. 完全异步和非阻塞
    4. 启动方式默认是Netty

    需要的maven依赖:(加入依赖,如果同时存在spring-boot-starter-web,则会优先用spring-boot-starter-web)

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

    使用:

    import org.springframework.http.MediaType;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    import top.vchar.demo.spring.pojo.Member;
    
    import java.time.Duration;
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * <p> 测试 </p>
     *
     * @author vchar fred
     * @version 1.0
     * @create_date 2019/8/3 16:04
     */
    @RestController
    public class IndexController {
    
        @GetMapping("/demo1")
        public String demo1(){
            return "demo-1";
        }
    
        //Mono用于返回0或1个元素
        @GetMapping("/demo2")
        public Mono<String> demo2(){
            return Mono.just("demo2");
        }
    
    
        private static Map<String, Member> map = new HashMap<>();
        static {
            for(int i=0; i<10; i++){
                map.put(""+i, new Member(i, "demo-"+i));
            }
        }
    
        //Flux返回0或N个元素
        @GetMapping("/demo3")
        public Flux<Member> demo3(){
          Collection<Member> members = map.values();
          return Flux.fromIterable(members);
        }
    
        //Mono用于返回0或1个元素
        @GetMapping("/demo4")
        public Mono<Member> demo4(final String id){
            return Mono.justOrEmpty(map.get(id));
        }
    
        /**
         * 分批次返回
         *
         * 这里每隔2s返回一个对象;webflux是字符串,需要做特殊设置
         */
        @GetMapping(value = "/demo5", produces = MediaType.APPLICATION_STREAM_JSON_VALUE)
        public Flux<Member> demo5(){
            Collection<Member> members = map.values();
            //每个延迟2秒返回
            return Flux.fromIterable(members).delayElements(Duration.ofSeconds(2));
        }
    }
    

    Mono 是响应流 Publisher 具有基础 rx 操作符,可以成功发布元素或者错误

    Flux 是响应流 Publisher 具有基础 rx 操作符,可以成功发布 0 到 N 个元素或者错误。Flux 其实是 Mono 的一个补充

    响应式编程使用的数据库需要保证访问速度的快速,即通常使用redis等数据库,否则和普通的方式区别不会太大。

    import org.junit.Test;
    import org.springframework.http.MediaType;
    import org.springframework.web.reactive.function.client.WebClient;
    import reactor.core.publisher.Mono;

    /**
     * <p> webflux客户端Webclient测试 </p>
     *
     * @author vchar fred
     * @version 1.0
     * @create_date 2019/8/4 9:48
     */
    public class WebClientTest {
    
        @Test
        public void testBase(){
            Mono<String> result = WebClient.create().get()
                    .uri("http://127.0.0.1:8080//demo4?id={id}", 2)
                    .accept(MediaType.APPLICATION_JSON)
                    .retrieve().bodyToMono(String.class);
            System.out.println(result.block());//阻塞等待结果返回
        }
    
    }
    

    20、springboot监控actuator

    加入如下依赖:

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

    访问监控地址:

    http://127.0.0.1:8080/actuator
    http://127.0.0.1:8080/actuator/health
    http://127.0.0.1:8080/actuator/info
    http://127.0.0.1:8080/actuator/metrics  查看应用基本指标列表
    http://127.0.0.1:8080/actuator/metrics/{name}       通过上述列表,查看具体 查看具体指标
    

    出于安全考虑,除/health和/info之外的所有执行器默认都是禁用的;若需要打开如做如下配置

    management:
      endpoints:
        web:
          exposure:
            # 开启全部
            include: '*'
            # 开启某个
            #include: metrics
            # 关闭某个
            #exclude: metrics
    

    三、SpringCloud 学习笔记

    微服务主要包括:

    • 网关:路由转发、请求过滤
    • 服务发现和注册:服务调用者和被调用方的信息维护
    • 配置中心:管理配置,动态更新配置
    • 链路追踪:分析调用链路耗时
    • 负载均衡:分发负载
    • 熔断:保护自己和被调用方

    SpringCloud微服务组件

    通信方式------http restful
    网关----------zuul/spring-cloud-gateway
    注册中心------eruka/consul
    配置中心------config
    断路器--------hystrix
    分布式追踪系统--sleuth+zipkin
    

    1、注册中心和服务调用

    注册中心:服务管理,核心是有个服务注册表,心跳机制动态维护。

    分布式CAP原理:一致性(C:数据同步)、可用性(A:正常响应时间)、分区容错性(P:机器数)三者不可同时获取

    zookeeper: CP设计,保证一致性,集群搭建时,某个节点失效,会从剩下的节点中重新选择一个。或半数以上节点不可用则无法提供服务,因此可用性无法满足

    Eureka: AP原则,无主从节点,一个节点挂了,自动切换其他节点可以使用,去中心化。

    分布式系统中P必须保证,即多节点部署;只能在CA中二选一。

    eureka注册中心

    添加maven依赖(建议使用idea进行自定构建项目)

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>top.vchar.demo.spring</groupId>
    <artifactId>springcloud-eureka-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>eureka-server</name>
    <description>eureka注册中心</description>
    
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    

    配置:

    server:
      port: 8761
    eureka:
      instance:
        hostname: localhost
      client:
        # 关闭客服端配置
        register-with-eureka: false
        fetch-registry: false
        service-url:
          default-zone: http://${eureka.instance.hostname}:${server.port}/eureka/
    spring:
      application:
        name: eureka-register-center
    

    在启动类上添加如下注解:

    @EnableEurekaServer            
    

    服务端注册服务到eureka注册中心

    添加maven依赖:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>top.vchar.demo.spring</groupId>
    <artifactId>product-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>product-server</name>
    <description>商品服务</description>
    
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    

    添加配置信息:

    server:
      port: 8771
    # 配置注册中心地址
    eureka:
      client:
        service-url:
          default-zone: http://localhost:8761/eureka/
    # 配置应用名称
    spring:
      application:
        name: product-service    
    

    service接口和controller写法和普通的无区别,这里不做举例了

    使用ribbon调用服务

    ribbon和httpClient等类似

    添加maven:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>top.vchar.dem.spring</groupId>
    <artifactId>springcloud-ribbon</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springcloud-ribbon</name>
    <description>服务调用</description>
    
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    

    修改配置:

    server:
      port: 8781
    # 配置注册中心地址
    eureka:
      client:
        service-url:
          default-zone: http://localhost:8761/eureka/
    # 配置应用名称
    spring:
      application:
        name: order-service
    
    # 自定义策略配置,默认是轮询的,建议使用默认的    
    product-service:
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    

    使用:

    注入一个配置的bean

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
    

    在service中调用

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cloud.client.ServiceInstance;
    import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
    import org.springframework.stereotype.Service;
    import org.springframework.web.client.RestTemplate;
    import top.vchar.dem.spring.pojo.ProductOrder;
    import top.vchar.dem.spring.service.OrderService;
    
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.UUID;
    @Service
    public class OrderServiceImpl implements OrderService {
    
        private static Map<Integer, ProductOrder> orderMap = new HashMap<>();
    
        @Autowired
        private RestTemplate restTemplate;
    
        @Autowired
        private LoadBalancerClient loadBalancerClient;
    
        @Override
        public ProductOrder save1(int userId, int productId) {
            System.out.println("方式一");
            Map<String, Object> product = restTemplate.getForObject("http://product-service/api/v1/product/find?id="+productId, Map.class);
            return save(userId, productId, product);
        }
    
        @Override
        public ProductOrder save2(int userId, int productId) {
            System.out.println("方式二");
            ServiceInstance serviceInstance = loadBalancerClient.choose("product-service");
            String url = String.format("http://%s:%s/api/v1/product/find?id="+productId, serviceInstance.getHost(), serviceInstance.getPort());
            RestTemplate restTemplate1 = new RestTemplate();
            Map<String, Object> product = restTemplate1.getForObject(url, Map.class);
            return save(userId, productId, product);
        }
    
        private ProductOrder save(int userId, int productId, Map<String, Object> product){
            ProductOrder productOrder = new ProductOrder();
            productOrder.setUserId(userId);
            productOrder.setCreateTime(new Date());
            productOrder.setTradeNo(UUID.randomUUID().toString());
            productOrder.setProductId(productId);
            productOrder.setId(orderMap.size());
            productOrder.setProductName(product.get("name").toString());
            productOrder.setPrice(Integer.parseInt(product.get("price").toString()));
            orderMap.put(productOrder.getId(), productOrder);
            return productOrder;
        }
    }
    

    说明

    @LoadBalanced会从注册中心获取节点信息,然后从中选择一个节点给RestTemplate使用

    feign:伪RPC客户端

    feign已经集成了ribbon

    添加maven依赖:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    

    在启动类上添加如下注解

    @EnableFeignClients
    

    使用:

    定义一个接口供其他service调用

    @FeignClient(name = "product-service")//调用服务名称
    public interface ProductClient {
    
        //路径
        @GetMapping("/api/v1/product/find")
        String findProductById(@RequestParam(value = "id") int id);
    
    }
    

    service中注入使用

    @Service
    public class ProductServiceImpl implements ProductService {
    
        @Autowired
        private ProductClient productClient;
    
        @Override
        public ProductOrder findProductById(int id) {
            String result = productClient.findProductById(id);
            JsonNode jsonNode = JsonUtil.str2JsonNode(result);
            ProductOrder productOrder = new ProductOrder();
            productOrder.setId(jsonNode.findValue("id").asInt());
            productOrder.setProductName(jsonNode.findValue("name").asText());
            productOrder.setPrice(jsonNode.findValue("price").asInt());
            return productOrder;
        }
    } 
    

    注意:

    1. 路径必须和服务的一致;
    2. 使用RequestBody时,必须使用postMapping
    3. 多个参数时,通过@RequestParam来指定参数,名称要和服务的一样

    服务降级熔断

    系统负载过高,突发流量或网络等各种异常情况,常用解决方案

    1. 熔断:为了防止整个系统故障,停止出现问题的服务的访问
    2. 降级:抛弃一些非核心的接口和数据
    3. 熔断和降级互相交集:
      • 相同点:从可用性和可靠信息出发,为防止系统崩溃;最终让用户体验到的是某些功能暂时不可用
      • 不同点:服务熔断一般是下游服务故障导致,而服务降级一般是从整个系统负荷考虑,有调用方控制

    Hystrix (豪猪)

    添加maven依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    

    在启动类上添加注解

    @EnableCircuitBreaker   
    

    使用示例

    在方法上加@HystrixCommand(fallbackMethod = "xxx")注解

    @RestController
    @RequestMapping("/api/v3/order")
    public class IndexController {
    
        @Autowired
        private OrderService orderService;
    
        @RequestMapping("/update")
        @HystrixCommand(fallbackMethod = "updateOrderFail")
        public String update(@RequestParam("id") int id, @RequestParam("name")String name){
            return orderService.update(id, name);
        }
    
        //这里一定要和HystrixCommand注解中的方法一致,且参数也必须一致;当服务异常时会调用此方法
        private String updateOrderFail(int id, String name){
            System.out.println("服务异常: "+id+" "+name);
            return "{'code':'-1', 'msg':'当前访问人数过多,请稍后再试'}";
        }
    }
    
    对于Feign
    @FeignClient(name = "product-service", fallback = ProductClientFallback.class)
    public interface ProductClient {
    
        @GetMapping("/api/v1/product/find")
        String findProductById(@RequestParam(value = "id") int id);
    
    }
    

    fallback中配置的类必须实现这个接口,并且注入微spring的bean

    @Component
    public class ProductClientFallback implements ProductClient {
    
        @Override
        public String findProductById(int id) {
            System.out.println("Feign 调用服务异常");
            return null;
        }
    }
    

    同时在配置文件中开启 Hystrix

    # 开启  feign的  hystrix支持
    feign:
      hystrix:
        enabled: true    
    

    监控界面

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    

    添加注解:

    @EnableHystrixDashboard
    

    添加配置:

    management:
      endpoints:
        web:
          exposure:
            include: "*"    
    

    地址:

    http://localhost:8783/hystrix    
    

    在输入框中输入:

    http://localhost:8783/actuator/hystrix.stream    
    

    点击 Monitor Stream进入监控界面

    相关文章

      网友评论

        本文标题:Spring 全家桶学习笔记

        本文链接:https://www.haomeiwen.com/subject/uhozhctx.html