spring-boot之整合组件

本文为第二篇
主要是中间件与部署方面的内容

主要参考:
spring-boot-learning

redis

简介

Spring Boot 提供了对 Redis 集成的组件包:spring-boot-starter-data-redis,它依赖于 spring-data-redis 和 lettuce。Spring Boot 1.0 默认使用的是 Jedis 客户端,2.0 替换成了 Lettuce

Lettuce → Spring Data Redis → Spring Data → spring-boot-starter-data-redis
  1. Lettuce:是一个可伸缩线程安全的 Redis 客户端,多个线程可以共享同一个 RedisConnection,它利用优秀 Netty NIO 框架来高效地管理多个连接
  2. Spring Data Redis:是 Spring Data 项目中的一个主要模块,实现了对 Redis 客户端 API 的高度封装,使对 Redis 的操作更加便捷。
  3. Spring Data:是 Spring 框架中的一个主要项目,目的是为了简化构建基于 Spring 框架应用的数据访问,包括非关系数据库、Map-Reduce 框架、云数据服务等,另外也包含对关系数据库的访问支持。

安装

与postgres的安装相同,在docker-compse.yml中添加:

  redis:
    image: redis:4.0.13
    container_name: redis
    restart: always
    command: --appendonly yes
    ports:
      - 6379:6379
    volumes:
      - ./redis_data:/data

然后docker-compose up -d 等待即可

使用

  • 依赖

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
    

    引入 commons-pool 2 是因为 Lettuce 需要使用 commons-pool 2 创建 Redis 连接池。

  • 配置

    配置文件application.properties:

    # Redis 数据库索引(默认为 0)
    spring.redis.database=0
    # Redis 服务器地址
    spring.redis.host=localhost
    # Redis 服务器连接端口
    spring.redis.port=6379  
    # Redis 服务器连接密码(默认为空)
    spring.redis.password=
    # 连接池最大连接数(使用负值表示没有限制) 默认 8
    spring.redis.lettuce.pool.max-active=8
    # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
    spring.redis.lettuce.pool.max-wait=-1
    # 连接池中的最大空闲连接 默认 8
    spring.redis.lettuce.pool.max-idle=8
    # 连接池中的最小空闲连接 默认 0
    spring.redis.lettuce.pool.min-idle=0
    

    配置类RedisConfig

    @Configuration
    @EnableCaching
    public class RedisConfig extends CachingConfigurerSupport{
    
        @Bean
        public KeyGenerator keyGenerator() {
            return new KeyGenerator() {
                @Override
                public Object generate(Object target, Method method, Object... params) {
                    StringBuilder sb = new StringBuilder();
                    sb.append(target.getClass().getName());
                    sb.append(method.getName());
                    for (Object obj : params) {
                        sb.append(obj.toString());
                    }
                    return sb.toString();
                }
            };
        }
    }
    

    以上配置了主键生成的策略,如不配置会默认使用参数名作为主键,这里添加了类名+函数名+参数名

  • 使用

    public class TestRedisTemplate {
      @Autowired
      private RedisTemplate redisTemplate;
    
      @Test
      public void testString()  {
          redisTemplate.opsForValue().set("neo", "ityouknow");
          Assert.isTrue("ityouknow".equals(redisTemplate.opsForValue().get("neo")), "测试出错");
      }
    }
    

session

  • 简介
    使用redis来存储http的sessionId信息,这个spring boot对这块都进行了封装。封装到牙齿的感觉。

  • 依赖

    <dependency>
      <groupId>org.springframework.session</groupId>
      <artifactId>spring-session-data-redis</artifactId>
    </dependency>
    
  • 配置

    application.properties:
    这里的配置就是redis的配置即可

    配置类:

    @Configuration
    @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 86400*30)
    public class SessionConfig {
    }
    

    maxInactiveIntervalInSeconds: 设置 Session 失效时间

  • 使用

    @PostMapping(value = "/login")
      public String login (HttpServletRequest request,String userName,String password){
          String msg="logon failure!";
          User user= userRepository.getByUserName(userName);
          if (user!=null && user.getPassword().equals(password)){
              request.getSession().setAttribute("user",user);
              msg="login successful!";
          }
          return msg;
      }
    
      @GetMapping(value = "/logout")
      public String logout (HttpServletRequest request){
          request.getSession().removeAttribute("user");
          return "loginout successful!";
      }
    
  • 问题

    这里,如果有登录的要求,需要在每个路由中都去判断是否登录,这样在写的时候会比较费劲,适合使用spring切片的方式来进行登录与否的验证

