首页 零碎笔记
文章
取消

零碎笔记

反射

clazz.getDeclaredConstructor().newInstance() 优于 clazz.newInstance()

需要注意的是,如果你想调用的构造函数是 public 的,那么可以直接使用 clazz.newInstance(args) 来创建实例,它与 clazz.getDeclaredConstructor().newInstance(args) 的效果是一样的。但是如果构造函数是 protected、private 或默认的(即没有修饰符),那么就必须使用 getDeclaredConstructor() 方法来获取构造函数,否则会抛出 NoSuchMethodException 异常。

Java 8

LocalDateTime/LocalDate 加减运算

1
2
3
4
5
6
7
8
// 在 Java 8 中,可以使用 plus 和 minus 方法实现 LocalDateTime 和 LocalDate 的加减运算。
// 例如,计算 7 天后的 LocalDateTime,可以使用以下代码:
LocalDateTime now = LocalDateTime.now();
LocalDateTime later = now.plusDays(7);

// 类似地,如果要计算 7 天前的 LocalDate,可以使用以下代码:
LocalDate today = LocalDate.now();
LocalDate before = today.minusDays(7);

除了 plusDays() 和 minusDays(),还可以使用类似的方法 plusHours()、plusMinutes()、plusSeconds()等,来进行加减运算。

Java 并发

什么叫做线程自旋

很多synchronized里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然synchronized里面的代码执行地非常快,不妨让等待锁的线程不要被阻塞,而是在synchronized的边界做忙循环,这就是自旋。如果做了多次忙循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略。

线程阻塞涉及到用户态和内核态切换

阻塞线程会释放cpu资源,因而会由内核态切换到用户态,当需要抢占cpu资源的时候就会进入内核态

Linux

curl命令 curl 是常用的命令行工具,用来请求 Web 服务器。它的名字就是客户端(client)的 URL 工具的意思。它的功能非常强大,命令行参数多达几十种。如果熟练的话,完全可以取代 Postman 这一类的图形界面工具。

进入用户主目录

cd ~

获取root权限

su,然后输入密码;exit,退出root

切换到更目录

cd /

Linux中的隐藏文件默认以’.’开头

请求方式和postman

  1. postman发送Ajax——post请求:headers设置:X-Requested-With:XMLHttpRequest,Content-Type:application/x-www-form-urlencoded,在x-www-form-urlencoded中填写数据

系统设计

  1. 业务往往伴随着参数校验

后端与数据库交互

  1. 数据库的日期类型是date,后端参数为String时也可以存入,取数据的时候,也可以直接赋值给String,范围查询也可以直接使用日期字符串作为参数,总结似乎存在一个转换器能自动将String转换成数据库类型Date,和将数据库类型Date转换成String,同时月或日只有一位时,数据库会自动补零

日期的格式化

1
2
3
4
5
6
7
8
// SpringBoot提供的日期格式化注解,经测试这个不起作用
@DateTimeFormat

// jackson提供的注解,如果前后端传的数据都是json格式,那么后台接数据,传数据都可以用@JsonFormat ;
@JsonFormat

// fastjson提供的注解,兼具上面两个注解的功能
@JSONField

目前这三个注解还没测试

数据库sql

  1. default约束不能使用函数
  2. enum枚举类型的用法,字段名 enum('0','1');,枚举类型不能够起到严格的校验,,但可以保证不会有范围外的数据入库,可参考博文: enum的使用
  3. from后面的派生表必须有别名,否则报错,像下面这段:
1
2
3
4
5
6
7
8
delete from Person where id not in
(
    select t.id from # from后面的派生表必须有别名,否则报错
    (
        select min(id) as id from Person group by Email
    ) t
)

Spring事务

  1. 悲观锁:数据库的默认是心啊
    • 共享锁:A事务加了共享锁,其他事务也只能加共享锁,能够读但不能改
    • 排他锁:A事务加了排他锁,其他事务不能加任何锁,所以既不能读也不能写
  2. 乐观锁
    • 在更新数据前,判断他人是否修改,如果修改放弃更新否则提交
    • 通常通过版本号判断数据是否修改,如果被修改版本号+1

传播机制

传播机制

编程式事务

