spring boot 高级


基础

六、自定义Starter

1. 底层原理

2. 自定义配置Starter

starter :

1、这个场景需要使用到的依赖是什么?

2、如何编写自动配置

@Configuration 		//指定这个类是一个配置类
@ConditionalOnMissingBean@ConditionalOnClass
@ConditionalOnXXX 	//在指定条件成立的情况下 自动配置类生效
@AutoConfigureAfter //指定自动配置类的顺序
@Bean 					//给容 器中添加组件

@ConfigurationPropertie 结合相关xxxProperties类来绑定相关的配置
@EnableConfigurationProperties //让xxxProperties生效加入到容 器中
   
自动配置类要能加载
将需要启动就加载的自动配置类,配置在META- INF /spring. factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\

3、模式:

  1. 启动器(starter)启动器只用来做依赖导入,专门来写一个自动配置模块,启动器依赖自动配置,别人只需要引入启动器 例如:spring-boot-starter-web 作用只是导入依赖,依赖里面有具体实现

  2. 官方命名规范:前缀 “spring-boot-starter-[模块名]” 例如:“spring-boot-starter-web”

    自定义命名规范:后缀 “-spring-boot-starter” 例如:“mybatis-spring-boot-starter”

可以参阅 博客

七、spring boot 缓存

1. 缓存抽象Cache

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

一、搭建基本环境
1. 导入数据库文件创建出department和employee表

  1. 创建javaBean封装数据

  2. 整合MyBatis操作数据库

    1. 配置数据源信息

    2. 使用注解版的MyBatis;

      ​ 1) 、@MapperScan指定需 要扫描的mapper接口所在的包

二、快速体验缓存

步骤:

  1. 开启基于注解的缓存

  2. 标注缓存注解

    将方法的运行结果进行缓存,以后再要相同的数据,直接从缓存中获取,不用调用方法;
    CacheManager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字;
    里面的几个属性:
    cacheNames/value: 指定缓存组件的名字;
    key: 缓存数据使用的key;可以用它来指定。默认是使用方法参数的值1 -方法的返回值
    编写SpEL; #id;参数id的值 #a0 #pθ #root . args[0]
    keyGenerator: key的生成器;可以自己指定key的生成器的组件id
    key/keyGenerator: 二选一使用
    cacheManager: 指定缓存管理器;或者cacheResol ver指定获取解析器
    condition: 指定符合条件的情况下才缓存; condition=“#id>0”
    unless: 否定缓存,当unles s指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断
    unless = "#result == null "
    sync:是否使用异步模式


底层原理

原理:

1、自动配置类; CacheAutoConfiguration
2、缓存的配置类 org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration…

3、哪个配置类默认生效: SimpleCacheConf iguration;
4、给容器中注册了- -个CacheManager: ConcurrentMapCacheManager
5、可以获取和创建ConcurrentMapCache类型的缓存组件;他的作用将数据保存在ConcurrentMap中;

运行流程:

@Cacheable:
1、方法运行之前,先去查询Cache (缓存组件),按照cacheNames指定的名字获取;
( CacheManager先获取相应的缓存),第一次获取緩存如果没有Cache组件会自动创建。
2、去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
key是按照某种策略生成的;默认是使用keyGenerator生成的, 默认使用SimpleKeyGenerator生 成key;
SimpleKeyGenerator生成key的默认策略;
如果没有参数; key=new SimpleKey();
如果有一个参数: key=参数的值
如果有多个参数: key=new SimpleKey(params);
3、没有查到缓存就调用目标方法:
4、将目标方法返回的结果,放进缓存中

@Cacheabl e标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,
如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据;

核心:

1)、使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
2)、key使用keyGenerator生成的,默认是SimpleKeyGenerator

注解使用

@Cacheable

存储缓存: 在目标方法执行之前判断有没有对应缓存中的key,

如果有,就取缓存中的值不在执行方法。没有等方法执行完后存到缓存中。