缓存

简介

这里的缓存,是将从关系型数据库中读取数据,变成先从缓存中读取,若没有则从关系型数据库中读取,这里用的缓存,也是用的redis。spring对这些操作也进行了封装,只需要用几个注解,就可以完成

@Cacheable是读取完之后,再次读取时候会做缓存,主要用在Get上
@CachePut,这个是数据发生变化之后,将更新变化数据,主要用在Put上
@CacheEvict,这个是数据删除之后,同时清除缓存

他们共有的属性:value:缓存的名称;key:缓存的key值(redis的hash结构);condition:在什么条件上做缓存
CacheEvict,还有两个属性,allEntries:这个是true清除缓存的所有内容,不是删hash的key,而是删这个整个对象,默认是false;beforeInvocation:这个是值true指在执行Delele前,清除缓存,false是之后清除,默认是false;

依赖

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

使用

  • @Cacheable:读取数据之后,缓存数据

    @GetMapping(value = "user/{id}")
    @Cacheable(value="user", key="#id")
    public User getId(@PathVariable("id") Long id){
        User user = this.userRepository.getById(id);
        return user;
    }
    
  • @CachePut:数据修改之后,更新缓存数据

    @ApiOperation(value ="修改用户信息", notes ="根据传参修改用户")
      @PutMapping(value = "user")
      @CachePut(value="user",key="#id")
      public User modify(String nickName, Long id) throws Exception {
          int r = this.userRepository.upateNickNameById(nickName, id);
          if (r < 0){
              throw new Exception();
          }
    
          User u = this.userRepository.getById(id);
          return u;
      }
    
  • @CacheEvict:数据删除之后,删除缓存数据

    @DeleteMapping(value = "user/{id}")
      @CacheEvict(value="user", key="#id")
      public BaseResult<String> Delete (@PathVariable("id") Long id){
          this.userRepository.deleteById(id);
    
          return BaseResult.success();
      }
    

RabbitMQ

简介

借鉴

  • 说RabbitMQ,要先介绍AMQP
    AMQP(Advanced Message Queuing Protocol,高级消息队列协议)是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。
    RabbitMQ 是一个开源的 AMQP 实现,服务器端用 Erlang 语言编写,支持多种客户端

  • 基本概念
    通常我们谈到队列服务,会有三个概念:生产者(Producer)、队列(Queue)、消费者(Consumer)。RabbitMQ 在这个基本概念之上,多做了一层抽象,在发消息者和队列之间加入了交换机(Exchange)。这样发消息者和队列就没有直接联系,转而变成发消息者把消息给交换器,交换器根据调度策略再把消息再给队列。
    rabbitmq流转图.png

    1. 生产者发送消息的时候指定RoutingKey,然后消息被发送到Exchange
    2. Exchange根据一些列规则(BindingKey)将消息路由到指定的队列中
    3. 消费者从队列中消费消息
  • 交换机
    交换机有4种类型:Direct Exchange(默认)、Topic Exchange、Headers Exchange、Fanout Exchange
    前3种都是消息队列,虽然交换机与队列之间的映射机制不同(路由方式),但对于一条消息,只有一个生产者与消费者。Fanout是广播,对于一条消息可以有多个消费者。
    Direct Exchange把消息路由到BindingKey和RoutingKey完全匹配的队列中
    Topic Exchange把消息路由到BindingKey可以模糊匹配的队列中,Binding Key可以认为是消息名称,Binding Key是一种模糊匹配,代表一类消息,其中"*"表示一个单词,"#"表示多个单词(包括0,1)
    Fanout Exchange把消息发送与该交换机绑定的所有队列上,不光BindingKey,用来广播

