[前端学java05-SpringBoot实战] 常用注解 + redis实现统计功能

导航

[react] Hooks

[React 从零实践01-后台] 代码分割
[React 从零实践02-后台] 权限控制
[React 从零实践03-后台] 自定义hooks
[React 从零实践04-后台] docker-compose 部署react+egg+nginx+mysql
[React 从零实践05-后台] Gitlab-CI使用Docker自动化部署

[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
[源码-vue02] computed 响应式 - 初始化,访问,更新过程
[源码-vue03] watch 侦听属性 - 初始化和更新
[源码-vue04] Vue.set 和 vm.$set
[源码-vue05] Vue.extend

[源码-vue06] Vue.nextTick 和 vm.$nextTick
[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI

[数据结构和算法01] 二分查找和排序

[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模块化
[深入13] 观察者模式 发布订阅模式 双向数据绑定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手写Promise
[深入20] 手写函数
[深入21] 数据结构和算法 - 二分查找和排序
[深入22] js和v8垃圾回收机制
[深入23] JS设计模式 - 代理,策略,单例

[前端学java01-SpringBoot实战] 环境配置和HelloWorld服务
[前端学java02-SpringBoot实战] mybatis + mysql 实现歌曲增删改查
[前端学java03-SpringBoot实战] lombok,日志,部署
[前端学java04-SpringBoot实战] 静态资源 + 拦截器 + 前后端文件上传
[前端学java05-SpringBoot实战] 常用注解 + redis实现统计功能

(一) 前置知识

(1) 一些单词

security 安全 保护
core 核心
archetype 原型 // create from archetype 从原型创建
conditional 条件的
final 最终 决赛 // 在java中是常量的关键字
framework 框架 架构 // spring framework => spring框架
external 外部的 // external libraries 扩展类库

(2) SpringBoot的默认错误解析

  • src/main/resources/templates/error/4xx.html或5xx.html会被自动解析
    • 1.可以在resources/templates文件下
    • 2.也可以在静态资源文件夹下resources/static|public|resources|META-INF.resources 等
  • 当访问不存在的页面时,会返回templates/error/4xx|5xx页面


    image

(3) java基本数据类型

  • java提供了8中基本类型 四个整数型 两个浮点型 字符型 布尔型
  • 四个整数型
    • byte 8位
    • short 16位
    • int 32位 => 4个Byte
    • long 64位 => 8个Byte
  • 两个浮点型
    • float
    • double
  • 字符型
    • char
  • 布尔型
    • boolean
  • 属性:SIZE TYPE MIN_VALUE MAX_VALUE

(4) java的引用类型

  • 所有引用类型的默认值都是 null
  • 一个引用变量可以用来引用任何与之兼容的类型

(5) 常量 final

  • 常量通常大写
final double PI = 3.1415927;

(6) 自动类型转换

int i = 128;   
byte b = (byte)i;

(7) List 和 ArrayList 的区别

List<SomeObject> myList = new ArrayList<SomeObject>();
ArrayList<SomeObject> myList = new ArrayList<SomeObject>();

区别:
(1) List是一个接口,它没有实现任何属性和方法,在List上如果调用方法,实际上是调用的 ArrayList 的方法
(2) List是一个接口,需要使用实现类,比如ArrayList
(3) 使用方式不同:Array数组使用下标获取元素,List是get
(4) 初始化:Array数组必须指定大小,不灵活。List 可以自己扩充大小,方便

(8) IOC AOP

  • IOC 控制反转/依赖注入 => Bean对象的实例化 Bean对象的创建
  • AOP 面向切面编程 => 动态代理
  • Spring JDBC + 事务
    image

    image

(二) SpringBoot常用注解

(1) @RestController

  • @RestController = @Controller + @ResponseBody
  • 特点
    • 添加该注解后,Controller类中的方法 无法返回页面
    • 相当于在方法上自动加了 @ResponseBody 注解,所以 没办法跳转并传入数据到另一个页面
    • 返会的内容就是 return 的内容

(2) @GetMapping @PostMapping @PutMappiing @DeleteMapping

(3) @PathVariable @CookieValue @RequestHeader @ReqeustAttribute @RequestParam @RequestBody @MatrixVariable

  • @PathVariable 在 RESTful 风格下url中获取路径变量
// 测试 request
// 测试 @PathVariable
// 测试 @CookieValue
// 测试 @RequestHeader => 对比 @RequestParam @RequestBody @RequestPart
// 测试 @RequestAttribute
// 测试 @RequestParam
// 测试 @RequestBody
// 测试URL:http://localhost:7777/car/1/owner/woow_wu7?age=20&city=chongqing
@GetMapping("/car/{id}/owner/{username}")
public Void getPath(
        HttpServletRequest request,
        @PathVariable("id") int id,
        @PathVariable("username") String username,
        @PathVariable Map<String, String> pathVariable,
        // @CookieValue("name") String name,
        @RequestHeader("User-Agent") String userAgent,
        @RequestHeader Map<String, String> headers,
        @RequestParam("age") int age,
        @RequestParam Map<String, String> params
        // @RequestBody String body
        // @RequestAttribute("message") String message
) {
    log.info("@PathVariable('id') => id:{}, username: {}", id, username);
    log.info("@PathVariable Map<String, String> => 可以用一个map对象,接收所有的path变量 => pv: {}", pathVariable);
    String tempId = pathVariable.get("id"); // Map实例有 ( map.get ) ( map.put ) 等方法
    System.out.println(tempId);

    log.info("@RequestHeader('User-Agent') => User-Agent: {}", userAgent);
    log.info("@RequestHeader Map<String, String> => headers: {}", headers);

    log.info("@RequestParam Map<String, String> => params: {}", params);
    // log.info("@CookieValue('name') => name: {}", name);
    // log.info("@RequestBody String body => 可以获取post请求的body,也可以是一个Map实例,比如 @RequestBody Map<String, Object> body ====> body{}", body);

    request.setAttribute("message", "success");
    log.info("request: {}", request.getAttribute("message"));
    return null;
}

(4) @Configuration配置类的注解 + @Bean向容器中注册组件

  • @Configuration => 可以理解为xml中的 beans 标签
    • 作用:是告诉springboot标注的类是一个 配置类
    • 注意点:@Configuration 标注的 配置类本身也是组件
    • 参数:@Configuration(proxyBeanMethods = true)
  • @Bean => 可以理解为xml中的 bean 标签
    • 作用:向容器中添加组件,并且添加的组件是 ( 单实例 )
    • 组件ID:是方法名
    • 返回类型:就是组件的类型
    • 返回值:就是组件在容器中的实例
    • @Bean(组件名) => @Bean()的参数可以重命名组件名,而不是用方法名
(1) 
/src/main/java/com.example.demo/config/PetConfig.java
-------

// 1. 对比参考 UserConfig 类
// 2. @Configuration 标注的类是配置类,配置类本身也是组件
// 3. 外界无论对配置类中的这个注册方法调用多少次获取的都是之前容器中的单实例,前提是 @Configuration(proxyBeanMethods = true
// 4. 如果 @Configuration(proxyBeanMethods = false) 外界调用拿到的就不是 ( 代理对象 ),就 ( 不是单实例 ) 的了
@Configuration // 配置类 => 相当于以前的xml配置文件,xml中有 beans bean 标签
public class PetConfig {
    @Bean // 向容器中注册组件 => @Bean(pet02)这样写可以把注册到容器中的组件重新命名为pet02,而不是用方法名pet01
    public PetBean pet01() {
        return new PetBean("dog", "white");
    }
}
(2) 
问题:如何验证组件已经注册到容器中呢?
回答:在主类中可以查看,因为主类中通过 IOC 可以通过 run.getBeanDefinitionNames() 获取到组件名数组

问题:如果获取到容器中的组件呢?
回答:run.getBean 来获取


具体代码:
// 主程序类,主配置类
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        // 1. 返回 IOC 容器
        // 2. IOC的作用是:控制反转 和 依赖注入
        ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);

        // 3. 查看容器里的组件
        String[] beanDefinitionNames = run.getBeanDefinitionNames();
        for (String name : beanDefinitionNames) {
              System.out.println(name);
        }

        // 3. 从容器中获取组件 userX
        UserBean userX1 = run.getBean("userX", UserBean.class);
        UserBean userX2 = run.getBean("userX", UserBean.class);
        System.out.println("组件:" + (userX1 == userX2));
        System.out.println("上面是true,因为注册的组件默认就是单实例的,因为@Bean给容器注册的组件是单实例的");

        // 3. 从容器中获取组件 pet01
        PetBean pet01 = run.getBean("pet01", PetBean.class);
        System.out.println(pet01.getName()); // 获取pet01对象中的name属性
    }
}