编程式事务

Spring邮件服务

  1. Spring Boot application.yml邮件服务配置:

邮件服务配置

Spring参数校验

  1. @Validated和@Valid只会在父方法中生效,在实现方法中覆盖方法的参数校验规则会失败且会报错:javax.validation.ConstraintDeclarationException: HV000151: A method overriding another method must not redefine the parameter constraint configuration,所以写参数校验时应该在父方法中定义配置,子类继承就好了,不用修改
  2. 在Service中使用参数校验,需要用@Validated和@Valid组合使用,@Validated的作用是:提示Spring为组件开启数据校验功能,相比与@Valid,Validated能为@Controller、@Service、@Component等标注的组件开启数据校验功能、而@Valid只能为Controller组件开启数据校验,但是@Validated不支持嵌套验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import com.flameking.community.entity.User;
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
/**
 * service接口
 */
@Validated
public interface UserService {

  /**
   * 根据用户id查询用户信息
   * @param id
   * @return 帖子对应的发帖人的信息
   */
  User selectUserById(Integer id);

  /**
   * 注册用户信息
   * @param user
   * @return
   */
  int register(@Valid User user);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.util.Date;

@Data
public class User {
    private Integer id;
    @NotBlank
    private String username;
    @NotBlank
    private String password;
    private String salt;
    @NotBlank
    private String email;
    private Integer type;
    private Integer status;
    private String activationCode;
    private String headerUrl;
    private Date createTime;
}

Spring Boot测试配置

  1. SpringBoot2.4.x之后,改为默认仅集成JUnit5,干掉了兼容JUnit4,所以如果想使用Junit4可以选择使用低版本的(低版本同时兼容Junit5和Junit4),当然也可以直接引入Junit4的依赖

Spring Boot数据库配置

  1. 数据库配置问题:java.sql.SQLException: No timezone mapping entry for 'HongKong',将其修改为serverTimezone=Asia/Shanghai
  2. 数据库serverTimezone配置的不一样可能会使得数据库日期类查询与我们当前时间有差别,比如可能差8个小时,但我们一般设置为serverTimezone=Asia/Shanghai是没有问题的,另外MySQL8.0+版本不写serverTimezone好像会报错,虽然我用的是5.7.33

Spring Boot Bean的问题

  1. IDEA,注入的mapper类总是报错:
    Mapper注入

Spring Boot mybatis配置的问题

type-aliases-package的作用:
type-aliases-package

sql 输出日志配置:

1
2
3
4
5
# 如果是 mybatis,mybatis-plus => mybatis
mybatis-plus:
  mapper-locations: classpath:/mapper/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

Spring Boot Thymeleaf引擎

  1. Thymeleaf命名空间:xmlns:th=”http://www.thymeleaf.org”
  2. Thymeleaf的公共页面复用:

复用公共部分

注意:复用的原理是直接用公共部分替换原来的元素,所以其实原来的元素标签与复用的公共部分标签不同也没问题,样式不同也没问题,因为最后都会替换成公共部分

Spring Boot 日志

java web 下有好几种日志框架,比如:logback,log4j,log4j2(slj4f 并不是一种日志框架,它相当于定义了规范,实现了这个规范的日志框架就能够用 slj4f 调用)。其中性能最高的应该是 logback 了,而且 springboot 默认使用的也是 logback 日志,在application.yml中我们可以定义日志:

日志配置

在application中我们只能对日志进行简单的配置,实际业务需求中我们可能需要:将不同级别的日志放在不同的文件中,每个项目的日志放在项目文件下;要进行这样复杂的配置就需要使用到xml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <contextName>community</contextName>
    <property name="LOG_PATH" value="E:/log/"/>
    <property name="APPDIR" value="community"/> <!--日志最终会被存在D:/work/data/community下-->