准备

  • 安装

    docker-compose.yml

    rabbitmq:
      image: rabbitmq:3-management
      container_name: rabbitmq
      restart: always
      environment:
        RABBITMQ_DEFAULT_USER: "rabbitmq"
        RABBITMQ_DEFAULT_PASS: "rabbitmq"
      ports:
        - 15672:15672
        - 5672:5672
      volumes:
        - ./rabbitmq_data:/var/lib/rabbitmq
    
  • 依赖

 <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-amqp</artifactId>
 </dependency>
  • 配置

    spring.rabbitmq.host=192.168.0.1
    spring.rabbitmq.port=5672
    spring.rabbitmq.username=rabbitmq
    spring.rabbitmq.password=rabbitmq
    

使用

  • Direct Exchange
    spring boot默认使用时,应该自己封装了exchange,只需定义routeKey即可
    可以发送对象,二进制发送,需要实现Serializable

    1. 定义Queue
    @Configuration
    public class RabbitConfig {
    
        @Bean
        public Queue Queue() {
            return new Queue("hello");
        }
    
    }
    
    1. 定义生产者
    public class HelloSender {
    
        @Autowired
        private AmqpTemplate rabbitTemplate;
    
        public void send() {
            String context = "hello " + new Date();
            System.out.println("Sender : " + context);
            this.rabbitTemplate.convertAndSend("hello", context);
        }
    
    }
    
    1. 定义接收者
    @Component
    @RabbitListener(queues = "hello")
    public class HelloReceiver {
    
        @RabbitHandler
        public void process(String hello) {
            System.out.println("Receiver  : " + hello);
        }
    
    }
    
  • topic exchange
    需要定义queue、exchange,并且binding queue与exchange

    1.定义queue、exchange

    @Configuration
    public class TopicRabbitConfig {
    
        final static String message = "topic.message";    // queue名
        final static String messages = "topic.messages";  // queue名
    
        //定义队列
        @Bean
        public Queue queueMessage() {
            return new Queue(TopicRabbitConfig.message);
        }
    
        @Bean
        public Queue queueMessages() {
            return new Queue(TopicRabbitConfig.messages);
        }
    
        //交换机
        @Bean
        TopicExchange exchange() {
            return new TopicExchange("exchange");
        }
    
        //将队列和交换机绑定
        @Bean
        Binding bindingExchangeMessage(Queue queueMessage, TopicExchange exchange) {
            return BindingBuilder.bind(queueMessage).to(exchange).with("topic.message");
        }
    
        @Bean
        Binding bindingExchangeMessages(Queue queueMessages, TopicExchange exchange) {
            return BindingBuilder.bind(queueMessages).to(exchange).with("topic.#");
        }
    }
    

    注意定义queue时使用,只是queue名,在接收者时用来指定某个queue使用。
    后边binding时候使用with才是bindingKey

    1. 发送者
    public void send1() {
        String context = "hi, i am message 1";
        System.out.println("Sender : " + context);
        this.rabbitTemplate.convertAndSend("exchange", "topic.message", context);
    }
    
    public void send2() {
        String context = "hi, i am messages 2";
        System.out.println("Sender : " + context);
        this.rabbitTemplate.convertAndSend("exchange", "topic.messages", context);
    }
    

    这个“exchange”是指明交换机,topic.message与top.messages是routeKey

    1. 接收者

    接收者同上,没有改变。

  • fanout
    fanout与topic类似,定义部分稍有不同,生产者与消费者使用相同

    @Configuration
    public class FanoutRabbitConfig {
    
        //定义队列
        @Bean
        public Queue AMessage() {
            return new Queue("fanout.A");
        }
    
        @Bean
        public Queue BMessage() {
            return new Queue("fanout.B");
        }
    
        //定义交换机
        @Bean
        FanoutExchange fanoutExchange() {
            return new FanoutExchange("fanoutExchange");
        }
    
        //分部进行绑定
        @Bean
        Binding bindingExchangeA(Queue AMessage,FanoutExchange fanoutExchange) {
            return BindingBuilder.bind(AMessage).to(fanoutExchange);
        }
    
        @Bean
        Binding bindingExchangeB(Queue BMessage, FanoutExchange fanoutExchange) {
            return BindingBuilder.bind(BMessage).to(fanoutExchange);
        }
    }
    

