[前端学java05-SpringBoot实战] 常用注解 +

2021-10-07  本文已影响0人  woow_wu7

导航

[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的默认错误解析

(3) java基本数据类型

(4) java的引用类型

(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

(二) SpringBoot常用注解

(1) @RestController

(2) @GetMapping @PostMapping @PutMappiing @DeleteMapping

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

// 测试 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向容器中注册组件

(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 条件装配注解

@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 向容器中添加组件

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文件配置的组件添加到容器中

(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 实现配置绑定

(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 单元测试

<!-- spring-boot-starter-test -->
<!-- 单元测试场景启动器 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
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的方式

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没查出来就超时
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的 ( 自动配置类 )

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

image
image
image
image

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

(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

(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访问的次数

(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

项目源码

上一篇下一篇

猜你喜欢

热点阅读