存在缓存中的 key 默认是方法参数名 value是方法返回的结果 ,可以用cacheable中的 key 属性指定key值

@Service
@EnableCaching
public class EmpServiceimpl implements EmpService {
    
    
    @Autowired
    EmployeeMapper employeeMapper;

    @Cacheable(cacheNames = "emp")
    public Employee getEmp(Integer id) {
    
    
        System.out.println("查询第"+id+"号员工");
        return employeeMapper.getById(id);
    }
}

@CachePut

@CachePut: 既调用方法,又更新缓存数据;修改了数据库的某个数据,同时更新缓存;
运行时机:
1、先调用目标方法
2、将目标方法的结果缓存起来
测试步骤:
1、查询1号员工;查到的结果会放在缓存中;
2、更新1号员工; [lastName : zhangsan; gender:1]
将方法的返回值也放进缓存了;
4、再查询1号员工?
应该是更新后的员工;
为什么是没更新前的员工 [1号员工没有在缓存中更新]

第一次传入缓存的是key : 1 value: Las tName:张三

第二次传入缓存的是key: 传入的employee对象值 返回的employee对象;

@EnableCaching
public class EmpServiceimpl implements EmpService {
    
    
    @Autowired
    EmployeeMapper employeeMapper;

    @Cacheable(cacheNames = "emp")
    public Employee getEmp(Integer id) {
    
    
        System.out.println("查询第"+id+"号员工");
        return employeeMapper.getById(id);
    }

    @CachePut(value = "emp",key = "#result.id") //key = "#employee.id"
    public Employee updateEmp(Employee employee) {
    
    
        System.out.println("----------------> update:"+employee);
        employeeMapper.updataEmp(employee);
        return employee;
    }
}

@CacheEvict

@CacheEvict:缓存清除
key:指定要清除的数据
allEntries = true: 指定清除这个缓存中所有的数据 再次查询需要从数据库再从新获取值
beforeInvocation = false: 缓存的清除是否在方法之前执行
默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除
beforeInvocation = true:
代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除

    @Override
    @CacheEvict(value = "emp",allEntries = true,beforeInvocation = true)
    public void delEmp(Integer id) {
    
    
        System.out.println("delete :"+id);
        //employeeMapper.delEmp(id);
        //int i=10/0;
    }

@Caching

coustmize 自定义配置

@Caching(
   cacheable = {
    
    
      @Cacheable(value = "emp",key = "#lastName") //按方法参数lastName存储
   },
   put={
    
     //put注解是方法执行后调用,所有一定会执行该方法
      @CachePut(value = "emp",key = "#result.id"), //按方法返回值id存储
      @CachePut(value = "emp",key = "#result.email") //按方法返回值email存储
   }
)
public Employee getByLastName(String lastName) {
    
    
   return employeeMapper.getByLastName(lastName);
}

@CacheConfig

指定公共属性 抽取缓存的公共配置

@Service
@CacheConfig(cacheNames="emp")
@EnableCaching
public class EmpServiceimpl implements EmpService {
    
    
    @Autowired
    EmployeeMapper employeeMapper;

    @Cacheable(/*cacheNames = "emp"*/)
    public Employee getEmp(Integer id) {
    
    
        System.out.println("查询第"+id+"号员工");
        return employeeMapper.getById(id);
    }
}

2. Redis and Cache

1、安装redis:使用docker;
2、引入redis的starter
3、配置redis
4、测试缓存
原理: CacheManager===Cache 缓存组件来实际给缓存中存取数据
1)、引入redis的starter, 容器中保存的是RedisCacheManager;
2)、RedisCacheManager 帮我们创建RedisCache 来作为缓存组件;

普通Redis使用

默认使用的是RedisTemplate 也就是Lettuce 更详细的请看redis Study document

<dependencies>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
   </dependency>
</dependencies>
spring.datasource.url=jdbc:mysql://localhost:3306/spring_cache?characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=1008611
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