Elasticsearch

简介

elasticsearch也借鉴了一篇文章,可惜是微信的,链接有signature,访问会受限,这里没法给出原链接了。

  • 是什么
    Elasticsearch 是一个 real-time, distributed storage, search engine

  • 特性
    Elasticsearch是专门做搜索的,我们在用搜狗、google时候,输入关键字没那么匹配都可以搜索的出来,用elaticsearch就可以做到:

    1. Elasticsearch对模糊搜索非常擅长(模糊查询)
    2. 没有那么准确的关键字也能搜出相关的结果(相关性查询)
    3. 从Elasticsearch搜索到的数据可以根据评分过滤掉大部分的,只要返回评分高的给用户就好了(原生就支持排序)
  • 简单介绍原理
    分词: 写入到Elasticsearch的数据会进行分词
    存储:
    elasticsearch数据结构.png
    从右往左看:position是记录所在的位置,term dictionary是分词字典, term index是为了快速查找分词所做的索引

  • 基本术语

关系型数据库Elasticsearch
TableIndex(Type)
RowDocument
ColumnFiled
SchemaMapping
SQLDSL

这个术语与mongo的术语基本相同,mongo中与Table对应的概念是Collection。所以在分布式存储这块,elasticsearch应该用的NoSQL,至于与mongo的关系还需要探索一下

准备

  • 安装
    借鉴1
    借鉴2

    docker-compose.yml

    version: '3'
    services:
      es1:
        image: docker.elastic.co/elasticsearch/elasticsearch:6.8.6
        container_name: es1
        environment:
          - node.name=es01
          - cluster.name=es-cluster
          - bootstrap.memory_lock=true
          - "ES_JAVA_OPTS=-Xms1024m -Xmx1024m"
        ulimits:
          nproc: 65535
          memlock:
            soft: -1
            hard: -1
        volumes:
          - ./elasticsearch_data/es1_data:/usr/share/elasticsearch/data
        ports:
          - 9200:9200
          - 9300:9300
        networks:
          - esnet
    
      es2:
      image: docker.elastic.co/elasticsearch/elasticsearch:6.8.6
      container_name: es2
      environment:
        - node.name=es02
        - cluster.name=es-cluster
        - bootstrap.memory_lock=true
        - "ES_JAVA_OPTS=-Xms1024m -Xmx1024m"
        - "discovery.zen.ping.unicast.hosts=es1"
      ulimits:
        nproc: 65535
        memlock:
          soft: -1
          hard: -1
      volumes:
        - ./elasticsearch_data/es2_data:/usr/share/elasticsearch/data
      ports:
        - 9201:9200
        - 9301:9300
      networks:
        - esnet
    
      kibana:
        image: docker.elastic.co/kibana/kibana:6.8.6
        container_name: kibana
        environment:
          SERVER_NAME: localhost
          ELASTICSEARCH_URL: http://es1:9200
        ports:
          - 5601:5601
        ulimits:
          nproc: 65535
          memlock:
            soft: -1
            hard: -1
        networks:
          - esnet
    
    networks:
      esnet:
    

    这里ulimits是对资源的限制,nproc是线程的个数,memlock
    浏览器打开http://localhost:5601即可监控集群状态

    在启动过程中出现了max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]意思是最大的虚拟内存太小了,要求扩容,解决方案:
    修改本机的 /etc/sysctl.conf,在文件末尾增加:vm.max_map_count=262144
    sysctl -p生效

    由于本机的内存限制,使用过程中,将es2节点删去之后进行的实验

  • 依赖

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

    spring.data.elasticsearch.cluster-name=es-cluster
    # 集群节点地址列表,用逗号分隔
    spring.data.elasticsearch.cluster-nodes=localhost:9300
    

使用

