SpringBoot 热部署

2021-06-08 00:00:00 +0000

概述

最近由于团队缺人,做了一个前后端不分离的web项目。因为看过那本《JavaEE开发的颠覆者: Spring Boot实战》,之前在当当也写过一些前端代码,所以理所当然的选了AngularJs1 + BootStrap + SpringBoot作为技术栈来完成任务。说实在的,SpringBoot基本的功能,书里写的还是挺详细的,只是AngularJs和BootStrap如果没有人指导一下,上手还是挺困难的。说起这事儿,还要感谢当时在当当的时候那个带我的小师父。有问必答不厌其烦,且能引导人思考,挺好的人。

写前端代码的时候,有时候想立刻得到调整后的结果,如果仅仅是样式,可以通过浏览器控制台调试。但是涉及到js脚本,除了热部署我还真没想到其他更好的方式,搜了很多网页,最后记录一下亲测好用的方法。

步骤

1 导包

<!--SpringBoot热部署配置 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional><version>2.4.5</version></dependency>

2 配置项

在配置项中增加这一行:spring.devtools.restart.enabled=true

3 IDEA配置

3.1 第一处修改

在这个路径下:Preferences -> Build, Execution, Deployment -> Complier,找到Build project automatically,选中。例子图示如下:
hotDeployConfig1

3.2 第二处修改

Mac系统使用组合键:shift + option + command + /,弹窗中单击Registry,新弹窗勾选compiler.automake.allow.when.app.running
hotDeployConfig2

hotDeployConfig3

redis学习

2021-05-24 00:00:00 +0000

概述

redis在大部分时候,被用作缓存,用于加快查询速度。但其适用范围,不仅仅限于缓存,比如用户关系的存储:求两个用户的共同关注好友。就可以使用redis的列表类型求交集来完成,操作简单、高效。 之前看过一本《redis开发与运维》,里面讲的是如何使用redis,对于redis如何管理(运维)。对于没接触过redis的人,可以通过那本书入门上手。最近在看《redis设计与实现》,这本书里讲的是原理层面的内容,适用于充分理解其解决问题的思路。 我希望通过这篇总结,可以讲清楚,为啥redis这么快,如何实现的。

字符串

redis是用C语言实现的,其基本的5中数据结构中,字符串是最简单的。后续支持的地理位置,出了LBS相关业务,其他的基本用不上。 KV结构中,字符串这种数据结构,redis依托于C语言,有自己的实现:简单动态字符串(simple dynamic string SDS),其数据结构有三个元素:len(字符串长度),free(未使用空间),buf(实际字符数组)。

buf数组,实现仍然是参考C语言,这样可以使用一部分C语言的字符串库函数。也就是说:以\0表示字符串结尾。但是len统计长度的时候,是不包含\0这一字节的。

SDS解决了C语言字符串的两个问题:1 获取字符串长度代价高;2 无法自动扩展;3 无法保存带有\0格式的二进制数据

优势1:获取字符串长度的时候,仅仅访问其len属性即可得到。而C语言的字符串需要遍历,才能获取到。

优势2:防止出现缓冲区溢出。C语言字符串在执行/strcat函数的时候,会把一个字符串B拼接到另一个字符串A的尾部,当A字符串没有足够剩余空间的时候,就会覆盖其他内容。redis字符串首先会检查A字符串剩余空间的长度,如果不够,则为其重新分配充足的内存,以便容纳B字符串。

优势3:可以保存任意格式的二进制数据,而非仅仅文本数据。(就算数据有\0也能保存)

分配空间的时候,使用“预分配”和“惰性释放”两个策略,减少分配内存操作次数,降低耗时。

链表

链表的底层实现用的是/listNode

字典

字典的底层实现用的是/dictht

spring的拦截器和过滤器

2021-04-07 00:00:00 +0000

概述

spring有拦截器有过滤器,对其过滤路径和场景做说明。

过滤器

implements javax.servlet.Filter,然后实现其方法就写好一个过滤器了。注入bean的方式,添加过滤器代码如下。过滤器属于servlet规范,基于函数回调实现,所有请求都可以拦截,比如静态资源图片、jsp、js等。其实现代码如下

    @Bean
    public FilterRegistrationBean crossDomainFilter() {
        FilterRegistrationBean crossDomain = new FilterRegistrationBean();
        crossDomain.setFilter(new CrossDomainFilter());
        crossDomain.setName("cross-domain-filter");
        crossDomain.setOrder(0);
        crossDomain.setUrlPatterns(ImmutableList.of("/*"));
        return crossDomain;
    }