(5) @ConditionalOnBean 条件装配注解

  • @ConditionalOnBean(name) 表示IOC容器中存在 ( name组件 ) 时,才向容器中添加 ( @ConditionalOnBean ) 组件,不存在name就不添加
  • @ConditionalOnBean(name) 如果标注在类上,则表示类里面所有@Bean生效的前提是:容器中有name组件
  • 一般都场景启动器中就有非常多的各种条件注解,条件判断
  • 在IOC中
    • 1.优先解析 @Component,@Service,@Controller,@Mapper等注解类
    • 2.再解析配置类,即@Configuration标注的类
    • 3.最后解析配置类中定义的 @Bean
@Configuration
// @ConditionalOnBean(name = "com.example.demo.bean.ImportBean") 如果标注在类上,则表示里面所有@Bean生效的前提是容器中有com.example.demo.bean.ImportBean组件
public class TestConditionalOnBeanConfig {

    // @ConditionalOnBean(name) 表示IOC容器中存在 ( name组件 ) 时,才向容器中添加 ( @ConditionalOnBean ) 组件,不存在name就不添加
    // @ConditionalOnBean(name) 如果标注在类上,则表示类里面所有@Bean生效的前提是:容器中有name组件
    // - 在IOC中
      // - 1.优先解析 `@Component,@Service,@Controller,@Mapper`等注解类
      // - 2.再解析配置类,即`@Configuration`标注的类
      // - 3.最后解析配置类中定义的 `@Bean`
    @ConditionalOnBean(name = "com.example.demo.bean.ImportBean")
    @Bean("@ConditionalOnBean")
    public TestConditionalOnBeanBean registerConditionalOnBean() {
        return new TestConditionalOnBeanBean("@ConditionalOnBean");
    }
}