spring-boot下使用elasticsearch与JPA很相似,因为它们都是在从相同的父类下继承而来

  • 定义对象

    @Document(indexName = "customer", type = "customer", shards = 1, replicas = 0, refreshInterval = "-1")
    public class Customer {
        @Id
        private String id;
        private String userName;
        private String address;
        private int age;
        //省略部分 getter/setter
    }
    

    @Document 注解会对实体中的所有属性建立索引
    indexName = "customer" 表示创建一个名称为 "customer" 的索引
    type = "customer" 表示在索引中创建一个名为 "customer" 的 type
    shards = 1 表示只使用一个分片
    replicas = 0 表示不使用复制
    refreshInterval = "-1" 表示禁用索引刷新

  • repository

    public interface CustomerRepository extends ElasticsearchRepository<Customer, String> {
      public List<Customer> findByAddress(String address);
      public Customer findByUserName(String userName);
      public int  deleteByUserName(String userName);
    }
    
  • 使用

    @SpringBootTest
    public class CustomerRepositoryTest {
      @Autowired
      private CustomerRepository repository;
    
      @Test
      public void saveCustomers() {
          repository.save(new Customer("Alice", "北京",13));
          repository.save(new Customer("Bob", "北京",23));
          repository.save(new Customer("Hibe", "上海",30));
      }
    
      @Test
      public void fetchAllCustomers() {
          for (Customer customer : repository.findAll()) {
              System.out.println(customer);
          }
      }
    
      @Test
      public void deleteCustomers() {
          repository.deleteByUserName("Hibe");
      }
    
      @Test
      public void updateCustomers() {
          Customer customer= repository.findByUserName("Bob");
          customer.setAddress("北京市海淀区");
          repository.save(customer);
          Customer xcustomer=repository.findByUserName("Bob");
          System.out.println(xcustomer);
      }
    
      @Test
      public void fetchIndividualCustomers() {
          for (Customer customer : repository.findByAddress("北京")) {
              System.out.println(customer);
          }
      }
    }
    

Quartz

简介

Quartz其实就是linux的cron,能够定时执行某些任务,spring boot内部也集成了一个简单的定时任务调度。说怎么实现原理的话,可能有2种,一种的用定时器来实现,另一种封装系统的cron来实现,在内核层都应该是软中断。

spring boot自带

  1. application上增加@EnableScheduling
@Spring BootApplication
@EnableScheduling
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  1. 对应函数上增加配置@Scheduled(),cron与fixedRate都可以
@Component
public class SchedulerTask {

    private int count=0;

    @Scheduled(cron="*/6 * * * * ?")
    private void process(){
        System.out.println("this is scheduler task runing  "+(count++));
    }

    @Scheduled(fixedRate = 6000)
    public void reportCurrentTime() {
        System.out.println("现在时间:" + dateFormat.format(new Date()));
    }
}

cron:秒、时、分,日、月、星期、年; *表示每, /表示步长,也就是间隔, ?表示不确定(因为周与日是不会共存的,所以选择一种,另外一个就用?来表示)

Quartz

  • 4个基本概念

    Job: 一个接口,表示要执行的任务
    JobDetail: Job的实例
    Trigger: 什么情况下触发,类似cron或者fixedRate
    Scheduler: 调度器,包含这Job与Trigger

  • 依赖

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>
    
  • 使用

    1. 定义job,需要实现Job接口
    public class ScheduledJob implements Job {
        @Override  
        public void execute(JobExecutionContext context) throws JobExecutionException {
            System.out.println("schedule job1 is running ...");
        }  
    }
    
    1. 调用
    private void scheduleJob(Scheduler scheduler) throws SchedulerException{
        // 这里生成JobDetail
        JobDetail jobDetail = JobBuilder.newJob(ScheduledJob.class) .withIdentity("job", "group1").build();
    
        // 这里生成Trigger
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/6 * * * * ?");
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("trigger", "group") .withSchedule(scheduleBuilder).build();
    
        // 用Scheduler,注册JobDetail与Trigger
        scheduler.scheduleJob(jobDetail,cronTrigger);
    }  
    
    1. 调用
    public void scheduleJobs() throws SchedulerException {
      Scheduler scheduler = schedulerFactoryBean.getScheduler();
      scheduleJob(scheduler);
    }  
    
    1. 启动时开始运行
    @Component
    public class MyStartupRunner implements CommandLineRunner {
    
        @Autowired
        public CronSchedulerJob scheduleJobs;
    
        @Override
        public void run(String... args) throws Exception {
            scheduleJobs.scheduleJobs();
            System.out.println(">>>>>>>>>>>>>>>定时任务开始执行<<<<<<<<<<<<<");
        }
    }
    