上述代码中,setUrlPatterns参数可以是个正则路径,这里使用/*拦截所有请求,使用/api/*拦截api开头的请求。

拦截器

implements org.springframework.web.servlet.HandlerInterceptor,然后重写各方法。拦截器属于spring规范,基于反射实现,只拦截请求,比如静态资源图片、js等不会拦截。拦截器定义的代码如下

public class LogInterceptor implements HandlerInterceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(LogInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        LOGGER.info("LogInterceptor preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
        LOGGER.info("LogInterceptor postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        LOGGER.info("LogInterceptor afterCompletion");
    }
}

拦截器使用的代码

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class WebsiteApplication implements WebMvcConfigurer {

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

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
    // 拦截器有序 ArrayList
		registry.addInterceptor(new LogInterceptor()).addPathPatterns("/**");
		registry.addInterceptor(new UploadSqlInterceptor()).addPathPatterns("/api/**");
	}
}

拦截路径

addPathPatterns和excludePathPatterns
如果二者都存在,则优先判断exclude,再经过include。其拦截方式源码在拦截器的代码在org.springframework.web.servlet.handler.MappedInterceptor类的matches方法。

拦截器的拦截路径匹配关系与过滤器不同, /*/abd/*表示只拦截该匹配路径。比如/ab/abd/a,不能拦截/abd/a/b /**/abd/**表示拦截匹配路径 及其子路径。比如/abd/a/b

拦截器使用方式

如果想让一个拦截器拦截两个路径,用过只调用一次addInterceptor,并一次性写完拦截路径。如果写两个addInterceptor语句,则容易达不到预期。例子如下。

// 1. 意思是:不能拦截api/v3和api/v4开头的接口。
registry.addInterceptor(loggerInterceptor).excludePathPatterns("/api/v3/**", "/api/v4/**"); 

// 2. 前一句意思是不能拦截api/v3开头的接口,但是可以拦截api/v1、api/v2、api/v4等开头的接口。
//    后一句意思是不能拦截api/v4开头的接口,但是可以拦截api/v1、api/v2、api/v3开头的接口。
//    所以,以下两行代码加起来的意思是,所有接口都能被拦截,且/api/v2/开头的,要以语句定义顺序,被拦截两遍。
registry.addInterceptor(loggerInterceptor).excludePathPatterns("/api/v3/**");
registry.addInterceptor(loggerInterceptor).excludePathPatterns("/api/v4/**");

过滤器和拦截器调用顺序

虚拟机启动时,过滤器初始化方法init调用顺序未知;同理,虚拟机关闭是,过滤器销毁方法destroy调用顺序未知;

接受用户请求时,过滤器和拦截器的调用顺序 sequence

shell相关

2021-04-06 00:00:00 +0000

概述

#!/bin/bash
# 告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell。

:<<!
多行注释...
多行注释...
多行注释...
!

字符串

echo "hi, jiler."
echo "hi, wujunpeng"

# 变量名和等号之间不能有空格
myName="wujunpeng"
echo $myName
echo ${myName}

# 只读变量
readonly myName

# 删除变量(不能删除只读变量)
unset myName

# 字符串长度,效果一样
str="test str"
echo $#str ${#str}

# 截取字符串,从第2位开始,截取1个字符
echo ${str:1:1}

获取参数

# 比如命令行执行为文件 ./start.sh
./start.sh wujunpeng firstPlan
# 在./start.sh内部获取参数时,
echo "执行的文件名:$0";
echo "第一个参数为:$1";
echo "第二个参数为:$2";
# $10 不能获取第十个参数,获取第十个参数需要${10}
$# # 表示参数个数

数组

int_arr=(1 2 3 4 5)
str_arr[0]=My # #!/usr/bin/env zsh 数组下标从1开始
str_arr[1]=name
str_arr[2]=is
str_arr[3]=wujunpeng

echo $int_arr[1]

echo "数组的元素为: ${int_arr[*]}"
echo "数组的元素为: ${int_arr[@]}"
length=${#int_arr[*]} 获取数组长度
length=${#int_arr[@]} 获取数组长度

置换

# 变量置换
word=replace
echo ${param}
echo ${param:-${word}} # 如果param为空 或 未赋值,用word的内容取代param,但param不变
echo ${param:=word} # 如果param为空 或 未赋值,用word赋值给param
echo ${param:+word} # 如果param有值,用word取代param,但param不变
# 命令置换
DATE=`date`
echo ${DATE}
# 算式置换
echo $((1+2))

if条件

a=10
b=20
if [ $a == $b ]
then
   echo "a 等于 b"
fi
if [ $a != $b ]
then
   echo "a 不等于 b"
fi
# 写成一行
if [ $a == $b ]; then echo "a 等于 b"; fi

# if后的可以使用其他的语法,判断数据是否相等
if [ $a -eq $b ] # 判断相等equals
if [ $a -ne $b ] # 判断不相等 not equals
if [ $a -gt $b ] # 判断> greater than
if [ $a -lt $b ] # 判断< less than
if [ $a -ge $b ] # 判断>= greater or equals
if [ $a -le $b ] # 判断<= less or equals
# 与或非 -a -o !,直接用&& 和 || 也支持
[ $a -lt 20 -a $b -gt 100 ] # if (a < 20 && b > 100)
[ $a -lt 20 -o $b -gt 100 ] # if (a < 20 || b > 100)

# 可以判断字符串是否为空
if [ -z $str ] # 检查字符串长度是否为0,如果为0则返回true
if [ -n $str ] # 检查字符串长度是否不为0,如果不为0则返回true
if [ $str ] # 字符串为空返回true

# 可以判断文件类型
if [ -f $fileAddress ] # 如果是普通文件(不是目录,不是设备文件)返回true
if [ -r $fileAddress ] # 检查文件是否可读;如果检查可写用-w,可执行用-x
if [ -e $fileAddress ] # 检查文件是否存在,存在返回true

test

if test -e $str # 如果文件存在

循环

for

for each in 1 2 3 4 5
do
	echo $each
done
# for循环遍历数组
int_arr=(1 2 3 4 5)
for each in ${int_arr[@]}
do
	echo $each
done
# for循环遍历带空格的元素
name_arr[0]=jiler
name_arr[1]="jiler hell"
SAVEIFS=$IFS
IFS=$(echo -en "\n\b")
for each in ${name_arr[@]}
do
	echo ${each}
done
IFS=$SAVEIFS

until

a=0
until [ ! $a -lt 10 ]
do
   echo $a
   a=`expr $a + 1`
done

case switch

case $aNum in
    1)  echo '你选择了 1'
    ;;
    2)  echo '你选择了 2'
    ;;
    3)  echo '你选择了 3'
    ;;
    4)  echo '你选择了 4'
    ;;
    *)  echo '你没有输入 1 到 4 之间的数字'
    ;;
esac

seq序列遍历

基本语法:
1 seq [选项]… 尾数
2 seq [选项]… 首数 尾数
3 seq [选项]… 首数 增量 尾数

seq -s '#' 5 # 指定分隔符,横向输出 1#2#3#4#5
seq -s ' ' 10 # 空格作为分隔符
seq -w 1 10 # 前面补0,输出1-10
seq 1 2 10 # 从1开始,增量为2,不超过10
seq -f "%03g" 98 101 # 宽度为3,不足补0

重定向

# 输出 重定向 到test.sh文件
echo "wujunpeng" > test.sh

# 输入 重定向 到test.sh文件
command < test.sh
# 重定向输出,追加
echo "wujunpeng" >> test.sh
# 重定向输入
file < test.sh
# 将标准输出和标准错误输出 以追加的方式 重定向 到文件file
command >> file 2>&1

文件包含

# 类似于include import等,可通过两种方式
source ./test1.sh
. ./test1.sh

awk 命令 行处理

处理文本文件的语言,是一个强大的文本分析工具

awk '{print $1,$4}' log.txt # 打印出 log.txt文件 每行按空格分隔的第一个和第四个元素
awk -F, '{print $1,$2}'   log.txt # 打印出 log.txt文件 每行按逗号分隔的第一个和第四个元素
awk -F '[ ,]' '{print $1,$2,$5}' log.txt # 打印出 log.txt文件 每行按先按空格,再按逗号分隔的第一个和第四个元素
awk -va=1 '{print $1,$1+a}' log.txt # 设置变量a=1,如果$1是数字,则是加法;如果$1是字符串,则连接
awk '$2 ~ /th/ {print $2,$4}' log.txt # 第二列包含th的行,打印出第二例和第四列,~表示正则匹配
awk '/re/ ' log.txt # 输出包含re的行
awk '$2 !~ /th/ ' log.txt # 输出第二列不包含th的行
awk '!/re/ ' log.txt # 输出不包含re的行

awk -f cal.awk score.txt # cal.awk内容详见附录

sed 命令

用脚本来处理文本文件,用来自动编辑一个或多个文件、简化对文件的反复操作、编写转换程序等。具体就不写例子了,目前其应用场景未知。

简单应用

# bc 命令
echo 'scale=2; (2.777 - 1.4744)/1' | bc # 保留两位小数进行计算
echo "obase=2;8" | bc # 十进制转二进制
echo "ibase=2;111" | bc # 二进制转十进制
echo "obase=10;ibase=2;1101" | bc # 二进制转十进制
echo "sqrt(100)" | bc # 开平方

# nohup 命令 后台运行,比如java的发布就可以用
nohup java -jar *.jar

# wc 命令
wc -l Hello.java # 统计文件中的行数 -w表示字数 -c表示字节数

# scp 命令
scp /user/wujunpeng/web/test.java wujunpeng@192.168.0.23:~/Desktop
# 如果是复制目录则用-r (-C 表示允许压缩 -P 4588表示指定端口)
scp -r /user/wujunpeng/web/test wujunpeng@192.168.0.23:~/Desktop


# xargs 命令 给命令传递参数的过滤器,也是组合多个命令的工具
seq -f "dir%03g" 1 3 | xargs mkdir # 联系创建多个目录

# curl http命令行工具
curl -o /dev/null -s -w %{http_code} https://www.baidu.com # 获取访问百度的返回值

# ps 命令
ps -ef | grep java

# 用mac电脑给手机充电的时候,有时候会出现频繁断开又链接的情况,用下面这个命令可以解决这个问题
sudo killall -STOP -c usbd

# nslookup 查询机器对应的域名
nslookup 10.3.43.4

# 端口被占用
lsof -i :8080


附录

awk文件内容

#!/bin/awk -f
#运行前
BEGIN {
    math = 0
    english = 0
    computer = 0

    printf "NAME    NO.   MATH  ENGLISH  COMPUTER   TOTAL\n"
    printf "---------------------------------------------\n"
}
#运行中
{
    math+=$3
    english+=$4
    computer+=$5
    printf "%-6s %-6s %4d %8d %8d %8d\n", $1, $2, $3,$4,$5, $3+$4+$5
}
#运行后
END {
    printf "---------------------------------------------\n"
    printf "  TOTAL:%10d %8d %8d \n", math, english, computer
    printf "AVERAGE:%10.2f %8.2f %8.2f\n", math/NR, english/NR, computer/NR
}

java 虚拟机相关

2021-04-03 00:00:00 +0000

概述

关于java虚拟机,学过好几遍,但学一遍忘一遍。而我这个人,经常被人说“记忆力”好,比如我喜欢看电视剧,台词都记得。其实并不是我记忆力有多么的好,而是经常重复。前几天忽然想明白,我是如何记住那些电视剧台词的。首先要有兴趣记,兴趣这个事儿吧,是可以有的。只要你愿意对一件事培养兴趣,是能产生兴趣的,关键是你不愿意。而工作技能知识的学习,是需要有兴趣的,要有探索新知识的能力。再就是重复,重复次数多了,自然就能记住,比如快排算法,每天看一遍,默写一遍,总是能记住的。最后我能想到的一点是,把你想记住的内容,与你已经产生浓厚兴趣的其他事情建立联系。知识联系起来,就容易记住了。

虚拟机一些参数

# 设置堆内存,最小内存Xms,最大内存 Xmx。最大最小一样,可以避免堆自动扩展
-Xmx4g -Xms4g
# 设置方法区大小
-XX:PermSize=10m -XX:MaxPermSize=10m

对象是否存活

虚拟机判断可以回收已经不被使用的对象,书上介绍了两种判断方法。

  1. 引用计数法
    有引用时,计数器加1;引用失效,计数器减1。优点是实现简单,判定效率高。问题是:无法解决循环引用的问题。
  2. 可达性分析(Reachability Analysis)
    基本思想是以可以作为GC Roots的对象作为起点,向下搜索,搜索路径成为引用链(Reference Chain)。当一个对象不被任何一个GC Roots对象引用,就可以被回收。

可以作为GC Roots的对象有如下几种情况:

  1. 虚拟机栈中引用的对象
  2. 方法区中 类静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈中JNI(Native方法)引用的对象

四种引用类型,由强到弱依次是:

  1. 强引用( Strong Reference )
    普遍存在的类似于Object obj = new Object()就是强引用
  2. 软引用( Soft Reference)
    描述一下有用 但非必须的对象。在系统将要发生内存溢出之前,会把这些对象列入回收范围内,进行第二次回收。
  3. 弱引用( Week Reference )
    也是描述非必须的对象,这些对象只能生存到下一次垃圾回收之前。
  4. 虚引用( Phantom Reference )
    一个对象是否有虚引用存在,不影响生存时间。也无法通过虚引用获取一个对象实例。设置虚引用的目的是:在这个对象被回收的时候,收到一个系统通知

对象实例回收过程:
一个对象被回收要经历两次标记过程:一是经过可达性分析后,没有与GC Roots相关的引用,这时,对象会被第一次标记,并判断是否需要筛选(执行其finalize方法);二是再次标记,则会被回收。如果第二次标记时,对象被重新引用,则可以逃过被回收。

类的回收:
当一个类被判断为无用类时,可以被回收。判断条件比较苛刻,必须同时满足三个条件。一是该类所有实例对象已经被回收;二是加载改类的ClassLoader被回收;三是该类的java.lang.Class对象没有在任何地方被引用,无法通过反射访问该方法。