(5) @Import 向容器中添加组件

  • @Import 和 @Bean 的作用类似,都是向容器中添加组
  • 语法:@Import({ 类名.class, 类名.class... })
  • 组件名:@Import导入容器中的组件名默认是 ( 全类名 )
  • 注意点:
    • @Import()必须用在组件类上,组件肯定是在springboot IOC容器中的
    • @Import() 比如用在@Config @Controller @Service 等组件上都可以
    • @Import()可以导入第三方包
src/main/java/com.example.demo/config/PetConfig.java
-------

// @Import
// 1. @Import 和 @Bean 的作用类似,都是向容器中添加组
// 2. @Import导入容器中的组件名默认是 ( `全类名` )
@Import({UserBean.class, PetBean.class})
@Configuration(proxyBeanMethods = true) // 配置类 => 相当于以前的xml配置文件,xml中有 beans bean 标签
public class PetConfig {
    @Bean // 向容器中注册组件 => @Bean(pet02)这样写可以把注册到容器中的组件重新命名为pet02,而不是用方法名pet01
    public PetBean pet01() {
        return new PetBean("dog", "white");
    }
}

// 以上打印的话
// @Import向容器添加的组件名是:com.example.demo.bean.PetBean
// @Bean向容器添加的组件名是:pet01

(6) @ImportResource 把传统的xml文件配置的组件添加到容器中

  • @ImportResource("classpath:beans/beans.xml")
    • 参数:classpath:beans/beans.xml 表示的是 配置组件beans.xml的文件路径
    • 具体:beans.xml 被放在了 src/main/resources/beans/beans.xml
(1)
src/main/java/com.example.demo/config/PetConig.java
-------

// @ImportResource
// 1. @ImportResource("classpath:beans/beans.xml")
// 2. 参数:`classpath:beans/beans.xml` 表示的是 `配置组件beans.xml的文件路径`
// 3. 具体:`beans.xml` 被放在了 `src/main/resources/beans/beans.xml` 中
@Import({UserBean.class, PetBean.class})
@Configuration(proxyBeanMethods = true) // 配置类 => 相当于以前的xml配置文件,xml中有 beans bean 标签
@ImportResource("classpath:beans/beans.xml")
public class PetConfig {

    @Bean // 向容器中注册组件 => @Bean(pet02)这样写可以把注册到容器中的组件重新命名为pet02,而不是用方法名pet01
    public PetBean pet01() {
        return new PetBean("dog", "white");
    }
}
(2)
src/main/resources/beans/beans.xml
-------

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
        
    <!--UserBean-->
    <bean id="haha" class="com.example.demo.bean.UserBean">
        <property name="name" value="zhangsan"></property>
        <property name="age" value="20"></property>
    </bean>
    <!--PetBean-->
    <bean id="hehe" class="com.example.demo.bean.PetBean">
        <property name="name" value="dog"></property>
        <property name="color" value="red"></property>
    </bean>