    <!-- error file -->
    <appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/${APPDIR}/log_error.log</file>                                        <!--该日志命名为log_error.log-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/${APPDIR}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>          <!--新生成日志存放的地址-->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>5MB</maxFileSize>                      <!--日志内容最大不能超过5MB,否则会另外生成新的文件存储-->
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>30</maxHistory>                              <!--日志文件的最大存储期限是30天,超过会自动删除-->
        </rollingPolicy>
        <append>true</append>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%d %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>                 <!--日志输出格式-->
            <charset>utf-8</charset>                                                                <!--日志字符集utf-8支持中文-->
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">                                  <!-- 在 appender 中设置 filter 子节点,在默认级别上再此过滤,配置 onMatch,onMismatch 可实现只输出单个级别,当前配置只会在文件中输出 error 级别的日志-->
            <level>error</level>                                                                    <!--过滤级别是error-->
            <onMatch>ACCEPT</onMatch>                                                               <!--如果匹配就接受否则拒绝-->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- warn file -->
    <appender name="FILE_WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/${APPDIR}/log_warn.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/${APPDIR}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>5MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <append>true</append>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%d %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- info file -->
    <appender name="FILE_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/${APPDIR}/log_info.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/${APPDIR}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>5MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <append>true</append>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%d %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- console -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>debug</level>
        </filter>
    </appender>

    <logger name="com.flameking.community" level="debug"/>                       <!--com.nowcoder.community包下的日志级别是debug,sql 语句的打印结果是 debug 级别,所以这里 level 如果设置 debug 以上的级别,如 info,就不会显示结果,只会有 sql 语句-->

    <root level="info">                                                         <!--整个项目下所有的jar包日志级别是info,低于info的debug、trace都不会输出-->
        <appender-ref ref="FILE_ERROR"/>
        <appender-ref ref="FILE_WARN"/>
        <appender-ref ref="FILE_INFO"/>                                         <!--日志级别从大到小:error、warn、info、debug、trace-->
        <appender-ref ref="STDOUT"/>
    </root>

</configuration>

首先这个文件是被Spring Boot自动识别的,我们只需要将其放在resources文件夹下,并将其命名为logback-spring.xml,当然根据不同的环境这些命名都是可以的 logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy,其中 logback-spring.xml、logback.xml 在 Spring Boot 环境的下都是可以的,然后在代码中我们就可以使用 @slf4j 来打印日志了

当然也可自定义命名,想要自定义文件名可配置:logging.config 指定配置文件名:

1
2
logging:
  config: classpath:logging-config.xml

像上面我们的自定义命名是:logging-config.xml

什么时候改打印什么日志

程序中什么时候打印什么级别的日志

logback.xml 获取到 spring boot yaml 文件中的配置

SpringMVC转发和重定向

  1. 我们在使用重定向的时候往往是为了解耦两个不相关的业务,比如登录和注册、删除记录和查询记录
  2. 转发关键词:forward,重定向:redirect
  3. 两个不同类型的请求互相转发会报错
  4. 请求转发后存入model的参数在目标请求的model中是不存在的,此时需要使用@RequestAttribute注解获取对应的参数
  5. 请求转发后依然会进入拦截器

SpringMVC请求注解

  1. @RequestBody,这个是专门用来接收前端json数据的,这些数据放在请求体中

SpringMVC接收参数

  1. 对于checkbox的输入框,在是否选择时,如果后端使用Boolean类型接收,当没选中时,Boolean接收值总是null,而用boolean就没问题
  2. application/x-www-form-urlencoded 的参数,后端使用 JavaBean 接收会出现属性值为空的情况,这时推荐直接用 @RequestParam 配合属性参数接收,而不是 Javabean,同时也可使用原生的 HttpServletRequest 进行接收,好像还可以使用 Map(paramMap) 类型进行接收。

SpringMVC拦截器

  1. 如果任何一个拦截器的preHandle方法返回false或者抛出异常,或者handler方法中抛出异常都不会执行postHandle方法。

mybatis,mapper.xml和mapper.java需要注意的问题

使用注解@Param的时机:

