zoukankan      html  css  js  c++  java
  • SpringBoot学习笔记

    SpringBoot

    什么是Spring:Spring是为了解决企业级应用开发的复杂性而创建的,简化开发。

    如何简化Java的开发的

    • 基于POJO的轻量级和最小侵入性编程
    • 通过IOC,依赖注入和面向接口实现松耦合
    • 基于切面和惯例进行声明式编程
    • 通过切面和模板减少样式代码

    SppringBoot主要优点:

    • 开箱即用,提供各种默认配置来简化项目配置
    • 内嵌式容器简化Web项目
    • 没有冗余代码和生成XML配置的要求
    • 让所有的Spring开发者更快的入门

    什么是微服务:微服务是一种架构风格,它要求我们在开发一个应用的时候,这个应用必须构建成一系列小服务的组合,可以通过http的方式进行互通,要说微服务架构,可以先看看我们以前的单体应用架构。

    单体应用架构:我们将一个应用中的所有应用服务都封装在一个应用中,数据访问,web访问等放在一个war包中。

    • 这样做的好处是,易于开发和测试,也十分方便部署,当要拓展时,只要将war复制多份,然后放到多个服务器上,在做个负载均衡就可以了。
    • 单体架构应用的缺点是,哪怕要修改一个非常小的地方,都要停掉整个服务,重新打包,再部署这个应用的war包,特别是对于一个大型应用,我们不可能把所有内容放在一个应用里面,如何维护,如何分工合作都是问题。

    微服务架构就是打破 all in one 的架构方式,把每个功能元素独立出来,把独立出来的功能元素动态组合,节省调用资源,每个功能元素的服务器都是一个可替换的,可独立升级的代码。

    第一个SpringBoot

    1. 可以从官网新建项目后下载,解压后使用 IDEA 打开
    2. IDEA中新建 spring initiali 项目,选中 spring web 直接创建

    导入 jar 包即可

    自动装配

    pom.xml

    • Spring Boot Dependencies 核心依赖在父工程中
    • 我们在写或者引入一些 SpringBoot 依赖的时候,不需要指定版本,就是因为父工程中已经写了。

    启动器

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

    比如:spring-boot-starter-web,它就会自动帮我们导入web项目所有的依赖。

    springboot会将所有的的功能场景,都变成一个个的启动器。

    需要什么功能,找到对应的启动器就可以了(官网找) 。

    主程序

    //标注这是一个springboot的应用
    @SpringBootApplication
    public class Springboot01HelloworldApplication {
    
        public static void main(String[] args) {
            //启动springboot应用
            SpringApplication.run(Springboot01HelloworldApplication.class, args);
        }
    
    }
    

    注解

    @SpringBootConfiguration   //springboot的配置
    	@Configuration   //spring配置类
    			@Component    //说明这也是一个spring的组件
    
    @EnableAutoConfiguration   //自动配置
    	@AutoConfigurationPackage   //自动配置包
    		@Import({Registrar.class})   //导入注册器
    	@Import({AutoConfigurationImportSelector.class})   //导入选择器
    			//获取所有的配置
                List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
    
    

    获取候选的配置

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }
    

    META-INF/spring.factories:自动配置的核心文件

    SpringBoot所有装配是在启动时扫描并加载,所有的自动配置类都在spring.factories,但只有导入了对应的start,有了启动器,对应的配置才会生效,然后自动装配。

    1. SpringBoot在启动的时候,从类路径下META-INF/spring.factories获取指定的值。

    2. 将这些自动配置的类导入容器,自动配置类就会自动生效,帮我们自动配置。

    3. 以前我们需要自动配置的东西,现在SpringBoot帮我们做了。

    4. 整合JavaEE,解决方案和自动配置的东西都在springframework.boot.autoconfigure:2.4.0包下。

    5. 它会把所有需要导入的组件以类名的方式返回,这些组件就会被添加到容器。

    6. 容器中也会存在非常多的 ***AutoConfigure 的文件,就是这个类给容器导入了这个场景所需要的所有组件。

    7. 有了自动装配,免去了我们手动编写配置注入功能组件的工作。

    SpringApplication这个类主要是:

    • 推断应用的类型是普通的项目还是web项目。
    • 查找并加载所有可用初始化器,设置的initializers属性中。
    • 找出所有的应用程序监听器,设置到listeners中。
    • 推断并设置main方法的定义类,找到运行的主类。

    自动装配的原理:

    1. SpringBoot启动会加载大量的自动配置类。

    2. 我们看我们需要的配置类有没有在SpringBoot写好的自动配置类中。

    3. 再看这个配置类中到底装配了哪些组件(如果我们需要的组件在里面,就不用手动装配了)。

    4. 给容器中自动配置类添加组件时,会从properties类中获取某些属性,我们只需要在配置文件中指定这些属性即可。

      ***AutoConfiguration:自动配置类,给容器中添加组件

      ***Properties:封装配置文件的相关属性

    yaml中配置debug:true,启动可以查看哪些配置类生效了。

    在springboot中,有非常多的***.Configuretion会帮助我们进行拓展配置,只要看见了这个,需要注意。

    @EnableWebMvc,这配置是导入了一个类,DelegatingWebMvcConfiguration,从容器中获取所有的WebMvcConfigurer。

    yaml讲解

    SpringBoot使用一个全局的配置文件,配置文件名称是固定的

    • application.properties
      • 语法结构:key=value
    • application.yaml
      • 语法结构:key:空格 value

    配置文件可以帮我们修改SpringBoot自动配置的默认值。

    YAML是"YAML Ain't a Markup Language"(YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言),但为了强调这种语言以数据做为中心,而不是以标记语言为重点,而用反向缩略语重命名。

    标记语言

    以前的配置文件,大多数使用xml来配置:比如一个简单的端口配置,对比一下yaml和xml

    yaml配置:

    server:
      port: 8088
    

    xml配置

    <server>
        <port>8088</port>
    </server> 
    

    使用注解@ConfigurationProperties将配置文件中的值映射到这个组件中,将类中的属性与配置文件一一对应。

    yaml也可以这样使用

    person:
      name: 周周${random.uuid}
      age:  ${random.int}
      happy: true
      birth: 2020/10/10
      maps: {k1: v1,k2: v2}
      list:
        - code
        - music
        - girl
      dog:
        name: ${person.zr:hello}_旺财
        age: 5
    

    也可以使用@PropertySource加载指定的properties文件【不建议使用,建议使用yaml】

    @ConfigurationProperties只用绑定一次,@Value需要每个字段添加。

    yaml可以松散绑定,即yaml中写first-name,实体类写firstName,-后的字母默认是大写的,这就是松散绑定。

    JSR303数据验证,就是我们可以在字段增加一层过滤器验证,可以保证数据的合法性。

    复杂类型对象,yaml可以封装对象,@Value就不可以。

    结论:

    • 配置yaml和properties都可以获取到值,强烈推荐yaml。
    • 如果我们在某个业务中,只获取配置文件中的某个值,可以使用一下@Value。
    • 如果在javaBean和配置文件进行映射,就直接使用@ConfigurationProperties。

    JSR303

    验证是否为邮箱格式

    @Component
    @ConfigurationProperties(prefix = "person")
    @Validated//数据校验
    public class Person {
    
        @Email
        private String name;
    }
    

    其它注解

    空检查
    @Null       验证对象是否为null
    @NotNull    验证对象是否不为null, 无法查检长度为0的字符串
    @NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
    @NotEmpty 检查约束元素是否为NULL或者是EMPTY.
    
    Booelan检查
    @AssertTrue     验证 Boolean 对象是否为 true
    @AssertFalse    验证 Boolean 对象是否为 false
    
    长度检查
    @Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
    @Length(min=, max=) Validates that the annotated string is between min and max included.
    
    日期检查
    @Past           验证 Date 和 Calendar 对象是否在当前时间之前
    @Future     验证 Date 和 Calendar 对象是否在当前时间之后
    @Pattern    验证 String 对象是否符合正则表达式的规则
    
    数值检查,建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null
    @Min            验证 Number 和 String 对象是否大等于指定的值
    @Max            验证 Number 和 String 对象是否小等于指定的值
    @DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
    @DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
    @Digits     验证 Number 和 String 的构成是否合法
    @Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
    
    @Range(min=, max=) 检查数字是否介于min和max之间.
    @Range(min=10000,max=50000,message="range.bean.wage")
    private BigDecimal wage;
    
    @Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
    @CreditCardNumber信用卡验证
    @Email  验证是否是邮件地址,如果为null,不进行验证,算通过验证。
    @ScriptAssert(lang= ,script=, alias=)
    @URL(protocol=,host=, port=,regexp=, flags=)
        
    @Pattern 正则表达式
    

    Web开发

    静态资源

    在springboot中,我们可以使用以下路径处理静态资源

    优先级:resorces>static(默认)>public

    首页

    WebMvcAutoConfiguration下部分源码

    private Optional<Resource> getWelcomePage() {
        String[] locations = WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations());
        return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
    }
    
    private Resource getIndexHtml(String location) {
        return this.resourceLoader.getResource(location + "index.html");
    }
    

    即在资源配置目录下放入index.html即可自动识别。

    Thymeleaf

    pom中引入Thymeleaf,就可以将.html放在templates下访问。

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

    部分源码

    @ConfigurationProperties(
        prefix = "spring.thymeleaf"
    )
    public class ThymeleafProperties {
        private static final Charset DEFAULT_ENCODING;
        public static final String DEFAULT_PREFIX = "classpath:/templates/";
        public static final String DEFAULT_SUFFIX = ".html";
        private boolean checkTemplate = true;
        private boolean checkTemplateLocation = true;
        private String prefix = "classpath:/templates/";
        private String suffix = ".html";
      ......
    }
    

    可以看出来前缀是classpath:/templates/,后缀.html,即html文件放在classpath:/templates/下

    测试

    package com.zr.controller;
    
    //在templates下的所有页面,只能通过controller来跳转
    //需要模板引擎的支持
    @Controller
    public class IndexController {
        @RequestMapping("/test")
        public String index(Model model){
            model.addAttribute("msg","hello test");
            return "test";
        }
    }
    

    test.html

    <body>
    <div th:text="${msg}"></div>
    </body>
    

    拓展SpringMvc

    package com.zr.config;
    
    //扩展springmvc
    @Configuration
    //@EnableWebMvc //这配置是导入了一个类,DelegatingWebMvcConfiguration,从容器中获取所有的WebMvcConfigurer。
    public class MyMvcConfig implements WebMvcConfigurer {
        //视图跳转
        @Override
        public void addViewControllers(ViewControllerRegistry registry) {
            registry.addViewController("/zr").setViewName("test");
            registry.addViewController("/").setViewName("index");
            registry.addViewController("/index.html").setViewName("index");
        }
    
        @Bean
         public ViewResolver myViewResolver(){
             return new MyViewResolver();
         }
        
          //自定义视图解析器
         public static class MyViewResolver implements ViewResolver{
        
             @Override
             public View resolveViewName(String s, Locale locale) throws Exception {
                 return null;
             }
         }
    }
    

    员工管理系统

    首页配置,使用了thymeleaf接管,所有的herf引用改为th:herf="@{}"(th标签支持xmlns:th="http://www.thymeleaf.org")

    页面国际化

    1. 需要配置i18n文件
    2. 如果需要在项目中进行按钮自由切换,我们需要自定义一个组件LocaleResolver
    3. 然后将自己写的组件配置到spring容器 @Bean
    4. {}

    整合JDBC使用

    对于数据访问层,无论使SQL(关系型数据库)还是NOSQL(非关系型数据库),SpringBoot底层都是采用 Spring Data 的方式进行统一处理。

    application.yml

    spring:
      datasource:
        username: root
        password: 123456
        #时区配置报错 增加时区配置serverTimezone=UTC
        url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&chacterEncoding=utf-8&serverTimezone=UTC
        driver-class-name: com.mysql.cj.jdbc.Driver
    

    mybatis数据库 user表

    测试

    @SpringBootTest
    class Springboot04DataApplicationTests {
    
        @Autowired
        DataSource dataSource;
        @Test
        void contextLoads() throws SQLException {
            //查看默认的数据源
            System.out.println(dataSource.getClass());
    
            //获得数据库连接
            Connection connection = dataSource.getConnection();
            System.out.println(connection);
    
            //***templete: SpringBoot已经配置好的bean 拿来即用
    
            connection.close();
        }
    }
    

    controller

    @RestController
    public class JdbcController {
        @Autowired
        JdbcTemplate jdbcTemplate;
    
        //查询数据库的所有信息
        //没有实体类,数据库的东西怎么取  Map
        @GetMapping("/userlist")
        public List<Map<String,Object>> userList(){
            String sql = "select * from user";
            List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
            return maps;
        }
    
        @GetMapping("/addUser")
        public String addUser(){
            String sql = "insert into user(id,name,pwd) values(7,'韩信','6666')";
            jdbcTemplate.update(sql);
            return "add OK!";
        }
        @GetMapping("/updateUser/{id}")
        public String updateUser(@PathVariable("id") int id){
            String sql = "update user set name=?,pwd=? where id="+id;
            //封装
            Object[] objects = new Object[2];
            objects[0] = "小周啊";
            objects[1] = "8888";
    
            jdbcTemplate.update(sql,objects);
            return "update OK!";
        }
        @GetMapping("/deleteUser/{id}")
        public String deleteUser(@PathVariable("id") int id){
            String sql = "delete from user where id=?";
            jdbcTemplate.update(sql,id);
            return "delele OK!";
        }
    }
    

    整合Druid数据源

    Druid是阿里巴巴开源平台上的一个数据库连接池实现,结合了C3P0,DBCP,PEOXOOL等DB池的优点,同时加入了日志监控。

    SpringBoot默认的是Hikari数据源。

    application.yml

    spring:
      datasource:
        username: root
        password: 123456
        #时区配置报错 增加时区配置serverTimezone=UTC
        url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&chacterEncoding=utf-8&serverTimezone=UTC
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
    
        #SpringBoot默认不注入这些属性的,需要自己绑定
        #durid配置
        initialSize: 5
        minIdel: 5
        maxActive: 20
        maxWait: 60000
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
        #配置监控拦截,stat 监控统计, wall 防御sql注入,log4j 日志
        #如果允许时报错,导入log4j依赖
        filters: stat,wall,log4j
        maxPoolPreparedStatementPerConnectionSize: 20
        useGlobalDataSourceStat: true
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    

    监控

    DruidConfig

    @Configuration
    public class DruidConfig {
        @ConfigurationProperties(prefix = "spring.datasource")
        @Bean
        public DataSource druidDataSource(){
            return new DruidDataSource();
        }
        //后台监控
        //springboot内置了Servlet,所有没有web.xml,替代方法: ServletRegistrationBean
        @Bean
        public ServletRegistrationBean statViewServlet(){
            // ServletRegistrationBean<StatusManagerServlet> bean = new ServletRegistrationBean<>(new StatusManagerServlet(), "/druid/*");
            ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
            //后台需要有人登录,账号密码配置
            HashMap<String, String> initParameters = new HashMap<>();
            //增加配置
            initParameters.put("loginUsername","admin"); //登录的key是固定的
            initParameters.put("loginPassword","123456");
    
            //允许谁可以访问
            initParameters.put("allow","");
    
            //禁止谁能访问   initParameters.put("zr","39.405.48.101");
    
            bean.setInitParameters(initParameters); //设置初始化参数
            return bean;
        }
        @Bean
        //filter
        public FilterRegistrationBean webStatFilter(){
            FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
    
            bean.setFilter(new WebStatFilter());
    
            //可以过滤哪些请求
    
            HashMap<String, String> initParameters = new HashMap<>();
            //这些不进行统计
            initParameters.put("exclusions","*.js,*.css,/druid/*");
            bean.setInitParameters(initParameters);
    
            return bean;
        }
    }
    

    启动后访问localhost:8080/druid ,账号密码为配置中设置的值,登陆后进入监控界面。

    整合MyBatis

    依赖mybatis-spring-boot-starter

    application.properties

    spring.datasource.username=root
    spring.datasource.password=123456
    spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&chacterEncoding=utf-8
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    
    #整合mybatis
    mybatis.type-aliases-package=com.zr.pojo
    mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
    

    User

    package com.zr.pojo;
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
        private Integer id;
        private String name;
        private String pwd;
    }
    

    UserMapper

    package com.zr.mapper;
    //这个注解表示了这是一个mybatis的mapper类
    @Mapper
    @Repository
    public interface UserMapper {
    
        List<User> queryUserList();
    
        User queryById(int id);
    
        int addUser(User user);
    
        int updateUser(User user);
    
        int deleteUser(int id);
    }
    

    resource/mapper/UserMapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.zr.mapper.UserMapper">
        <select id="queryUserList" resultType="User">
        select * from user;
      </select>
        <select id="queryById" resultType="User">
        select * from user where id=#{id};
      </select>
    
        <insert id="addUser" parameterType="User">
            insert into user(id,name,pwd) values (#{id},#{name},#{pwd});
        </insert>
    
        <update id="updateUser" parameterType="User">
            update user set name = #{name},pwd=#{pwd} where id=#{id};
        </update>
    
        <delete id="deleteUser" parameterType="int">
            delete from user where id=#{id};
        </delete>
    </mapper>
    

    UserController

    package com.zr.controller;
    @RestController
    public class UserController {
        @Autowired
        private UserMapper userMapper;
        @GetMapping("/queryUserList")
        public List<User> queryUserList(){
            List<User> userList = userMapper.queryUserList();
            return userList;
        }
    
        @GetMapping("/queryById/{id}")
        public User queryById(@PathVariable("id") int id){
            User user = userMapper.queryById(id);
            System.out.println(user);
            return user;
        }
    
        @GetMapping("/addUser")
        public String addUser(){
            userMapper.addUser(new User(7,"韩信","33333"));
            return "addUser OK";
        }
        @GetMapping("/updateUser")
        public String updateUser(){
            userMapper.updateUser(new User(7,"韩信","54321"));
            return "updateUser OK";
        }
    
        @GetMapping("/deleteUser")
        public String deleteUser(){
            userMapper.deleteUser(7);
            return "deleteUser OK";
        }
    }
    

    以上仅为测试mybatis,没有编写业务层。

    SpringSecurity

    SpringSecurity是针对spring项目的安全框架,也是 SpringBoot 底层安全模块默认的技术选型,它可以实现强大的web安全控制,对于安全控制,我们只需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理。

    记住以下几个类:

    • WebSecurityConfigurerAdapter:自定义Security策略
    • AuthenticationManagerBuilder:自定义认证策略
    • @EnableWebSecurity:开启WebSecurity模式

    SpringSecurity的两个主要目标是“认证” 和 “授权”(访问控制)

    “认证” Authentication

    “授权” Authorization

    这两个概念是统用的,而不只是SpringSecurity中存在。

    项目结构

    application.properties

    spring.thymeleaf.cache=false
    

    login.html

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
        <title>登录</title>
        <!--semantic-ui-->
        <link href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
    </head>
    <body>
    
    <!--主容器-->
    <div class="ui container">
    
        <div class="ui segment">
    
            <div style="text-align: center">
                <h1 class="header">登录</h1>
            </div>
    
            <div class="ui placeholder segment">
                <div class="ui column very relaxed stackable grid">
                    <div class="column">
                        <div class="ui form">
                            <form th:action="@{/login}" method="post">
                                <div class="field">
                                    <label>Username</label>
                                    <div class="ui left icon input">
                                        <input type="text" placeholder="Username" name="username">
                                        <i class="user icon"></i>
                                    </div>
                                </div>
                                <div class="field">
                                    <label>Password</label>
                                    <div class="ui left icon input">
                                        <input type="password" name="password">
                                        <i class="lock icon"></i>
                                    </div>
                                </div>
                                <div class="field">
                                    <input type="checkbox" name="remember"> 记住我
                                </div>
    
                                <input type="submit" class="ui blue submit button"/>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
    
            <div style="text-align: center">
                <div class="ui label">
                    </i>注册
                </div>
                <br><br>
                <small>blog.kuangstudy.com</small>
            </div>
            <div class="ui segment" style="text-align: center">
                <h3>Spring Security Study by 秦疆</h3>
            </div>
        </div>
    
    
    </div>
    
    <script th:src="@{/qinjiang/js/jquery-3.1.1.min.js}"></script>
    <script th:src="@{/qinjiang/js/semantic.min.js}"></script>
    
    </body>
    </html>
    

    index.html

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org"
          xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
        <title>首页</title>
        <!--semantic-ui-->
        <link href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
        <link th:href="@{/qinjiang/css/qinstyle.css}" rel="stylesheet">
    </head>
    <body>
    
    <!--主容器-->
    <div class="ui container">
    
        <div class="ui segment" id="index-header-nav" th:fragment="nav-menu">
            <div class="ui secondary menu">
                <a class="item"  th:href="@{/index}">首页</a>
    
                <!--登录注销-->
                <div class="right menu">
                    <!--如果未登录-->
                    <div sec:authorize="!isAuthenticated()">
                        <a class="item" th:href="@{/toLogin}">
                            <i class="address card icon"></i> 登录
                        </a>
                    </div>
    
                    <!--如果已登录 用户名,注销按钮-->
                    <!--注销-->
                    <div sec:authorize="isAuthenticated()">
                        <a class="item">
                            用户名:<span sec:authentication="name"></span>
                            <!--角色:<span sec:authentication="principal.getAuthorities()"></span>-->
                            <!--角色:<span sec:authentication="authorities"></span>-->
                        </a>
                    </div>
                    <div sec:authorize="isAuthenticated()">
                    <a class="item" th:href="@{/logout}">
                        <i class="sign-out icon"></i> 注销
                    </a>
                    </div>
                    <!--已登录
                    <a th:href="@{/usr/toUserCenter}">
                        <i class="address card icon"></i> admin
                    </a>
                    -->
                </div>
            </div>
        </div>
    
        <div class="ui segment" style="text-align: center">
            <h3>Spring Security Study by 秦疆</h3>
        </div>
    
        <div>
            <br>
            <div class="ui three column stackable grid">
                <!--菜单根据用户的角色动态显示sec:authorize="hasRole('vip1')-->
                <div class="column" sec:authorize="hasRole('vip1')">
                    <div class="ui raised segment">
                        <div class="ui">
                            <div class="content">
                                <h5 class="content">Level 1</h5>
                                <hr>
                                <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
                                <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
                                <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
                            </div>
                        </div>
                    </div>
                </div>
    
                <div class="column" sec:authorize="hasRole('vip2')">
                    <div class="ui raised segment">
                        <div class="ui">
                            <div class="content">
                                <h5 class="content">Level 2</h5>
                                <hr>
                                <div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
                                <div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
                                <div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
                            </div>
                        </div>
                    </div>
                </div>
    
                <div class="column" sec:authorize="hasRole('vip3')">
                    <div class="ui raised segment">
                        <div class="ui">
                            <div class="content">
                                <h5 class="content">Level 3</h5>
                                <hr>
                                <div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
                                <div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
                                <div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
                            </div>
                        </div>
                    </div>
                </div>
    
            </div>
        </div>
        
    </div>
    
    
    <script th:src="@{/qinjiang/js/jquery-3.1.1.min.js}"></script>
    <script th:src="@{/qinjiang/js/semantic.min.js}"></script>
    
    </body>
    </html>
    

    RouterController

    package com.zr.controller;
    
    @Controller
    public class RouterController {
        @RequestMapping({"/","/index"})
        public String index(){
            return "index";
        }
        @RequestMapping("/toLogin")
        public String toLogin(){
            return "views/login";
        }
    
        @RequestMapping("/level1/{id}")
        public String level1(@PathVariable("id") int id){
            return "views/level1/"+id;
        }
        @RequestMapping("/level2/{id}")
        public String level2(@PathVariable("id")int id){
            return "views/level2/"+id;
        }
        @RequestMapping("/level3/{id}")
        public String level3(@PathVariable("id")int id){
            return "views/level3/"+id;
        }
    }
    

    SecurityConfig

    package com.zr.config;
    
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        //授权
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //首页所有人可以访问,功能页只有对应有权限的人才能访问
            //请求授权的规则
            http.authorizeRequests()
                    .antMatchers("/").permitAll()
                    .antMatchers("/level1/**").hasRole("vip1")
                    .antMatchers("/level2/**").hasRole("vip2")
                    .antMatchers("/level3/**").hasRole("vip3");
    
            //没有权限默认会到登录页 开启登录页面
            //usernameParameter("username").passwordParameter("password")
            // 括号内为前端传入的用户名为username,底层源码默认为username,当前端为name,pwd时就需要更改括号内问name,pwd
            //loginProcessingUrl("/login");//点登录跳转的路径
            http.formLogin().loginPage("/toLogin").usernameParameter("username").passwordParameter("password").loginProcessingUrl("/login");
    
            http.csrf().disable();//防止跨站脚本请求攻击关闭  登出失败可能的原因
            //开启了注销功能
            http.logout().logoutSuccessUrl("/index");
    
            //开启记住我功能,cookie 默认保存两周,自定义接收前端的参数
            //开启记住我功能 默认保存两周
            http.rememberMe().rememberMeParameter("remember");
        }
    
        //认证
        //密码编码:PasswordEncoder
        //在Spring Security 5.0+中,新增了很多的加密方式
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            //正常应该从数据库中读  这里是从内存中读取
            auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                    .withUser("zr").password(new BCryptPasswordEncoder().encode("12345")).roles("vip2","vip3")
                    .and()
                    .withUser("zz").password(new BCryptPasswordEncoder().encode("12345")).roles("vip1","vip2")
                    .and()
                    .withUser("root").password(new BCryptPasswordEncoder().encode("12345")).roles("vip1","vip2","vip3");
        }
    }
    

    Shiro

    Apache Shiro 是一个Java的安全(权限)框架。

    Shiro 可以非常容易地开发出足够好的应用,其不仅可以用在JavaSE环境,还可以用在JavaEE环境。

    Shiro可以完成认证,授权,加密,会话管理,Web集成,缓存等。

    重要对象:

    Subject:应用代码直接交互的对象是subject,也就是说shiro的对外核心API就是subject,subject代表了当前的用户,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是subject,如网络爬虫,机器人等,与subject的所有交互都会委托给SecurityManager,subject是一个门面,SubjectManager才是具体的执行者。

    SecurityManager:安全管理器,即所有与安全有关的操作都会与SecurityManager交互,并且它管理着所有的subject,可以看出它是shiro的核心,它负责与shiro的其它组件交互,相当于SprinfMvc的DispatcherServlet的角色。

    Realm:shiro从realm获得安全数据(如用户,角色,权限),就是说SecurityManager要验证用户身份,那么它需要从realm获得相应的用户进行比较,来确定用户的身份是否合法,也需要从realm得到用户相应的角色,权限来验证用户的操作是否能够进行,可以把realm当成DataSource。

    整合shiro练习

    目录结构

    数据库为整合JDBC中的数据库增加一个perms字段

    依赖pom.xml

    <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-starter-thymeleaf</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.21</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
        </dependency>
    <!--    shrio,thymeleaf整合-->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>
    </dependencies>
    

    application.yml

    spring:
      datasource:
        username: root
        password: 123456
        #时区配置报错 增加时区配置serverTimezone=UTC
        url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&chacterEncoding=utf-8&serverTimezone=UTC
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
    
        #SpringBoot默认不注入这些属性的,需要自己绑定
        #durid配置
        initialSize: 5
        minIdel: 5
        maxActive: 20
        maxWait: 60000
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
        #配置监控拦截,stat 监控统计, wall 防御sql注入,log4j 日志
        #如果允许时报错,导入log4j依赖
        filters: stat,wall,log4j
        maxPoolPreparedStatementPerConnectionSize: 20
        useGlobalDataSourceStat: true
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    

    application.properties

    mybatis.type-aliases-package=com.zr.pojo
    mybatis.mapper-locations=classpath:mapper/*.xml
    

    index.html

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org"
          xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1>首页</h1>
       <div th:if="${session.loginUser==null}">
           <a th:href="@{/toLogin}">登录</a>
       </div>
    
    <p th:text="${msg}"></p>
    <hr>
    <div shiro:hasPermission="user:add">
        <a th:href="@{/user/add}">add</a>
    </div>
    
    <div shiro:hasPermission="user:update">
        <a th:href="@{/user/update}">update</a>
    </div>
    </body>
    </html>
    

    login.html

    <body>
    <h1>登录</h1>
    <p th:text="${msg}" style="color: red"></p>
    <form th:action="@{/login}">
        <p>用户名:<input name="username" type="text"></p>
        <p> 密码:<input name="password" type="password"></p>
       <p><input type="submit"></p>
    </form>
    </body>
    

    add.html

    <body>
    <h1>add</h1>
    </body>
    

    update.html

    <body>
    <h1>update</h1>
    </body>
    

    User实体类

    package com.zr.pojo;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
        private int id;
        private String name;
        private String pwd;
        private String perms;
    }
    

    UserMapper

    package com.zr.mapper;
    
    @Repository
    @Mapper
    public interface UserMapper {
        public User queryUserByName(String name);
    }
    

    UserMapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.zr.mapper.UserMapper">
    
        <select id="queryUserByName" parameterType="String" resultType="User">
        select * from user where name=#{name};
      </select>
    
    </mapper>
    

    UserService

    package com.zr.service;
    
    import com.zr.pojo.User;
    
    public interface UserService {
        public User queryUserByName(String name);
    }
    

    UserServiceImpl

    package com.zr.service;
    
    @Service
    public class UserServiceImpl implements UserService{
        @Autowired
        UserMapper userMapper;
        @Override
        public User queryUserByName(String name) {
    
            return userMapper.queryUserByName(name);
        }
    }
    

    MyController

    package com.zr.controller;
    
    @Controller
    public class MyController {
        @RequestMapping({"/","/index"})
        public String toIndex(Model model){
            model.addAttribute("msg","hello shiro");
            return "index";
        }
        @RequestMapping("/user/add")
        public String add(){
            return "user/add";
        }
        @RequestMapping("/user/update")
        public String update(){
            return "user/update";
        }
        @RequestMapping("/toLogin")
        public String toLogin(){
            return "login";
        }
    
        @RequestMapping("/login")
        public String login(String username,String password,Model model){
    
            //获取当前的用户
            Subject subject = SecurityUtils.getSubject();
            //封装用户的登录数据
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    
            try{
                subject.login(token); //执行登录的方法,如果没有一次就ok
                return "index";
            }catch (UnknownAccountException a){ //用户名不存在
                model.addAttribute("msg","用户名错误!");
                return "login";
            }catch (IncorrectCredentialsException a){ //密码不存在
                model.addAttribute("msg","密码错误!");
                return "login";
            }
        }
        @RequestMapping("/noauth")
        @ResponseBody
        public String unAuthorized(){
            return "未授权不能访问此页面!";
        }
    }
    

    UserRealm

    package com.zr.config;
    
    //自定义的UserRealm
    public class UserRealm extends AuthorizingRealm {
        @Autowired
        UserService userService;
        //授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            System.out.println("执行了==>>授权:AuthorizationInfo");
    
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    
            //info.addStringPermission("user:add");
            //拿到当前登录的对象
            Subject subject = SecurityUtils.getSubject();
            User currentUser = (User)subject.getPrincipal(); //拿到user对象
            //设置当前用户的权限
            info.addStringPermission(currentUser.getPerms());
    
            return info;
        }
    
        //认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            System.out.println("执行了==>>认证:AuthenticationInfo");
    
            //用户名密码 数据库中取
    
            UsernamePasswordToken userToken = (UsernamePasswordToken) token;
            User user = userService.queryUserByName(userToken.getUsername());
    
            if (user==null){
                return null;
            }
    
            Subject currentSubject = SecurityUtils.getSubject();
            Session session = currentSubject.getSession();
            session.setAttribute("loginUser",user);
    
    
            //密码认证 shiro默认做
            return new SimpleAuthenticationInfo(user,user.getPwd(),"");
        }
    }
    

    ShiroConfig

    package com.zr.config;
    
    @Configuration
    public class ShiroConfig {
    
        //ShiroFilterFactoryBean
        @Bean
        public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
            ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
            //设置安全管理器
            bean.setSecurityManager(defaultWebSecurityManager);
    
            //添加shiro的内置过滤器
            /*
                anon:无需认证就可以访问
                authc:必须认证
                user:必须拥有记住我功能才能用
                perms:拥有某个资源的权限才能访问
                role:拥有某个角色权限才能访问
             */
            Map<String, String> filterMap = new LinkedHashMap<>();
            //授权 没有授权会跳转到未授权界面(401)
            filterMap.put("/user/add","perms[user:add]");
            filterMap.put("/user/update","perms[user:update]");
    
            filterMap.put("/user/*","authc");
    
            bean.setFilterChainDefinitionMap(filterMap);
            //返回登录页
            bean.setLoginUrl("/toLogin");
            //未授权页面
            bean.setUnauthorizedUrl("/noauth");
            return bean;
        }
    
        //DefaultWebSecurityManager
        @Bean
        public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            //关联realm
            securityManager.setRealm(userRealm);
    
            return securityManager;
        }
    
        //创建realm对象,自定义
        @Bean
        public UserRealm userRealm(){
            return new UserRealm();
        }
    
        //整合shiroDialect 用来整合shiro和thymeleaf
        @Bean
        public ShiroDialect getShiroDialect(){
            return new ShiroDialect();
        }
    }
    

    Swagger

    学习目标:

    • 了解Swagger的概念和作用
    • 巩固前后端分离
    • 在SpringBoot中巩固Swagger

    Swagger简介

    • 号称最流行的Api框架
    • RestFul Api 文档在线自动生成工具=>Api文档与Api定义同步更新
    • 直接运行,可以在线测试Api接口
    • 支持多种语言

    在项目中使用swagger需要导入jar包

    • swagger2
    • ui

    SpringBoot集成Swagger

    1. 新建一个SpringBoot-web项目

    2. 导入相关依赖

      <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
      <dependency>
          <groupId>io.springfox</groupId>
          <artifactId>springfox-swagger2</artifactId>
          <version>2.9.2</version>
      </dependency>
      
      <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
      <dependency>
          <groupId>io.springfox</groupId>
          <artifactId>springfox-swagger-ui</artifactId>
          <version>2.9.2</version>
      </dependency>
      
      
    3. 编写helloword工程

    4. 配置swagger===>config

      package com.zr.config;
      
      @Configuration
      @EnableSwagger2  //开启swagger2
      public class SwaggerConfig {
      }
      
    5. 访问http://localhost:8080/swagger-ui.html

    配置Swagger

    Swagger的Bean实例 Docket

    package com.zr.config;
    
    import java.util.ArrayList;
    
    @Configuration
    @EnableSwagger2  //开启swagger2
    public class SwaggerConfig {
        //配置swagger的Docket的 Bean实例
        @Bean
        public Docket docket(){
            return new Docket(DocumentationType.SWAGGER_2)
                    .apiInfo(apiInfo());
        }
    
        //配置swagger文档的信息
        private ApiInfo apiInfo(){
            //作者信息
            Contact contact = new Contact("周周", "http://www.cnblogs.com/zhou-zr", "813794474@qq.com");
            return new ApiInfo("周周的SwaggerApi文档",
                    "coding",
                    "1.0",
                    "http://www.cnblogs.com/zhou-zr",
                    contact,
                    "Apache 2.0",
                    "http://www.apache.org/licenses/LICENSE-2.0",
                    new ArrayList()
            );
        }
    }
    

    http://localhost:8080/swagger-ui.html

    Swagger配置扫描接口

    Docket select()

    在配置Swagger中修改

    //配置swagger的Docket的 Bean实例
    //enable 是否启动swagger 如果为false 不能再浏览器中访问swagger
    @Bean
    public Docket docket(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
    	        .enable(false)
                .select()
                //配置要扫描接口的方式
                //basePackage 指定扫描的包
                //any() 扫描全部
                //none() 都不扫描
                //withClassAnnotation() 扫描类上的注解 参数是一个注解的反射对象
                //withMethodAnnotation() 扫描方法上的注解
                .apis(RequestHandlerSelectors.basePackage("com.zr.controller"))
                //过滤什么路径
                .paths(PathSelectors.ant("/zr/**"))
                .build();
    }
    

    根据是否是生产环境来开启swagger

    //配置swagger的Docket的 Bean实例
         //enable 是否启动swagger 如果为false 不能再浏览器中访问swagger
         @Bean
         public Docket docket(Environment environment){
             //设置要显示的swagger环境
             Profiles profiles = Profiles.of("dev");
             //通过environment.acceptsProfiles判断是否处在自己设定的环境当中
             boolean flag = environment.acceptsProfiles(profiles);
    
             return new Docket(DocumentationType.SWAGGER_2)
                     .apiInfo(apiInfo())
                     .enable(flag)
                     .select()
                     .apis(RequestHandlerSelectors.basePackage("com.zr.controller"))
                     //过滤什么路径
                     //.paths(PathSelectors.ant("/zr/**"))
                 .build();
     }
    

    application.properties

    spring.profiles.active=dev
    

    application-dev.properties

    server.port=8081
    

    application-pro.properties

    server.port=8082
    

    配置Api文档分组

    .groupName("zzr")
    

    如何配置多个分组:配置多个Docket实例即可。

    @Bean
    public Docket docket2(){
        return new Docket(DocumentationType.SWAGGER_2).groupName("A");
    }
    @Bean
    public Docket docket3(){
        return new Docket(DocumentationType.SWAGGER_2).groupName("B");
    }
    @Bean
    public Docket docket4(){
        return new Docket(DocumentationType.SWAGGER_2).groupName("C");
    }
    

    接口测试

    实体类配置

    package com.zr.pojo;
    
    // @Api(注释)
    @ApiModel("用户实体类")
    public class User {
        @ApiModelProperty("用户名")
        public String username;
        @ApiModelProperty("密码")
        public String password;
    }
    

    HelloController

    package com.zr.controller;
    
    @RestController
    public class HelloController {
        @GetMapping("/hello")
        public String hello(){
            return "hello";
        }
        //只有我们的接口中,返回值中存在实体类,就会被扫描到swagger中
        @PostMapping("/user")
        public User user(){
            return new User();
        }
        @ApiOperation("Hello控制类")
        @GetMapping("/hello2")
        public String hello(@ApiParam("用户名") String username){
            return "hello"+username;
        }
        @ApiOperation("post测试")
        @PostMapping("/postt")
        public User postt(@ApiParam("用户名")User user){
            return user;
        }
    }
    

    总结:

    1. 我们以通过Swagger给一些比较难理解的属性或者接口,增加注释信息
    2. 接口文档实时更新
    3. 可以在线测试

    注意点:在正式上线的时候,关闭Swagger。

    任务

    异步任务

    AsyncService

    package com.zr.service;
    
    @Service
    public class AsyncService {
        //告诉spring这是一个异步的方法
        @Async
        public void hello(){
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("数据正在处理");
        }
    }
    

    AsyncController

    package com.zr.controller;
    
    @RestController
    public class AsyncController {
    
        @Autowired
        AsyncService asyncService;
        @RequestMapping("/hello")
        public String hello(){
            asyncService.hello();  //停止3秒
            return "ok";
        }
    }
    

    主程序开启异步注解的功能

    package com.zr;
    
    //开启异步注解的功能
    @EnableAsync
    @SpringBootApplication
    public class Springboot09TestApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(Springboot09TestApplication.class, args);
        }
    
    }
    

    定时任务

    TaskScheduler  任务调度者
    TaskExecutor  任务执行者
    
    @EnableScheduling //开启定时功能的注解
    @Scheduled  //什么时候执行
    
    Cron 表达式
    

    主程序开启定时任务的支持

    package com.zr;
    
    @EnableScheduling //开启定时功能的注解
    @SpringBootApplication
    public class Springboot09TestApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(Springboot09TestApplication.class, args);
        }
    }
    

    定时任务设置

    package com.zr.service;
    
    @Service
    public class ScheduledService {
        //在一个特定的时间执行
    
        /*
            0 30 10 * * ?   每天的10.30 执行一次
            0 0/5 10,16 * * ?   每天的10点到16点 每隔五分钟执行一次
            0 20 10 ? * 1-6    每个月的周一到周六 10.20执行一次
         */
        //cron表达式  秒 分 时 日 月 星期几(0-7  ?每一天)
        @Scheduled(cron = "0 * * * * ?")
        public void hello(){
            System.out.println("hello,你被执行了");
        }
    }
    

    邮件任务

    package com.zr;
    
    @SpringBootTest
    class Springboot09TestApplicationTests {
    
        @Autowired
        JavaMailSenderImpl mailSender;
        @Test
        void contextLoads() {
            //一个简单的邮件
            SimpleMailMessage mailMessage = new SimpleMailMessage();
            mailMessage.setSubject("小周,你好!");
            mailMessage.setText("好好学习!");
            mailMessage.setTo("813794474@qq.com");
            mailMessage.setFrom("813794474@qq.com");
            mailSender.send(mailMessage);
        }
    
        @Test
        void contextLoads2() throws MessagingException {
            //一个复杂的邮件
            // MimeMessage 也可以直接new创建
            MimeMessage mimeMessage = mailSender.createMimeMessage();
            //组装
            MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true);
            helper.setSubject("周周,你好呀!");
            helper.setText("<h1><p style='color:red'>好好学习!</p></h1>",true);
            //附件
            helper.addAttachment("winC.PNG",new File("C:\Users\zr\Pictures\截图\winC.PNG"));
            helper.addAttachment("2.jpg",new File("C:\Users\zr\Pictures\截图\winC.PNG"));
    
            helper.setTo("813794474@qq.com");
            helper.setFrom("813794474@qq.com");
            mailSender.send(mimeMessage);
        }
    }
    

    分布式

    分布式系统理论

    什么是分布式系统:

    在《分布式系统原理与范型》 一书中有如下定义:分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统。

    分布式系统是有一组通过网络进行通信,为了完成共同的任务而协调工作的计算机节点组成的系统,分布式系统的出现是为了用廉价的,普通的机器完成单个计算机无法完成的计算,存储任务。其目的是利用更多的机器,处理更多的数据。

    只有当单个节点无法满足日益增长的计算,存储任务需求时,且硬件的提升高昂到得不偿失时,且程序也不能进一步优化的时候,我们才需要考虑分布式系统。

    单一应用架构:当网站流量很小时,只需一个应用,将所有的功能部署在一起,以减少部署节点和成本,此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

    适用于小型网站,小型管理系统,将所有的应用部署到一个应用中,简单易用。

    缺点:性能扩展比较难,协同开发问题,不利于升级维护。

    垂直应用架构:当数据量逐渐增大,单一应用增加机器带来的加速度越来越小,将一个应用拆分成互不相干的几个应用,以提升效率,此时,用于加速前端页面开发的Web框架(MVC)是关键。

    通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职,性能更易扩展。

    缺点:公用模块无法重复利用,开发性的浪费。

    分布式服务架构:当垂直应用越来越多,应用之间交互不可避免。将核心应用抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。

    流动计算架构:当服务越来越多,容量的评估,小服务资源的浪费问题等问题逐渐显现,此时需要增加一个调度中心基于访问的压力实时管理集群的容量,提高集群利用率。此时。用于提高资源利用率的资源调度和治理中心(SOA)是关键。

    RPC

    什么是RPC:

    RPC【Remote Procedure Call】是指远程过程调用,是一种进程间的通信方式,它是一种技术的思想,而不是规范,它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不是程序员显示编码这个远程调用的细节,即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。

    RPC两个核心:通信和序列化。

    序列化:数据传输需要转换。

    分布式 Dubbo + Zookeeper + SpringBoot

    Apache Dubbo是一款高性能,轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

    服务提供者(Provider):暴漏服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的 。

    服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

    注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。

    监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

    容器(Container):服务运行容器。

    调用关系说明

    1. 服务容器负责启动,加载,运行服务提供者。

    2. 服务提供者在启动时,向注册中心注册自己提供的服务。

    3. 服务消费者在启动时,向注册中心订阅自己所需的服务。

    4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。

    5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

    6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

    window下安装zookeeper

    官网下载zookeeper,解压至指定目录,管理员模式下进入到zookeeper的bin目录下,启动zkServer.cmd。

    【注意报错】在zkServer.cmd源文件中加pause,可查看报错信息。

    解决:进入conf目录,将zoo_sample.cfg复制一份将名字改为zoo.cfg。

    启动zkServer.cmd , 启动zkCli.cmd

    window下安装Dubbo

    dubbo本身并不是一个服务软件,它其实就是一个jar包,把Java程序连接到zookeeper,并利用zookeeper消费,提供服务。

    为了让用户更好的监控众多的dubbo服务,官方提供了一个可视化的监控程序dubbo-admin,不安装也不影响使用。

    下载dubbo-admin

    cmd进入下载的目录下,执行mvn clean package -Dmaven.test.skip=true

    打包成功

    执行dubbo-admin arget下的dubbo-admin-0.0.1-SNAPSHOT.jar

    命令:java -jar dubbo-admin-0.0.1-SNAPSHOT.jar 【注意 zookeeper的服务一定要打开】

    执行完毕后访问 http://localhost:7001 默认账号密码是root,root。

    登录成功界面

    zookeeper:注册中心

    dubbo-admin是一个监控管理后台,可以查看我们注册了哪些服务,哪些服务被消费了。

    Dubbo:jar包

    步骤:前提开启zookeeper

    1. 提供者提供服务
      1. 导入依赖
      2. 配置注册中心的地址,以及服务发现名,和要扫描的包
      3. 在想要被注册的服务上增加一个注解@Service (Dubbo的)
    2. 消费者如何消费
      1. 导入依赖
      2. 配置注册中心的地址,配置自己的服务名
      3. 从远程注入服务@Reference

    项目结构:

    provider-server

    TickerService

    package com.zr.service;
    
    public interface TickerService {
        public String getTicker();
    }
    

    TickerServiceImpl

    package com.zr.service;
    
    //服务注册与发现
    @Service  //可以被扫描到  项目一启动就自动注册到注册中心
    @Component  //使用dubbo后尽量不要使用service
    public class TickerServiceImpl implements TickerService{
    
        @Override
        public String getTicker() {
            return "zzr";
        }
    }
    

    application.properties

    server.port=8001
    
    #服务应用的名字
    dubbo.application.name=provider-server
    #注册中心地址
    dubbo.registry.address=zookeeper://127.0.0.1:2181
    #哪些服务要被注册
    dubbo.scan.base-packages=com.zr.service
    

    pom.xml(consumer-server的pom相同)

    <!-- dubbo -->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>2.7.8</version>
    </dependency>
    <!-- zkclient -->
    <dependency>
        <groupId>com.github.sgroschupf</groupId>
        <artifactId>zkclient</artifactId>
        <version>0.1</version>
    </dependency>
    <!--日志冲突-->
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-framework</artifactId>
        <version>2.12.0</version>
    </dependency>
    
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>2.12.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.4.14</version>
        <!--    排除slf4j-log4j12-->
        <exclusions>
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    

    consumer-server

    TickerService

    package com.zr.service;
    
    public interface TickerService {
        public String getTicker();
    }
    

    UserService

    package com.zr.service;
    
    @Service  //放到容器中
    public class UserService {
    
        //想拿到票provider-server提供的票  去注册中心拿
        @Reference //引用 pom坐标 定义路径相同的接口名
        TickerService tickerService;
    
        public void byTicket(){
            String ticker = tickerService.getTicker();
            System.out.println("在注册中心拿到了一张票"+ticker);
        }
    
    }
    

    application.properties

    server.port=8002
    #消费者从那里去拿需要暴漏自己的名字
    dubbo.application.name=consumer-server
    #注册中心的地址
    dubbo.registry.address=zookeeper://127.0.0.1:2181
    

    测试

    package com.zr;
    
    @SpringBootTest
    class ConsumerServerApplicationTests {
    
        @Autowired
        UserService userService;
        @Test
        void contextLoads() {
            userService.byTicket();
        }
    }
    

    结果

    dubbo-admin中查看服务提供者

  • 相关阅读:
    MongoVUE 使用教程
    Mongo
    thinkPHP
    从关系型数据库到非关系型数据库
    boost 文件系统
    boost 随机数发生器
    CString/string 区别及其转化
    boost uuid
    使用命令行解析php文件
    Log4PHP日志库使用
  • 原文地址:https://www.cnblogs.com/zhou-zr/p/14590420.html
Copyright © 2011-2022 走看看