</beans>

(7) @ConfigurationProperties + @Component 实现配置绑定

  • @ConfigurationProperties(prefix = "myapp")
  • @Compoennt的作用是将类组件添加到容器中
(1)
src/main/java/com.example.demo/bean/AppMessageBean.java
-------

// 1. 只有在容器中的组件才能获取 SpringBoot 的强大功能,也就是说要使用@ConfigurationProperties()必须用@Component将对象标记成容器组件
// 2. 该 bean 对象主要是测试 @ConfigurationProperties 和 @Component 两个注解
// 3. 如果类中的属性多于application.yml文件中的myapp的话,多的属性返回的是null
@Data
@Component
@ConfigurationProperties(prefix = "myapp") // prefix="myapp" 这个前缀的值是在 application.yml 文件中配置的
public class AppMessageBean {
    private String name;
    private String email;
    private String author;
    private String other;
}
(2)
src/main/resources/application.yml
-------

myapp:
  # 自定义的配置参数,这里主要用来验证 @ConfigurationProperties 注解的使用
  author: woow_wu7
  name: react-admin-java
  email: woow.wu7@gmail.com
(3)
src/main/java/com.example.demo/controller/TestController.java
-------
    @Autowired
    AppMessageBean appMessageBean;

    // (1)
    // 测试: @ConfigurationProperties 和 @Component 两个注解
    // 教程: https://www.cnblogs.com/jimoer/p/11374229.html
    @GetMapping("/@ConfigurationProperties")
    public AppMessageBean getAuthorName() {
        System.out.println(appMessageBean);
        String author = appMessageBean.getAuthor();
        System.out.println(author);
        return appMessageBean;
    }

(8) @SpringBootTest + @Test 单元测试

  • 1.引入maven场景启动器 spring-boot-starter-test
<!-- spring-boot-starter-test -->
<!-- 单元测试场景启动器 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
  • 2.编译一个测试类
src/test/java/com.example.demo/ApplicationTests.java
-------
@SpringBootTest
class ApplicationTests {
    @Test
    void contextLoads() { dosomething...}
}

(三) 修改maven依赖包的版本号的两种方法

以mysql为例

(1) 在 pom.xml 中通过 version 标签来指定 ( 原理是maven的就近依赖原则 )

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.21</version>
    <scope>runtime</scope>
</dependency>

(2) 在 pom.xml 中通过 properties 标签来修改 ( 原理是maven属性的就近优先原则 )

<properties>
    <java.version>1.8</java.version>
    <!-- 除了修改version还可以在properties中来修改依赖的版本号 -->
    <!-- <mysql.version>8.0.21</mysql.version>  -->
</properties>

(四) 数据访问

(1) mysql驱动 + JDBC数据库连接池 = springboot最基础的操作mysql的方式

  • 在这基础上还可以添加 mybatis 来操作数据库
  • 1.安装:mysql驱动 => mysql-connector-java
  • 2.安装:jdbc数据库连接池 => spring-boot-starter-data-jdbc ( 区分:spring-boot-starter-jdbc )
  • 3.配置项:spring.datasource...
  • 4.数据源:HikariDataSource
spring:
  datasource:
    # 1. 只要装了 ( mysql驱动 ) 和 ( jdbc数据库连接池 ),并且在这里配置好 ( 数据库连接池相关的配置项 ) 就能连接数据库
    # 2. mysql驱动 => mysql-connector-java
    # 3. jdbc连接池 => spring-boot-starter-jdbc 
    # 4. 更进一步:还可以使用 ( Druid数据源 + MyBatis )
    url: jdbc:mysql://localhost:3306/7-react-admin-java?serverTimezone=GMT%2B8&useSSL=false
    username: root
    password: root
    dirver-class-name: com.mysql.cj.jdbc.Driver
  jdbc:
    template:
      query-timeout: 10 # 10s没查出来就超时
  • 4.通过springBoot容器中的 JdbcTemplate 就行操作数据库 ( 单元测试 )
src/test/com.example.demo/ApplicationTests.java
-------

@SpringBootTest
@Slf4j
class ApplicationTests {
    @Autowired
    JdbcTemplate jdbcTemplate; // 自动注入容器中的 JdbcTemplate
    @Test
    void contextLoads() {
        Long aLong = jdbcTemplate.queryForObject("select count(*) from music", Long.class); // 操作数据库
        log.info("music总数据量:{}", aLong);
    }
}