邮件系统

  • 简介

    sendmail.png
    发送的时候使用SMTP协议,服务器推送时候使用POP3或者IMAP协议

  • 依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>
    
  • 配置

    spring.mail.host=smtp.163.com //邮箱服务器地址
    spring.mail.username=xxx@oo.com //用户名
    spring.mail.password=xxyyooo    //密码
    spring.mail.default-encoding=UTF-8
    
    //超时时间,可选
    spring.mail.properties.mail.smtp.connectiontimeout=5000  
    spring.mail.properties.mail.smtp.timeout=3000
    spring.mail.properties.mail.smtp.writetimeout=5000
    

    这里的密码需要注意,并不是邮箱的密码,而是开启POP3的客户端授权码

  • 使用

    @Component
    public class MailService{
    
        private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
        @Autowired
        private JavaMailSender mailSender;
    
        @Value("${spring.mail.username}")
        private String from;
    
        @Override
        public void sendSimpleMail(String to, String subject, String content) {
            SimpleMailMessage message = new SimpleMailMessage();
            message.setFrom(from);
            message.setTo(to);
            message.setSubject(subject);
            message.setText(content);
    
            try {
                mailSender.send(message);
                logger.info("简单邮件已经发送。");
            } catch (Exception e) {
                logger.error("发送简单邮件时发生异常!", e);
            }
    
        }
    }
    