mybatis.configuration.map-underscore-to-camel-case=true
logging.level.com.whgc.mapper=debug

debug=true #自动配置报告
spring.redis.host=192.168.154.130
@SpringBootTest
class Springboot01CacheApplicationTests {
    
    
   
    @Autowired
    RedisTemplate redisTemplate;
    @Test
    void t2(){
    
     //用set会有序列化影响, append 不会
        redisTemplate.opsForValue().append("testSet01","9874564");
    }

RedisConfig

@Configuration
public class RedisConfig {
    
    
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
    
    
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        template.setDefaultSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
        return template;
    }
}
    @Test
    void t3(){
    
    
        Employee emp = empService.getEmp(2); //即使存储对象也不会序列化
        redisTemplate.opsForValue().set("employee:"+emp.getId(), emp);
    }

Redis 结合 Cache

如果加了redis ,Cache 会自动把缓存存储到redis中

配置RedisCacheManager Serializer

	 @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
    
    
        //初始化一个RedisCacheWriter
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
        //设置CacheManager的值序列化方式为json序列化
        RedisSerializer<Object> jsonSerializer = new GenericJackson2JsonRedisSerializer();
        RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair
                .fromSerializer(jsonSerializer);
        RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(pair);
        //设置默认超过期时间是30秒
        defaultCacheConfig.entryTtl(Duration.ofSeconds(30));
        //初始化RedisCacheManager
        return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
    }
@Service
public class DeptServiceimpl implements DeptService {
    
    

    @Autowired
    DepartmentMapper departmentMapper;

    @Override
    @Cacheable(cacheNames = "dept")
    public Department getDept(Integer id) {
    
    
        return departmentMapper.getDept(id);
    }
}

八、spring boot 消息

  1. 大多应用中,可通过消息服务中间件来提升系统异步通信、扩展解耦能力

  2. 消息服务中两个重要概念:
    消息代理(message broker)和目的地(destination)
    当消息发送者发送消息以后,将由消息代理接管,消息代理保证消息传递到指定目
    的地。

  3. 消息队列主要有两种形式的目的地

    1. 队列(queue) :点对点消息通信(point-to-point)

    2. 主题(topic) :发布(publish) /订阅(subscribe) 消息通信

  4. 点对点式:
    消息发送者发送消息, 消息代理将其放入-一个队列中,消息接收者从队列中获取消息内容,消息读取后被移出队列
    消息只有唯一的发送者和接受者,但并不是说只能有一个接收者

  5. 发布订阅式:
    发送者(发布者)发送消息到主题,多个接收者(订阅者)监听(订阅)这个主题,那么就会在消息到达时同时收到消息

  6. Spring支持
    spring-jms提供了对 **JMS(java内部的)**的支持

    spring-rabbit提供了对 **AMQP (跨平台跨语言)**的支持
    需要ConnectionFactory的实现来连接消息代理
    提供Jms Template、RabbitTemplate来发送消息
    @JmsListener (JMS)、@RabbitListener (AMQP)注解在方法上监听消息代理发布的消息
    @EnableJms、@EnableRabbit开启支持

1. RabbitMQ

RabbitMQ简介:
RabbitMQ是一个由erlang开发的AMQP(Advanved Message Queue Protocol)的开源实现。
核心概念
Message
消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由-系列的可选属性组成,这些属性包括routing-key (路由键)、priority (相对于其他消息的优先权)、delivery-mode (指出该消息可能需要持久性存储)等。
Publisher
消息的生产者,也是一个向交换器发布消息的客户端应用程序。
Exchange
交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。
Exchange有4种类型: direct(默认), fanout, topic,和headers,不同类型的Exchange转发消息的策略有
所区别

use RabbitMQ

在Linux 上安装好后 Rabbitmq 图形化管理系统 账号 : guest 密码 : guest

  1. 引入spring-boot-starter-amqp

  2. application.ym|配置

  3. 测试RabbitMQ

  4. AmqpAdmin:管理组件

  5. RabbitTemplate: 消息发送处理组件