(2) HikariDataSource 和 Druid数据源 => 滤过Druid数据源

(五) Redis

Redis是一个开源的,( 内存中 ) 的 ( 数据结构存储系统 ),它可以用作 ( 数据库 ) ( 缓存 ) ( 消息中间件 )

(1) 导入maven的依赖包,即redis的场景启动器

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

(2) 查看springboot的autoconfigure中的redis的 ( 自动配置类 )

  • 目录:External Libraries/spring-boot-autoconfigure:2.4.2/data/redis/RedisAutoConfiguration.java
  • external 外部的意思
  • external libraries 扩展类库的意思
  • RedisAutoConfiguration.java (1)
    • redis自动配置:
      • @EnableConfigurationProperties(RedisProperties.class) (2)
      • spring.redis.xxx (3)
      • RedisAutoConfiguration.java => @EnableConfigurationProperties(RedisProperties.class) => spring.redis.xxx是对redis的配置,也就是说可以在application.yml中去做redis相关的配置,比如 spring.redis.xxx
    • 连接工厂是准备好的 ( 两种 )
      • Lettuce => LettuceConnectionConfiguration
      • Jedis => JedisConnectionConfiguration
    • 操作redis
      • 自动注入了下面两个组件类
      • RedisTemplate<Object, Object>
        • key: value
        • 对比 JdbcTemplate
      • StringRedisTemplate
        • key 和 value 都是 String

(3) 申请一个阿里云的 redis 服务器,并申请redis公网连接地址,和设置白名单,用户权限(读写)等

image

image

image

image