安全

  • 简介
    Spring Security,是封装401与403的模块。401是未登录问题,这个感觉还不错;403是无权限问题,这个对复杂的,感觉还差一点

  • 依赖

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

    加上这个依赖,默认所有的路由,都需要登录认证

  • 登录认证

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/", "/home").permitAll()
                    .anyRequest().authenticated()
                    .and()
                .formLogin()
                    // .loginPage("/login")
                    .permitAll()
                    .and()
                .logout()
                    .permitAll()
                    .and()
                .csrf()
                    .ignoringAntMatchers("/logout");
    
        }
    }
    

    这个是统一配置,哪些路由需要登录才能访问,哪个可以直接访问
    antMatchers("/", "/home").permitAll(),这个意思是”/“,"/home"可以直接访问
    anyRequest().authenticated(),这个是其他的都需要登录认证
    后边的几个是对登录与退出说的,对于Restful,可以省略

  • 权限认证
    权限认证,首先要告诉spring boot有哪些角色、与成员,然后可以在config中确定哪些路由需要什么样的角色、权限,或者可以直接在函数上注解权限或角色。

    1. 配置角色

    在SecurityConfig中增加:

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .passwordEncoder(new BCryptPasswordEncoder())
                .withUser("user")
                    .password(new BCryptPasswordEncoder()
                        .encode("123456")).roles("USER")
                .and()
                .withUser("admin")
                    .password(new BCryptPasswordEncoder()
                        .encode("admin")).roles("ADMIN", "USER");
    }
    

    这里是直接通过代码写进去的,没有借鉴意义,只作演示用途

    1. 统一配置路由方式

    同登录认证,在SecurityConfig中

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/resources/**", "/").permitAll()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/content/**").access("hasRole('ADMIN') or hasRole('USER')")
                .anyRequest().authenticated()
                .and()
            .formLogin()
    //                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll()
                .and()
            .csrf()
                .ignoringAntMatchers("/logout");
    }
    

    这里antMatchers("/admin/**").hasRole("ADMIN"),/admin路由,需要有'ADMIN'角色才可以访问,
    antMatchers("/content/**").access("hasRole('ADMIN') or hasRole('USER')"),/content路由,'ADMIN','USER'角色都可以访问

    1. 在函数注解方式

    使用PreAuthorize()来配置,使用之前,需要先打开PreAuthorize的开关,否则不起作用
    在SecurityConfig上,增加@EnableGlobalMethodSecurity(prePostEnabled = true) 注解即可。

    @PreAuthorize("hasAuthority('ADMIN')")
    @RequestMapping("/admin")
    public String admin() {
        return "admin";
    }
    
    1. 关于权限认证

    权限认证,是可以通过角色,也可以通过权限。如果角色固定,且较少的话,通过角色会好一些;如果角色不定,用权限来做会更佳一些。

监控

简介

对应用的监控,spring boot给出的方案是Actuator,它的监控分成两类:原生端点和用户自定义端点。自定义端点主要是指扩展性,用户可以根据自己的实际应用,定义一些比较关心的指标,在运行期进行监控。
原生端点又可以分成三类:
应用配置类,可以查看应用在运行期的静态信息,例如自动配置信息、加载的 springbean 信息、yml 文件配置信息、环境信息、请求映射信息;
度量指标类,主要是运行期的动态信息,如堆栈、请求连、一些健康指标、metrics 信息等;
操作控制类,主要是指 shutdown,用户可以发送一个请求将应用的监控功能关闭。

Actuator的提供了很多接口常用的:

接口路径描述
health/health报告应用程序的健康指标
info/info获取应用程序的定制信息,这些信息由 info 打头的属性提供
env/env获取全部环境属性
metrics/metrics报告各种应用程序度量信息,比如内存用量和 HTTP 请求计数
heapdump/heapdumpdump 一份应用的 JVM 堆信息
threaddump/threaddump获取线程活动的快照
httptrace/httptrace显示 HTTP 足迹,最近 100 个 HTTP request/repspons(未成功)
sessions/sessions如果我们使用了 Spring Session 展示应用中的 HTTP Sessions 信息
logfile/logfile返回 log file 中的内容(如果 logging.file 或者 logging.path 被设置)

另外,在Actuator基础上,构建了分布式的Admin,来收集各应用的Actuator信息,进行汇总显示

Actuator

  • 依赖

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

    开启哪些监控,可以进行配置
    management.endpoints.web.exposure.include=*,默认开启health与info,通过这个设置,开启所有
    management.endpoints.web.base-path=/manage, 默认base路径是/actuator/*,这个可以进行配置
    management.endpoint.health.show-details=always,对health监控显示的内容进行配置

Admin

  • server依赖

    <dependency>
      <groupId>de.codecentric</groupId>
      <artifactId>spring-boot-admin-starter-server</artifactId>
      <version>2.1.0</version>
    </dependency>
    
  • sever配置

    server.port=8000 修改启动的端口

  • client依赖

    <dependency>
      <groupId>de.codecentric</groupId>
      <artifactId>spring-boot-admin-starter-client</artifactId>
      <version>2.1.0</version>
    </dependency>
    

    spring-boot-admin-starter-client 会自动添加 Actuator 相关依赖

  • client配置

    spring.application.name=Admin Client
    # 配置sever的地址
    spring.boot.admin.client.url=http://localhost:8000  
    # 打开所有监控
    management.endpoints.web.exposure.include=*
    
  • 访问
    通过http://localhost:8000访问页面,就可以查看指标了

部署

普通部署

  • 打包配置
    可以打包成jar包,也可以打成war包,这里只介绍jar包
    配置文件中设置打包方式:

    <packaging>jar</packaging>
    
  • 打包命令
    cd到项目根目录下,执行
    mvn clean package -Dmaven.test.skip=true
    mvn clean 是清除项目target目录下的文件
    mvn package 是打包,可以跟clean一起执行,类似于管道。默认情况下,打包时候会自动运行test下的测试,如果失败打包就结束,可以通过-Dmaven.test.skip=true来禁用此测试。

    打包成功后,在target目录下,就会生成jar文件

  • 运行

    java -jar target/spring-boot-package-1.0.0.jar
    nohup java -jar spring-boot-package-1.0.0.jar &

  • 多配置文件

    1. pom.xml修改
    <profiles>
      <profile>
          <id>dev</id>
          <properties>
              <env>dev</env>
          </properties>
          <activation>
              <activeByDefault>true</activeByDefault>
          </activation>
      </profile>
      <profile>
          <id>test</id>
          <properties>
              <env>test</env>
          </properties>
      </profile>
      <profile>
          <id>pro</id>
          <properties>
              <env>pro</env>
          </properties>
      </profile>
      </profiles>
    
      <build>
          <plugins>
              <plugin>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-maven-plugin</artifactId>
              </plugin>
          </plugins>
      </build>
    
    1. 配置文件修改
      在 Spring Boot 中多环境配置文件名需要满足 application-.properties 的格式
      在 resources 目录下增加以下三个文件。
      application-dev.properties:开发环境
      application-test.properties:测试环境
      application-prod.properties:生产环境

      将原来的application.properties修改为spring.profiles.active=dev,这样默认时候的是dev配置文件

    2. 运行
      打包完成之后,可以通过java -jar target/spring-boot-package-1.0.0.jar --spring.profiles.active=dev 的方式来运行

  • Jenkins简介
    Jenkins,批量部署时候,它是目前CI领域使用最广泛的工具之一,它是一个独立的开源自动化服务器,可用于自动化各种任务,如构建、测试和部署软件。Jenkins 可以通过本机系统包以 Docker 的方式部署项目。

    使用 Jenkin 之后,部署项目的步骤如下:
    push 代码到 Github(或者 SVN) 触发 WebHook
    Jenkins 从仓库拉去代码
    Maven 构建项目、单元测试
    备份项目,停止正在运行的项目
    启动应用
    查看启动日志

docker部署

  • 配置
    在pom.xml中,增加

    <!--镜像前缀-->
    <properties>
      <docker.image.prefix>friday</docker.image.prefix>
    </properties>
    
    <!-- 中间省略 -->
    
    <build>
      <plugins>
          <plugin>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-maven-plugin</artifactId>
          </plugin>
          <!-- Docker maven plugin -->
          <plugin>
              <groupId>com.spotify</groupId>
              <artifactId>docker-maven-plugin</artifactId>
              <version>1.0.0</version>
              <configuration>
                  <imageName>${docker.image.prefix}/${project.artifactId}</imageName>
                  <dockerDirectory>src/main/docker</dockerDirectory>
                  <resources>
                      <resource>
                          <targetPath>/</targetPath>
                          <directory>${project.build.directory}</directory>
                          <include>${project.build.finalName}.jar</include>
                      </resource>
                  </resources>
              </configuration>
          </plugin>
          <!-- Docker maven plugin -->
      </plugins>
    </build>
    

    ${project.build.directory},构建目录,缺省为 target
    ${project.build.finalName},产出物名称,缺省为 ${project.artifactId}-${project.version}

  • Dockerfile

    FROM openjdk:8-jdk-alpine
    ADD account-0.1.0.jar app.jar
    ENTRYPOINT ["java","-jar","/app.jar"]
    

    account-0.1.0.jar是打的jar包

  • 打包部署

    1. pro的配置文件
      application-pro.properties中的localhost需要修改成对应的docker,如

      spring.datasource.url: jdbc:postgresql://postgresql:5432/account  
      ...
      spring.redis.host=redis
      
    2. 打包
      mvn clean package -Dmaven.test.skip=true

    3. 启动
      java -jar target/account-0.1.0.jar --spring.profiles.active=pro
      运行正常,若启动失败,需要调试到正常

    4. 打镜像
      mvn docker:build

    5. docker-compose.yml

    account:
    image: friday/account:latest
    container_name: account
    restart: unless-stopped
    environment:
      spring.profiles.active: pro
    ports: 
      - 8080:8080
    links: 
      - postgresql
      - redis
    
    1. 启动
      docker-compose up -d

后记

spring boot使用学习到此结束,嚼别人嚼过的馒头,虽速度不慢,但真不香。如果想深入的学习应该去看看spring boot的实现,Druid的实现,Elasticsearch的实现,RabbitMQ的原理等内容。这些全看,太费时,还有更重要的内容要去学习;不看,浅尝辄止,对于自己没有多少提高。那就综合一下,对spring boot的实现进行一下探索。

这里有关于学习有些话想说,使用级别的学习,也是最肤浅的学习,就如同小孩子学会用水杯喝水,我称它为器;对原理与实现的学习,是第二阶段的学习,这就像学习如何制造水杯,我称它为术法;在众多术法以及术法演化中,涌现出了一些共性的东西,我称它为道,道不再局于某个领域,经济、组织、控制、生态、心智无不包含其中。吾辈学习,应以器学术,以术悟道。

# spring 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×