自动配置
1、Rabbi tAutoConfiguration
2、有自动配置了连接工厂ConnectionFactory;
3、RabbitProperties 封装了RabbitMQ的配置
4、RabbitTemplate :给RabbitMQ发送和接受消息;
5、AmqpAdmin : RabbitMQ系统管理功能组件
<dependencies>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-amqp</artifactId>
   </dependency>
   <dependency>
      <groupId>org.springframework.amqp</groupId>
      <artifactId>spring-rabbit-test</artifactId>
      <scope>test</scope>
   </dependency>
</dependencies>
spring.rabbitmq.host=192.168.154.130
@SpringBootTest
class SpringbootRabbitmqApplicationTests {
    
    

    @Autowired
    RabbitTemplate rabbitTemplate;
    //1. 单播,点对点
    @Test
    void contextLoads() {
    
    
        Map<String,Object> map = new HashMap<>();
        map.put("msg","this is a info");
        map.put("data", Arrays.asList("Hello World",123,true));
        rabbitTemplate.convertAndSend("exchange.direct","whgc.news",new Book("西游记","吴承恩"));
    }
    //接收数据
    @Test
    void t1(){
    
    
        Object o = rabbitTemplate.receiveAndConvert("whgc");
        System.out.println(o.getClass());
        System.out.println(o);
    }
    //广播
    @Test
    void t2(){
    
    
        rabbitTemplate.convertAndSend("exchange.fanout","",new Book("三国演义","罗贯中"));
    }
       @Autowired
    AmqpAdmin amqpAdmin; //amqpAdmin创建管理工具
    @Test
    void createExchange(){
    
    
//        amqpAdmin.declareExchange(new DirectExchange("amqpadmin.exchange"));
//        System.out.println("Create sueecss finish!");
//        //Create bind 规则          创建一个队列
//        amqpAdmin.declareQueue(new Queue("amqpadmin.queue",true));
        //将 amqpadmin.queue 队列绑定到 amqpadmin.exchange 交换器 
        amqpAdmin.declareBinding(new Binding("amqpadmin.queue",Binding.DestinationType.QUEUE,"amqpadmin.exchange","amqp.haha",null));
    }
}

Listener Book.class

@Service
public class RabbitServiceimpl implements RabbitService {
    
    

    @Override  //test的时候,在发布在消息队列里的内容会被拦截,而且打印不出内容
   	//只有在原本就有内容的时候,一次性全部清空获得数据
    @RabbitListener(queues = "whgc.news")
    public void booklistener(Book book) {
    
    
        System.out.println("收到book 信息"+book);
    }

    @Override
    @RabbitListener(queues = "whgc.#")
    public void booklistener01(Message message) {
    
    
        System.out.println(message.getBody());
        System.out.println(message.getMessageProperties());
    }
}
@EnableRabbit
@SpringBootApplication
public class SpringbootRabbitmqApplication {
    
    

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

}

九、spring boot 检索

简介

我们的应用经常需要添加检索功能,开源的ElasticSearch是目前全文搜索引擎的首选。他可以快速的存储、搜索和分析海量数据。Spring Boot通过整合SpringData ElasticSearch为我们提供了非常便捷的检索功能支持;

Elasticsearch是一个分布式搜索服务, 提供Restful API,底层基于Lucene,采用多shard (分片)的方式保证数据安全,并且提供自动resharding的功能,github等大型的站点也是采用了ElasticSearch作为其搜索服务,

docker install ElasticSearch 访问 有数据表示成功安装

1. 启动
docker run -di --name=自定义名字 -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" -p 9200:9200 -p 9300:9300 elasticsearch:版本号
例如:docker run -d -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" --name es -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch

2. 查看状态,
docker ps
3. 查看容器日志错误信息
docker logs -f [容器id]/[name]
#max virtual memory areas vm.max_map_count [65530]...
4. 查看max_map_count 
cat /proc/sys/vm/max_map_count
5. 设置max_map_count
sysctl -w vm.max_map_count=262144

1. 整合ElasticSearch测试

  • 引入sp ring-boot-starter-data-elasticsearch
  • 安装Spring Data对应版本的ElasticSearch
  • application.yml配置
  • Spring Boot自动配置的
  • ElasticsearchRepository、ElasticsearchTemplate、 Client
  • 测试ElasticSearch

1)、SpringData ElasticSearch

SpringBoot默认支持两种技术来和ES交互;

1、Jest. (默认不生效) //过时了
需要导入jest的工具包(io. searchbox. client. JestClient)

2、SpringData ElasticSearch

  1. 、Client 节点信息clusterNodes; clusterName
    2)、ElasticsearchTemplate 操作es
    3)、编写一个ElasticsearchRepository的子接口来操作ES;

ElasConfig , 参照官网,不管是什么技术,官网教程都很完善。只是有一些写的不细致,让人拐进去,特别注意例子中的变量就ok了。

@Configuration
public class ElasConfig extends AbstractElasticsearchConfiguration {
    
    

  /*  @Bean
    public RestHighLevelClient restHighLevelClient(){
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("192.168.154.130",9200,"http")));
        return client;
    }*/
    @Override
    @Bean
    public RestHighLevelClient elasticsearchClient() {
    
    

        final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
                .connectedTo("192.168.154.130:9200")
                .build();
        return RestClients.create(clientConfiguration).rest();
    }
}

使用 ElasticsearchRepository的子接口来操作ES;

@Repository
public interface BookRepository extends ElasticsearchRepository<Book, String> {
    
    
    //其他方法写法参照官方文档
     List<Book> findByBooknameLike(String bookName);
     Book findByBookname(String bookName);

    List<Book> findByBooknameOrAuthor(String bookName,String author);
    //指定索引查询
    List<Book> findByIdBetween(String index1,String index2);
}
@Document(indexName = "whgc", type = "book", shards = 1, replicas = 0,refreshInterval = "-1")
public class Book {
    
    
    @Id  //建议id不要写Integer类型 
    private String id;
    @Field
    private String bookname;
    @Field
    private String author;
}  //bean class
@SpringBootTest
class SpringbootElasticsearchApplicationTests {
    
    

    @Autowired
    BookRepository bookRepository;
    @Test
    void contextLoads() {
    
    
        System.out.println("Test Context Run ---");
        //bookRepository.deleteAll();
        for(Book books:bookRepository.findByBooknameLike("红")){
    
    
            System.out.println(books);
        }
       Book book = bookRepository.findByBookname("三国");
        if(book!=null){
    
    
            System.out.println(book);
        }else {
    
    
            System.out.println("query a book you need not exists! ");
        }
    }
   //delete info  delete failed not rollback
    @Test
    void tOr(){
    
     //Or只出现前面的。不晓得为啥
        //List<Book> books = bookRepository.findByBooknameOrAuthor("三","红");
        //List<Book> books = bookRepository.findByIdBetween("1","3");
        //bookRepository.deleteById("3");
        //List<Book> books = bookRepository.findByIdBetween("1","3");
        final Iterable<Book> books = bookRepository.findAll();
        if(books == null){
    
    
            System.out.println("query a book you need not exists! ");
            return;
        }
        for(Book book : books){
    
    
            System.out.println(book);
        }
    }
}

十 、Spring boot 任务

1. 异步任务

在开发中,我们可能经常会遇到一些需要执行时间很长的任务,如果放在前端,会让用户一直卡在那儿等待或者一直转圈圈,体验非常不好。为了改善这种体验,我赶紧上网搜索,果然,前人早已有解决办法了。那就是异步。在Django中,我们可以使用celery异步框架,我们可以把耗时的任务扔到后台,而前端给用户立即返回,待用户需要查看结果时,点击查看即可,并且可以随时看到任务执行的状态。