  1. 在动态SQL中用到了参数作为判断条件,就算SQL对应的的方法有且只有这一个参数,如果不加@Param就会报错,比如使用 if 和 when 动态参数
1
2
3
4
5
6
<select id="getUserById" resultType="com.flameking.community.User">
    select * from user
    <if test="id!=null">
        where id=#{id}
    </if>
</select>
1
2
3
4
@Mapper
public interface UserMapper {
 List<User> getUserById(@Param("id")Integer id);
}
  1. 需要为参数指定别名
  2. 有多个参数时
  3. mapper.xml中的SQL使用了$,那么参数中也需要@Param注解,$ 会有注入漏洞的问题,但是有的时候你不得不使用$ 符号,例如要传入列名或者表名的时候,这个时候必须要添加 @Param 注解,例如:
1
2
3
4
@Mapper
public interface UserMapper {
 List<User> getAllUsers(@Param("order_by")String order_by);
}
1
2
3
4
5
6
<select id="getAllUsers" resultType="com.flameking.community.User">
    select * from user
 <if test="order_by!=null and order_by!=''">
        order by ${order_by} desc
 </if>
</select>

ajax请求

1
2
3
4
5
6
7
var json
const target = $.getJSON("/power/electron/" + getDateByOffset(offset), (res)=>{
    console.log(res)    //这里是可以得到JSon数据的
    json = res
})
console.log(json)   //但是就算再回调函数中赋值了json,函数外输出时值依然是undefined,因为函数里面的数据是异步加载的
json = target.responseJSon

因为ajax异步加载的原因,responsJSon我们再函数之外是获取不到的
关闭异步的方法:

1
$.ajaxSettings.async = false;

但是执行过后记得重新开启起步$.ajaxSettings.async = true;,避免影响到其他的ajax异步请求

前缀树过滤敏感词

前缀树

前缀树的特点

  1. 根节点为null
  2. 除了根节点其余节点只存储一个字符,且每个子节点包含的字符互不相同,如果相同则合并
  3. 根节点的子节点到叶节点连起来的字符是一个敏感词
  4. 会标记节点是否为叶节点
  5. 存在三个指针、指针一遍历前缀树,指针2和指针3作为开始指针和结尾指针遍历字符串

前缀树为什么块

某种意义上树的分支越多,查询效率越快

Redis

  1. key只允许String类型
  2. 基本数据类型:String、Hash、List、Set、SortedSet
  3. Redis为什么这么快:把所有数据存储在内存中,内存操作远比磁盘IO快,采用的IO多路复用技术(多个连接复用同一个线程)
  4. 数据持久化:
    • 快照(RDB):一次性把数据存入磁盘、适合数据体积小,以及每小时备份一次
    • AOF,存储操作日志,并且对操作日志进行追加存,缺点比较占用空间,数据回复比较慢(取出日志文件并重新执行一遍,比从磁盘中直接取出数据要慢),优点实时较号(因为存日志的操作比较快)
  5. Redis学习简单使用简单(主要学会命令操作)
  6. 常见应用场景:缓存、排行榜、计数器、社交网络(点赞、点踩、关注、收藏)
  7. SortedSet可以使用Range进行分页查询

Vue

计算属性computed

推荐使用计算属性来描述依赖响应式状态的复杂逻辑,我们在计算属性中定义某个方法A,这个方法里面就是复杂的计算逻辑,使用的时候就像调用属性一样直接就行了,注意不用加()

占位符

特指router中的path定义:/profile/:username,即:username就是占位符,是用来传参的

beforeRouteEnter

beforeRouteEnter 函数内部的 this 为 undefined,这是因为 beforeRouteEnter 是在页面创建前就执行的,先执行 beforeRouteEnter,再执行生命周期钩子函数 beforeCreate、created 等。

beforeRouteEnter 有三个参数,to、from、next to:里面是当前页面的路由对象。 from:里面是上一个页面的路由对象。 next:表示进入当前页面,beforeRouteEnter 内必须执行 next() ,否则无法进入页面,可以传入参数 vm 访问组件实例,相当于 this

beforeRouteUpdate

使用场景:组件复用,路由跳转;

1
2
3
4
5
6
beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用(即路由参数不同)
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
},

beforeRouteLeave

1
2
3
4
beforeRouteLeave (to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
}

全局事件总线

this.$bus.$emit('方法名', data),其中data是传递的数据,这个方法会触发某个组件绑定在bus上的方法,然后该组件的方法就会在组件内部触发执行 this.$bus.$on('方法名', this.method),第二个参数就是组件在method中定义的方法,第一个参数就是emit触发方法时传递的第一个参数

本文由作者按照 CC BY 4.0 进行授权