(4) 下载安装 redis 和 redis客户端

  • 下载连接
  • redis下载安装教程
  • windows系统redis客户端下载连接
  • redis常用命令
  • redis的使用
    • 1.在windows系统上安装完成后,在解压的目录下双击 redis-cli.exe 运行
    • 2.除了双击运行,也可以在该文件夹下打开cmd,输入 ./redis-cli.exe命令
  • redis常用命令
    • 获取读写权限
      • auth username:password
      • auth password
    • 是否禁用redis
      • config set disable [yes|no]
    • 查询默认密码 ( 登陆后才可以操作 )
      • config get requirepass
    • 修改密码 ( 登陆后才可以操作 )
      • config set requirepass 123456
    • 登陆redis
      • ./redis-cli -p 6379 -a 123456
    • 启动服务
      • ./redis-server --service-start
    • 停止服务
      • ./redis-server --service-stop
    • string
      • set key value 设置键值
      • get key 获取键值
      • del key 删除键值
      • mget key1 key2 同时获取
      • exists key 检查key是否存在
    • list => 是链表
      • lpush key value1 value2 在key所关联的list的头部 ( 插入 ) 所有value值
      • rpush key value1 value2 在key所关联的list的头部 ( 插入 ) 所有value值
      • lrange key start end 获取key链表中从start到end的元素的值,start、end可 为负数,若为-1则表示链表尾部的元素
      • lpushx key value 当key存在时才leftpush,如果不加x不存在时会新建
      • lpop key 返回并弹出指定的key关联的链表中的第一个元素,即头部元素
    • set => 是数组加链表
      • sadd key value1 value2 向set中添加数据,如果该key的值已有则不会 重复添加 l`
      • smembers key 获取set中所有的成员
      • scard key 获取set中成员的数量
      • srem key member1、member2 删除set中指定的成员
    • zset
      • zset中的每一个成员都会有一个分 数(score)与之关联,Redis正是通过分数来为集合中的成员进行从小到大的排序
      • zadd key score member score2 member2 将所有成员以及该成员的 分数存放到sorted-set中
    • hash
      • Redis中的Hashes类型可以看成具有String Key和String Value的map容器,所以适合存储值是对象的信息,比如 username
      • hset key fild value 为指定的key设定field/value对(键值对)
      • hgetall key 获取key中的所有filed-vaule
        image

(5) 在application.properties 或 applicatioin.yml中配置 redis

spring:
  redis:
    # 可以通过 (external libraries)/spring-boot-autoconfigure:2.4.2/data/redis/RedisAutoConfiguration/RedisProperties/url 来查看
    # url = redis://user:password@example.com:6379
    host: r-bp1z4zrytbuyv7mkuzpd.redis.rds.aliyuncs.com
    port: 6379
    password: woow_wu7:ALy123456789

(6) 在springboot的测试类中测试 读写redis

src/test/java/com.example.demo/ApplicationTests.java
-------

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    RedisTemplate redisTemplate;
    
    @Test
    void testRedis() {
        ValueOperations<String, String> stringStringValueOperations = stringRedisTemplate.opsForValue();
        stringStringValueOperations.set("redis", "ok"); // string set
        String redis = stringStringValueOperations.get("redis"); // string get
        log.info("redis: {}", redis);
    }
image

(7) 从 Lettuce 切换到 Jedis

  • springboot中默认使用的是 Lettuce,如何切换成 Jedis
(1) 导入依赖
        <!-- jedis -->
        <!-- SpringBoot默认使用的是Lettuce,要切换成jedis就必须安装该依赖 -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
   
(2) 修改配置
  redis:
    # 可以通过 (external libraries)/spring-boot-autoconfigure:2.4.2/data/redis/RedisAutoConfiguration/RedisProperties/url 来查看
    # url = redis://user:password@example.com:6379
    host: r-bp1z4zrytbuyv7mkuzpd.redis.rds.aliyuncs.com
    port: 6379
    password: woow_wu7:ALy123456789
    client-type: jedis #两种,默认是 Lettuce

(3) 测试
    @Autowired
    RedisConnectionFactory redisConnectionFactory;

    @Test
    void testRedis() {
        ValueOperations<String, String> stringStringValueOperations = stringRedisTemplate.opsForValue();
        stringStringValueOperations.set("redis", "ok");
        String redis = stringStringValueOperations.get("redis");
        log.info("redis: {}", redis);

        // 打印 redis 的连接工厂是用的 Lettuce 还是 jedis
        log.info(String.valueOf(redisConnectionFactory.getClass()));
    }

(8) 实现一个小功能 => 统计每个url访问的次数

  • 在springboot连接好redis后
  • 原理
    • 1.通过 Interceptor 拦截除去登陆,静态资源外的所有请求
    • 2.在拦截器中实现 HandlerInterceptor 接口的前置钩子 preHandle 方法
    • 3.在 preHandle 方法中通过获取自动注入的 StringRedisTemplateopsForValue()increment() 方法实现统计
    • 4.redis => 中的 increment(url)方法,key=url, value=计数
    • 5.拦截器通过 @Component 添加到容器中,在配置类中实现 WebMvcConfigurer 接口的 addInterceptors 方法实现拦截
  • 详细步骤
(1)
src/mian/java/com.example.demo/interceptor/RedisUrlCountInteceptor.java
-------

/**
 * Interceptor 和 Filter 都具有相同的功能
 * 区别:
 *  1. Filter: 是Servlet定义的原生组件,好处是脱离spring也能使用
 *  2. Interceptor: 是spring定义的接口,只能在spring中使用,可以使用Spring的 ( 自动装配 ) 等功能
 */
@Component // 将拦截器放到容器中
public class RedisUrlCountInterceptor implements HandlerInterceptor {

    @Autowired
    StringRedisTemplate stringRedisTemplate; // 自动注入操作redis的容器中的组件类

    // preHandle 前置钩子
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String requestURI = request.getRequestURI();
        // stringRedisTemplate.opsForValue().increment(url) => 该方法每次访问该地址,url计数都会+1
        stringRedisTemplate.opsForValue().increment(requestURI);
        return true;
    }
}
(2)
src/main/java/com.example.demo/config/AdminWebConfig.java
-------

/**
 * 拦截器
 * 1. 编写一个拦截器,实现 HandlerInterceptor 接口
 * 2. 把拦截器注册到容器中 ( 实现 WebMvcConfigurer 接口的  addInterceptors 方法)
 * 3. 指定拦截规则 【如果拦截所有,静态资源也会被拦截,可以用 excludePathPatterns 方法放行】
 */
// @Configuration 用于定义 ( 配置类 )
@Configuration
@EnableConfigurationProperties
public class AdminWebConfig implements WebMvcConfigurer {

    // 这里之所以可以自动注入,是因为RedisUrlCountInterceptor类通过@Compoent注册到容器中了,非配置类
    @Autowired
    RedisUrlCountInterceptor redisUrlCountInterceptor;

    // @Override表示被标注的方法是一个重写方法
    // override 覆盖
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(redisUrlCountInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/", "/login", "css/**", "/fonts/**", "/images/**", "/js/**");
    }
}
image

项目源码

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容