实现:

@Service
public class AsyncServiceimpl implements AsyncService {
    
    

    @Override
    @Async
    public void asynHello() {
    
    
            try{
    
    
                Thread.sleep(5000);
            }catch(Exception e){
    
    
                e.printStackTrace();
            }  
//       即时要睡5秒 前端的页面不会等待,不受到影响
            System.out.println("task on doing...");
    }
}
@RestController
public class HelloController {
    
    

    @Autowired
    AsyncService asyncService;

    @GetMapping({
    
    "/","/hello"})
    public String hello(){
    
    
        asyncService.asynHello();
        return "hello";
    }
}
@EnableAsync   //start scan 注解 @Async
@SpringBootApplication
public class SpringbootTaskApplication {
    
    

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

}

2. 定时任务

项目开发中经常需要执行一 些定时任务,比如需要在每天凌晨时候,分析一 ~次前一天的日志信息。 Spring为我们提供 了异步执行任务调度的方式,提供TaskExecutor、TaskScheduler接口。

@EnableScheduling @Scheduled

cron表达式:

字段 允许值 允许的特殊字符
0-59 , - * /
0-59 , - * /
0-23 , - * /
日期 1-31 , - * / ? L W C
月份 1-12 , - * /
星期 0-7 或 SUN-SAT 0,7是SUN , - * / ? L C #
特殊字符 表示含义 特殊字符 表示含义
, 枚举 - 区间
* 任意 / 步长(间隔多长时间一次)
? 日/ 星期冲突匹配(选择星期一 就不能用*) L 最后(最后一个周六,最后一个周日)
W 工作日 C 和calendar联系后计算过的值
# 星期 ,4#2,第二个星期四

示例:

@Service
public class scheduledServiceimpl implements scheduledService {
    
    
    /**
     * second(秒),minute (分),hour (时),,day of month(日) month (月),day ofweek (周几)
     *   0 * * * * MON-FRI    周一到周五,每天每时每分 0秒启动一次
     *   0,1,2,3,4 * * * * MON-FRI 周一到周五,每天每时每分 0,1,2,3,4秒启动一次
     *   0-4 * * * * MON-FRI 周一到周五,每天每时每分 0-4 秒 都启动一次
     *   0/4 * * * * MON-FRI 周一到周五,每天每时每分 每隔4秒 都启动一次
     *   [0 0/5 14,18 * * ?]每天14点整,和18点整,每隔5分钟执行- -次
     *  [0 15 10 ? * 1-6] 每个月的周一至周六10:15分执行- -次
     *  [0 0 2 ? * 6L]每个月的最后一个周六凌晨2点执行一次
     *  [0 0 2 LW * ?]每个月的最后一个工作日凌晨2点执行一-次
     *  [0 2-4 ? * 1#1]每个月的第一一个周一凌晨2点到4点期间,每个整点都执行一次;
     */
    //@Scheduled(cron = "0/4 * * ? * FRI")
    @Scheduled(cron = "0/4 * * ? * FRI")
    public void testScheduled() {
    
    
        System.out.println("hello   "+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }
@EnableAsync    //开启注解扫描的异步任务
@EnableScheduling  //开启注解扫描的定时任务
@SpringBootApplication
public class SpringbootTaskApplication {
    
    

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

3. 邮件任务

  • 邮件发送需要引入spring-boot-starter-mail
  • Spring Boot自动配置MailSenderAutoConfiguration
  • 定义MailProperties内容,配置在application.ymI中
  • 自动装配JavaMailSender
  • 测试邮件发送
[email protected]
spring.mail.password=ungigghkclzedcgi
spring.mail.host=smtp.qq.com

需要开启 IMAP 服务,

IMAP是什么?
IMAP,即Internet Message Access Protocol(互联网邮件访问协议),您可以通过这种协议从邮件服务器上获取邮件的信息、下载邮件等。IMAP与POP类似,都是一种邮件获取协议。
# 登录第三方客户端时,密码框请输入“授权码”进行验证。生成授权码
@SpringBootTest
class SpringbootTaskApplicationTests {
    
    

    @Autowired
    JavaMailSenderImpl javaMailSender;

    @Test
    void contextLoads() {
    
     //发送一个普通邮件
        SimpleMailMessage message = new SimpleMailMessage();
        message.setSubject("通知-今晚开会");
        message.setText("今晚 7:30 开会");

        message.setTo("[email protected]");
        message.setFrom("[email protected]");

        javaMailSender.send(message);
    }
    @Test
    void testSendFile() throws  Exception{
    
    
        //1. 创建一个复杂的消息邮件
        MimeMessage message = javaMailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(message,true);

        helper.setSubject("通知-今晚开会");
        helper.setText("源文本","<b style='color:red'> 今晚 7:30 开会 </b>");

        helper.setTo("[email protected]");
        helper.setFrom("[email protected]");
        helper.addAttachment("星河1.jpg",new File("C:\\Users\\thisissirz\\Desktop\\software\\wallpaper\\星河\\星河1.jpg"));
        helper.addAttachment("星河2.jpg",new File("C:\\Users\\thisissirz\\Desktop\\software\\wallpaper\\星河\\星河2.jpg"));
        javaMailSender.send(message);
    }
}

十一、Spring boot 安全

1. 安全

  • 应用程序的两个主要区域是“认证”和"授权”(或者访问控制) 。这两个主要区域是Spring Security的两个目标。
  • “认证”(Authentication) ,是建立一个他声明的主体的过程(一
    个“主体”-般是指用户,设备或一-些可以在你的应用程序中执行动
    作的其他系统)。
  • "授权”(Authorization) 指确定一个主体是否允许在你的应用程序执行一个动作的过程。为了抵达需要授权的店,主体的身份已经有认证过程建立。
  • 这个概念是通用的而不只在Spring Security中。

2. Spring Security

更详细请参考官方文档,官方文档是最好的老师。
pom.xml file

<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity5 -->
<dependency>
   <groupId>org.thymeleaf.extras</groupId>
   <artifactId>thymeleaf-extras-springsecurity5</artifactId>
   <version>3.0.4.RELEASE</version>
</dependency>

config.java

@EnableWebSecurity
public  class WebSecurityConfig {
    
    

    @Bean
    public UserDetailsService userDetailsService() throws Exception {
    
    
        // ensure the passwords are encoded properly
        User.UserBuilder users = User.withDefaultPasswordEncoder();
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(users.username("zhangsan").password("123456").roles("VIP1","VIP2").build());
        manager.createUser(users.username("lisi").password("123456").roles("VIP2","VIP3").build());
        manager.createUser(users.username("wangwu").password("123456").roles("VIP1","VIP3").build());
        //manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build());
        return manager;
    }

    @Configuration
    @Order(1)
    public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
    
    
        protected void configure(HttpSecurity http) throws Exception {
    
    
            http.authorizeRequests(authorize -> authorize
                    .antMatchers("/").permitAll()
                    .antMatchers("/level1/**").hasRole("VIP1")
                    .antMatchers("/level2/**").hasRole("VIP2")
                    .antMatchers("/level3/**").hasRole("VIP3")
                    )
                    .httpBasic(withDefaults());
            //开启自动配置的登陆功能,效果,如果没有登陆,没有权限就会来到登陆页面
            http.authorizeRequests(authorize -> authorize
                    .anyRequest().authenticated()).formLogin(withDefaults());
            //开启自动配置的注销功能。
            http.logout().logoutSuccessUrl("/"); //注销成功会返回到哪个页面
            //1. 访问 /logout 表示用户注销,清空session
            //记住我
            //http.rememberMe(); on the now is error
        }
    }

猜你喜欢

转载自blog.csdn.net/weixin_44961083/article/details/106929327