• 主页
  • 标签
所有文章 关于我

  • 主页
  • 标签

linux

2021-04-07

学习Linux目标:掌握linux常用命令,会安装开发中使用软件,例如mysql,jdk…

开发人员掌握就好,更多的是运维人员需要掌握

Linux介绍

Linux是基于Unix开发的,Linux是一种自由和开放源码的操作系统,存在着许多不同的Linux版本,但它们都使用了Linux内核

Unix是一个强大的多用户、多任务操作系统。 于1969年在AT&T的贝尔实验室开发。 UNIX的商标权由国际开放标准组织(The Open Group)所拥有。 UNIX操作系统是商业版,需要收费,价格比Microsoft Windows正版要贵一些

Linux可安装在各种计算机硬件设备中,比如手机、平板电脑、路由器、台式计算机

Linux系统的应用

服务器系统Web应用服务器、数据库服务器、接口服务器、DNS、FTP等等;

嵌入式系统路由器、防火墙、手机、PDA、IP 分享器、交换器、家电用品的微电脑控制器等等,

高性能运算、计算密集型应用Linux有强大的运算能力。

桌面应用系统 ubuntu

移动手持系统

Linux版本:*内核版本和发行版本*

内核版本是指在Linus领导下的内核小组开发维护的系统内核的版本号,非常小,只有几M

​ 常用的ubuntu centos fedora redhat

img

*Linux*****安装****

我们在虚拟机上来安装Linux

虚拟机:一台虚拟的电脑

虚拟机软件:

VMWare它是一个收费虚拟软件,密钥

VirtualBox :免费的

怎样安装linux系统(centos6.5):

参考《虚拟软件vmware安装.doc》,安装VmWare

参考《CentOS6详细安装文档.doc》,安装CentOS

前提需要一个.iso的镜像文件

img

典型安装就可以

imgimgimgimg

需要指定iso文件的位置

img

img

进入安装,选择skip(ctrl+alt可以将鼠标的光标跳出虚拟机)

img

img

时区,亚洲上海

img

img

选择basic server是安装为服务,只有dos命令,便于远程连接访问。如果是desktop桌面版的话不灵活且安装的内容会很多

慢慢等待安装完成,重新引导(重启)

登录时,使用的用户是root,录入密码时不会显示(其实已经输入了),显示第三行的内容为登录成功

img

很少会直接登录,一般都是远程访问

*Linux远程访问*

使用CRT软件打开

新建一个连接,快速连接

img

需要录入linux的ip地址及用户名密码,连接成功后可以在选项中设置窗口颜色方案,变为黑白的这种经典窗口

在虚拟机中查看ip地址 ,输入命令 ifconfig

img

注意事项:

1.关于linux安装时失败问题,有可能是硬件虚拟化没有打开,需要在bios中开启虚拟化。通过 img可以查看

2.关于CRT安装与破解(参考图片)

*Linux目录结构*

Linux系统它是文件系统

它的根目录 是”/”,是以树型结构来管理

img

Root用户登录后,显示时有一个~,它其实代表的就是root目录

通过命令cd.. 就会放回上层的根目录 /]#

通过命令ls 可以查看所有的文件名

bin boot cgroup dev etc home lib lost found media misc mnt net opt proc root sbin selinux srv sys tmp usr var

img

root管理员的home目录root

其他用户的home目录home目录中

我们可以将我们自己的文件安装在任意位置

*Linux常用命令(重点)*

1.切换目录命令 *cd* (这个命令非常重要)

cd app 切换到app目录 cd .. 切换到上一层目录 cd / 切换到系统根目录 cd ~ 切换到用户主目录 root cd - 切换到上一次执行时所在的目录

2.列出文件列表 *ls ll* (这个命令非常重要)

ls(list)是一个非常有用的命令,用来显示当前目录下的内容。配合参数的使用,能以不同的方式显示目录内容。

dir命令很少用

格式:ls [参数] [路径或文件名] ls –help 可以帮助我们查看帮助信息,都有哪些命令可以执行

可能会出现中文乱码的问题,可以在选项中选择编码为UTF-8

ls -a 显示所有文件或目录(包含隐藏的文件)

ls -l 使用较长格式列出信息(详细信息),缩写成ll

TIPS:在linux中以 . 开头的文件都是隐藏的文件

3.创建与删除目录(注意权限)

可以mkdir –help查看可用命令

mkdir(make directory)命令可用来创建子目录:mkdir app è 在当前目录下创建app目录mkdir –p app2/test è 级联创建aap2以及test目录

rmdir(remove directory)命令可用来删除“空”的子目录: rmdir app è 删除app目录

当用rmdir 删除非空目录时会显示失败

4.浏览文件

cat操作:cat用于显示文件的内容。

格式:cat [参数] <文件名>

img

more操作:more一般用于要显示的内容会超过一个画面长度的情况。

按空格键显示下一个画面,按回车键显示下一行内容,按 q 键退出查看

img

less操作:用法和more类似,不同的是less可以通过PgUp、PgDn键来控制

img

tail操作:tail命令是在实际使用过程中使用非常多的一个命令,它的功能是:用于显示文件后几行的内容

用法:

tail -10 /etc/passwd è 查看后10行数据

tail -f catalina.log è *动态查看日志*(非常重要)

ctrl+c 结束查看

5.文件操作

cp操作:是copy操作

cp(copy)命令可以将文件从一处复制到另一处。一般在使用cp命令时将一个文件复制成另一个文件或复制到某目录时,需要指定源文件名与目标文件名或目录。

cp a.txt b.txt è 将a.txt复制为b.txt文件

cp a.txt ../ è 将a.txt文件复制到上一层目录中

cp a.txt /a/b.txt 将a.txt文件复制到a目录下的b.txt文件中(如果没有这个文件会创建)

mv操作:是move相当于剪切

mv 移动或者重命名

mv a.txt ../ è 将a.txt文件移动到上一层目录中

mv a.txt b.txt è 将a.txt文件重命名为b.txt

rm操作:它可以帮助我们删除文件与目录

rm 删除文件

用法:rm [选项] 文件

rm a.txt è 删除a.txt文件 (删除需要用户确认,y/n)

rm 删除不需要询问

rm -f a.txt è 不询问,直接删除

rm 删除目录

rm -r a è 递归删除 (删除需要用户递归确认,y/n)

不询问递归删除(慎用)

rm -rf a è 不询问递归删除

rm -rf * è 删除所有文件

rm -rf /* è 自杀

****tar操作****:打包压缩与解压(打包或解压操作时常用)

ls或者ll命令下查看:d开头的一般是一个目录,r开头的是一个文件

tar命令位于/bin目录下,它能够将用户所指定的文件或目录打包成一个文件,但不做压缩。一般Linux上常用的压缩方式是选用tar将许多文件打包成一个文件,再以gzip压缩命令压缩成xxx.tar.gz(或称为xxx.tgz)的文件(所以一般.gz或者.tgz文件就是linux系统的文件,.zip就是windows系统的文件)

常用参数:

-c:创建一个新tar文件

-v:显示运行过程的信息

-f:指定文件名

-z:调用gzip压缩命令进行压缩

-t:查看压缩文件的内容

-x:解开tar文件

打包:

tar –cvf xxx.tar ./* 将./*(./*可以是多个文件)的文件打包成xxx.tar文件

打包并且压缩:

tar –zcvf xxx.tar.gz ./*

解压

tar –xvf xxx.tar 就会解压到当前路径

tar -xvf xxx.tar.gz -C /usr/aaa 注意:C要大写,必须在xxx.tar.gz的目录下执行这个命令

find操作:文件查找,用于查找符合条件的文件

示例:

find / -name “ins*” 查找文件名称是以ins开头的文件

find / -name “ins*” –ls 查找文件名称是以ins开头的文件并列出来详细信息

find / –user itcast –ls 查找用户itcast的文件并列出详细信息(user是权限符)

find / –user itcast –type d –ls 查找用户itcast的目录

find /-perm -777 –type d-ls 查找权限是777的文件

grep操作:字符串查找,用于查找文件中符合条件的字符串

用法: grep [选项] PATTERN [FILE]

示例:

grep lang anaconda-ks.cfg 在文件中查找lang

grep lang anaconda-ks.cfg –color 高亮显示

img

img

img

A是after,B是before的意思,A5 B5就是前五行和后五行

6.其它常用命令

clear:清屏命令

pwd:显示当前所在目录

touch:创建一个空文件 touch a.txt

ll -h:友好显示文件大小,k表示

wget:下载资料 wget http://nginx.org/download/nginx-1.9.12.tar.gz 下载的东西会保存再当前目录

LSB是Linux Standard Base的缩写,lsb_release命令用来显示LSB和特定版本的相关信息。如果使用该命令时不带参数,则默认加上-v参数。

-v, –version
显示版本信息
-i, –id
显示发行版的ID
-d, –description
显示该发行版的描述信息
-r, –release
显示当前系统是发行版的具体版本号
-c, –codename
发行版代号
-a, –all
显示上面的所有信息
-h, –help
显示帮助信息

netstat -anp 查看当前进程

netstat -lnp|grep 80 查看80端口 tcp和linux的进程

netstat -ntpl|grep 80 查看80端口 只有tcp

netstat -tunlp|grep 80 查看80端口 只有tcp

ps -aux |grep 80 查看80端口 最全

lsof -i:80 查看80端口 最少

kill -9 进程名字 杀死进程

CentOS7 的防火墙配置跟以前版本有很大区别,CentOS7这个版本的防火墙默认使用的是firewall,与之前的版本Centos 6.x使用iptables不一样

一、iptables防火墙
1、基本操作

查看防火墙状态

service iptables status

停止防火墙

service iptables stop

启动防火墙

service iptables start

重启防火墙

service iptables restart

永久关闭防火墙

chkconfig iptables off

永久关闭后重启

chkconfig iptables on  

2、开启80端口

vim /etc/sysconfig/iptables

加入如下代码

-A INPUT -m state –state NEW -m tcp -p tcp –dport 80 -j ACCEPT
保存退出后重启防火墙

service iptables restart
二、firewall防火墙
1、查看firewall服务状态

systemctl status firewalld

出现Active: active (running)切高亮显示则表示是启动状态。

出现 Active: inactive (dead)灰色表示停止,看单词也行。
2、查看firewall的状态

firewall-cmd –state
3、开启、重启、关闭、firewalld.service服务

开启

service firewalld start

重启

service firewalld restart

关闭

service firewalld stop
4、查看防火墙规则

firewall-cmd –list-all
5、查询、开放、关闭端口

查询端口是否开放

firewall-cmd –query-port=8080/tcp

开放80端口

firewall-cmd –permanent –add-port=80/tcp

移除端口

firewall-cmd –permanent –remove-port=8080/tcp
#重启防火墙(修改配置后要重启防火墙)
firewall-cmd –reload

参数解释

1、firwall-cmd:是Linux提供的操作firewall的一个工具;
2、–permanent:表示设置为持久;
3、–add-port:标识添加的端口;

CentOS7 默认使用firewalld防火墙,如果想换回iptables防火墙,可关闭firewalld并安装iptables。

VI与VIM编辑器

vim是vi的升级。

在Linux下一般使用vi编辑器来编辑文件。vi既可以查看文件也可以编辑文件。有三种模式: 命令行、 插入 、底行 模式

通过vi(vim)文件名,就可以对文件进行操作。

当操作时,开始是命令行模式,按i 或o 或 a 切换到插入模式

i 在当前位置前插入

I 在当前行首插入

a 在当前位置后插入

A 在当前行尾插入

o 在当前行之后插入一行

O 在当前行之前插入一行

再按esc 可以重新切换到命令行模式

在命令行模式下按 “:” 就可以切换到底行模式,更多详细用法,可以查询文档《Vim命令合集.docx》和《vi使用方法详细介绍.docx》

在命令行模式下可以使用一些快捷键:

打开文件:vim file

退出:esc 输入 :q (切换到底行模式,再按q就是退出)

修改文件:输入i进入插入模式

保存并退出:esc 输入:wq

不保存退出:esc 输入:q!

三种进入插入模式:

i:在当前的光标所在处插入

o:在当前光标所在的行的下一行插入

a:在光标所在的下一个字符插入

快捷键:

dd – 快速删除一行

yy - 复制当前行

nyy - 从当前行向后复制几行

p - 粘贴

R – 替换

重定向

echo “sdahufha” 命令就是直接打印出“”内容

> 重定向输出,覆盖原有内容; >> 重定向输出,换行+追加功能; 示例:

cat /etc/passwd > a.txt 将输出定向到a.txt中

cat /etc/passwd >> a.txt 输出并且追加

ifconfig > ifconfig.txt

管道

管道是Linux命令中重要的一个概念,其作用是将一个命令的输出用作另一个命令的输入。示例

ls –help | more 分页查询帮助信息

ps –ef | grep java 查询名称中包含java的进程 (ps -ef 查看当前的进程)

ifconfig | more

cat index.html | more

ps –ef | grep aio

&&命令执行控制

命令之间使用 && 连接,实现逻辑与的功能。

只有在 && 左边的命令返回真(命令返回值 $? == 0),&& 右边的命令才会被执行。

只要有一个命令返回假(命令返回值 $? == 1),后面的命令就不会被执行。

mkdir test && cd test

系统管理常用命令

date 可以显示或设置系统时间

date 显示当前系统时间

date -s “2014-01-01 10:10:10“ 设置系统时间df 显示磁盘信息

df –h 友好显示大小free 显示内存状态

free –m 以mb单位显示内存组昂头top 显示,管理执行中的程序

clear 清屏幕

ps 正在运行的某个进程的状态

ps –ef 查看所有进程

ps –ef | grep ssh 查找某一进程kill 杀掉某一进程kill + pid

kill 2868 杀掉2868编号的进程

kill -9 2868 强制杀死进程

du 显示目录或文件的大小。

du –h 显示当前目录的大小

who 显示目前登入系统的用户信息。

uname 显示系统信息。

uname -a 显示本机详细信息。依次为:内核名称(类别),主机名,内核版本号,内核版本,内核编译日期,硬件名,处理器类型,硬件平台类型,操作系统名称

*Linux下用户与组管理*

用户管理

useradd 添加一个用户

useradd test 添加test用户 (会出现在home目录下)

useradd test -d /home/t1 指定用户home目录

passwd 设置、修改密码

passwd test 为test用户设置密码

切换登录:

ssh -l test -p 22 192.168.19.128 (ssh协议默认22端口)

su – 用户名 切换到这个用户

userdel 删除一个用户

userdel test 删除test用户(不会删除home目录)

userdel –r test 删除用户以及home目录

组管理

当在创建一个新用户user时,若没有指定他所属于的组,就建立一个和该用户同名的私有组

创建用户时也可以指定所在组

groupadd 创建组

groupadd public 创建一个名为public的组

useradd u1 –g public 创建用户指定组groupdel 删除组,如果该组有用户成员,必须先删除用户才能删除组。

groupdel public

id命令:查看一个用户的UID和GID用法:id [选项] [用户名]

img

直接使用id,直接使用id 用户名

su命令:切换用户。用法:su [选项] [-] [用户 [参数] ]示例:

su u1 切换到u1用户

su - u1 切换到u1用户,并且将环境也切换到u1用户的环境(推荐使用)

账户文件

/etc/passwd 用户文件 /etc/shadow 密码文件 /etc/group 组信息文件

【用户文件】

root:x:0:0:root:/root:/bin/bash账号名称: 在系统中是唯一的用户密码: 此字段存放加密口令用户标识码(User ID): 系统内部用它来标示用户组标识码(Group ID): 系统内部用它来标识用户属性用户相关信息: 例如用户全名等用户目录: 用户登录系统后所进入的目录用户环境: 用户工作的环境

【密码文件】

shadow文件中每条记录用冒号间隔的9个字段组成.用户名:用户登录到系统时使用的名字,而且是惟一的口令: 存放加密的口令最后一次修改时间: 标识从某一时刻起到用户最后一次修改时间最大时间间隔: 口令保持有效的最大天数,即多少天后必须修改口令最小时间间隔: 再次修改口令之间的最小天数警告时间:从系统开始警告到口令正式失效的天数不活动时间: 口令过期少天后,该账号被禁用失效时间:指示口令失效的绝对天数(从1970年1月1日开始计算)标志:未使用

【组文件】

root:x:0:组名:用户所属组组口令:一般不用GID:组ID用户列表:属于该组的所有用户

*文件权限管理*

文件权限

img

img

r:对文件是指可读取内容 对目录是可以ls

w:对文件是指可修改文件内容,对目录 是指可以在其中创建或删除子节点(目录或文件)

x:对文件是指是否可以运行这个文件,对目录是指是否可以cd进入这个目录

Linux三种文件类型:

普通文件: 包括文本文件、数据文件、可执行的二进制程序文件等。

目录文件: Linux系统把目录看成是一种特殊的文件,利用它构成文件系统的树型结构。

设备文件: Linux系统把每一个设备都看成是一个文件

文件类型标识:

普通文件(-)目录(d)符号链接(l) 进入etc可以查看,相当于快捷方式字符设备文件(c)块设备文件(s)套接字(s)命名管道(p)

文件权限管理:

chmod 变更文件或目录的权限。

chmod 755 a.txt

chmod u=rwx,g=rx,o=rx a.txt

chmod 000 a.txt / chmod 777 a.txt

chmod u-r a.txt / chmod g+x a.txtchown 变更文件或目录改文件所属用户和组

chown u1:public a.txt:变更当前的目录或文件的所属用户和组(用户为u1,组为public)

chown -R u1:public dir:变更目录中的所有的子目录及子文件的所属用户和组

*常用网络操作*

主机名配置

hostname 查看主机名

hostname xxx 修改主机名,临时性修改 重启后无效

如果想要永久生效,可以修改/etc/sysconfig/network文件(vi 进入文件后编辑)

IP地址配置

Setup设置ip地址

ifconfig 查看(修改)ip地址(重启后无效)

ifconfig eth0 192.168.12.22 修改ip地址

如果想要永久生效

修改 /etc/sysconfig/network-scripts/ifcfg-eth0文件

可以直接输入setup进入网络配置设置(DHCP是自动分配,自己设置的话就是静态ip,空格,tab键)

域名映射

在windows中,System下的drivers下的etc下的hosts文件是域名映射文件

/etc/hosts文件用于在通过主机名进行访问时做ip地址解析之用

img

网络服务管理

service network status 查看指定服务的状态

service network stop 停止指定服务

service network start 启动指定服务

service network restart 重启指定服务

service —status–all 查看系统中所有后台服务

netstat –nltp 查看系统中网络进程的端口监听情况

防火墙设置

防火墙根据配置文件/etc/sysconfig/iptables来控制本机的”出”、”入”网络访问行为。

service iptables status 查看防火墙状态

*service iptables stop 关闭防火墙* (需要关闭以访问一些服务和下载)

service iptables start 启动防火墙

chkconfig iptables off 禁止防火墙自启

shell

Shell是命令解释器(command interpreter),是Unix操作系统的用户接口,程序从用户接口得到输入信息,shell将用户程序及其输入翻译成操作系统内核(kernel)能够识别的指令,并且操作系统内核执行完将返回的输出通过shell再呈现给用户

Shell也是一门编程语言,即shell脚本,shell是解释执行的脚本语言,可直接调用linux命令。 .java -> .class

一个系统可以存在多个shell,可以通过cat /etc/shells命令查看系统中安装的shell,不同的shell可能支持的命令语法是不相同的。

shell本质上是一个.sh结尾的文件,运行这个文件即是执行这个shell脚本

Shell种类

  操作系统内核(kernel)与shell是独立的套件,而且都可被替换。不同的操作系统使用不同的shell;同一个kernel之上可以使用不同的shell。

  常见的shell分为两大主流:

  sh:

    Bourne shell(sh) ,Solaris,hpux默认shell

    Bourne again shell(bash) ,Linux系统默认shell

  csh:

    C shell(csh)

    tc shell(tcsh)

查看使用Shell

img

创建Shell脚本

  一个shell脚本通常包含如下部分:

首行

第一行内容在脚本的首行左侧,表示脚本将要调用的shell解释器,内容如下:

#!/bin/bash

#!符号能够被内核识别成是一个脚本的开始,这一行必须位于脚本的首行,/bin/bash是bash程序的绝对路径,在这里表示后续的内容将通过bash程序解释执行。

注释

注释符号# 放在需注释内容的前面,如下:

img

内容

可执行内容和shell结构,echo是输出语句,date +%F是shell中生成当前日期,格式如2015-11-05

DATE=date -d "-1 day" +%F 前一天的日期

img

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/sh

# Database info
DB_USER=""
DB_PASS=""
DB_HOST=""
DB_NAME=""

# Others vars
BIN_DIR="/usr/bin" #the mysql bin path
BCK_DIR="/data/backup_mysql" #the backup file directory
DATE=`date +%F`

# TODO
$BIN_DIR/mysqldump --opt -h$DB_HOST -u$DB_USER -p$DB_PASS $DB_NAME | gzip > $BCK_DIR/$DATE.sql.zip

# 还原数据库
# 把 *.sql.zip 使用gunzip 或 本地的解压软件 解压为 *.sql 文件
# 用mysql-front导入前一天的 *.sql 文件即可恢复数据

#如果该数据库的用户没有分配 锁表 的权限,则备份会报错 when using LOCK TABLES 。那是因为mysqldump命令默认在导出时是要锁定表的,所以解决方式有两个。一个是给该用户开放 锁表 的权限;另一个是在命令中加上  --skip-lock-tables 这个参数。即是:

#$BIN_DIR/mysqldump --opt -u$DB_USER -p$DB_PASS -h$DB_HOST $DB_NAME --skip-lock-tables | gzip > $BCK_DIR/db_$DATE.sql.gz

Shell脚本的权限

  一般情况下,默认创建的脚本是没有执行权限的。

img

  没有权限不能执行,需要赋予可执行权限。(进入到该脚本文件目录 chmod +x sqlAutoBak.sh 添加执行权限,否则会报错 Permission denied)

img

执行脚本:./sqlAutoBak.sh

注:如果是在windows编写上传的文件可能会报错

1
/bin/sh^M: bad interpreter: No such file or directory

这是不同系统编码格式引起的:在 windows系统中编辑的 .sh文件可能有不可见字符,所以在 Linux系统下执行会报以上异常信息。可以在Windows上使用Notepad++转换成Unix格式(菜单中选择:编辑>档案格式转换>转换成UNIX)

Shell脚本的执行

1,输入脚本的绝对路径或相对路径

    /root/helloWorld.sh

    ./helloWorld.sh

2,bash或sh +脚本

    bash /root/helloWorld.sh

    sh helloWorld.sh

注:当脚本没有x权限时,root和文件所有者通过该方式可以正常执行。

img

3,在脚本的路径前再加”. “ 或source

    source /root/helloWorld.sh

    . ./helloWorld.sh

  区别:第一种和第二种会新开一个bash,不同bash中的变量无法共享。但是使用. ./脚本.sh 这种方式是在同一个shell里面执行的。

img

定时器

检查是否安装了crontab,如果提示未安装请自行安装,crontab安装包在系统光盘里面的pacekage文件夹crontabs安装包。

1
rpm -qa | grep crontab

crontab服务启动与关闭。

1
2
3
4
/etc/init.d/crond stop           --关闭服务
/etc/init.d/crond start --启动服务
/etc/init.d/crond restart --重启服务
/etc/init.d/crond reload --重新载入配置

编辑定时任务列表

1
crontab -e

查看任务是否创建成功

1
crontab -l

删除当前用户定时任务(删除所有,除非不再使用,否则没必要使用)

1
crontab -r        

设置crond开机自动启动

1
2
chkconfig  --list crond                 --查看crond是否开机自动启动
chkconfig --level 35 crond on --设置crond开机自动启动

日志问题

可参考文章:https://www.cnblogs.com/kevingrace/p/6307298.html

logrotate是Linux系统自带命令,用于对系统日志进行轮转、压缩和删除,也可以将日志发送到指定邮箱。它默认的配置文件在:

1
2
/etc/logrotate.conf 
/etc/logrotate.d/

以mqtt日志文件为例:

1.在logrotate.d目录中新建文件mosquitto,可以直接在该目录下使用vim mosquitto命令编辑文件mosquitto,写入:

1
2
3
4
5
6
7
/var/log/mosquitto/mosquitto.log {
daily
dateext
copytruncate
nocompress
rotate 15
}

/var/log/mosquitto/mosquitto.log: mosquitto日志输出文件(这里注意这个文件路径是之前.log的位置,一定要对,不然不会生效)
daily: 日志将按照天来转储,也可以设置为weekly和monthly
dateext: 转储后的日志文件会附加上日期信息,例如转储后文件为mosquitto.log-20181105,如果不加的话,文件名格式就变为了序号mosquitto.log-1
copytruncate: 备份日志文件并截断,设置为nocopytruncate时备份日志文件但是不截断
nocompress: 不进行压缩
rotate 15: 指定日志文件删除之前转储的次数

2.执行命令:/usr/sbin/logrotate -vf /etc/logrotate.d/mosquitto

完成后可看到在/var/log/mosquitto/目录中多了一个文件mosquitto.log-20210806

*Linux上软件安装介绍*

Linux上的软件安装有以下几种常见方式介绍

1.二进制发布包

软件已经针对具体平台编译打包发布,只要解压,修改配置即可(可能对linux的某个版本可以,其他版本不可用)

2.RPM包

软件已经按照redhat的包管理工具规范RPM进行打包发布,需要获取到相应的软件RPM发布包,然后用RPM命令进行安装(可能依赖其他包,需要下载其他包,但是可能没有打包依赖包)

3.Yum在线安装

软件已经以RPM规范打包,但发布在了网络上的一些服务器上,可用yum在线安装服务器上的rpm软件,并且会自动解决软件安装过程中的库依赖问题

4.源码编译安装

软件以源码工程的形式发布,需要获取到源码工程后用相应开发工具进行编译打包部署。

上传与下载工具介绍:就是windows和linux的文件交互

1.FileZilla:在windows上下载好

img

解压后,运行exe文件:

img

左边是windows的文件,右边是linux的文件,可以直接把左边的安装包拖拽到右边进行上传

2.lrzsz

我们可以在linux上使用yum安装方式安装 yum install lrzsz

注意:必须有网络

直接输入命令:yum install lrzsz 就会提示下载成功

可以在crt中设置上传与下载目录:在crt上面有个选项,会话选择

img

上传:

img

下载:

img

在Linux上安装JDK:

1.上传JDK到Linux的服务器

通过rz命令上传JDK,然后java –version发现可以查到版本号,所以卸载自带的open-JDK

rpm -qa | grep java 查找自带的java版本。然后复制,再通过rpm -e –nodeps 复制的内容卸载

rpm -e –nodeps java-1.6.0-openjdk-1.6.0.35-1.13.7.1.el6_6.i686

rpm -e –nodeps java-1.7.0-openjdk-1.7.0.79-2.5.5.4.el6.i686

2.在Linux服务器上安装JDK

通常将软件安装到/usr/local

直接解压就可以: tar –xvf jdk.tar.gz -C 目标路径

img./就是当前

3.配置JDK的环境变量

ll查看jdk下的文件获得版本号

配置环境变量:

① vi /etc/profile

② 在末尾行添加

​ #set java environment

​ JAVA_HOME=/usr/local/jdk/jdk1.7.0_71

​ CLASSPATH=.:$JAVA_HOME/lib.tools.jar

​ PATH=$JAVA_HOME/bin:$PATH

​ export JAVA_HOME CLASSPATH PATH

保存退出 :wq

③source /etc/profile 使更改的配置立即生效

在Linux上安装Mysql:

1.将mysql的安装文件上传到Linux的服务器.(也可以通过wget 网址下载Mysql的安装包)

rz上传

img

将mysql的tar解压:创建一个新的mysql目录

img

将系统自带的mysql卸载

img

img

–nodeps是忽略相关依赖包

2.安装MYSQL服务端

img

如果安装显示出错,需要导入相关的依赖包

下面的提示是告诉我们root用户的密码第一次是随机生成的,它保存在/root/.mysql_secret中,第一次登录需要修改root密码

img

3.安装MYSQL客户端

img

查看生成的root密码

img

img

报错:原因是没有启动mysql服务

需要开启mysql服务

img

可以通过netstat -nltp 查看现在的服务,找到3306的端口证明启动了mysql

然后进行登录,操作mysql时会报错,原因是第一次操作mysql必须修改root用户的密码

img

设置root用户的密码为root

img

*M*****ysql服务加入到系统服务并自动启动操作********:****chkconfig –add mysql

自动启动:chkconfig mysql on

查询列表:chkconfig

关于mysql远程访问设置:

先登录到mysql,然后输入下面命令进行设置

img

在linux中很多软件的端口都被”防火墙”限止,我们需要将防火墙关闭,也可以将防火墙打开3306端口

/sbin/iptables -I INPUT -p tcp –dport 3306 -j ACCEPT

/etc/rc.d/init.d/iptables save

/etc/init.d/iptables status

学习阶段我们也可以直接将防火墙关闭(不推荐,因为服务器需要防火墙):service iptables stop

在Linux上安装tomcat:

1.Tomcat上传到linux上:rz,上传文件。ll查看是否上传成功

2.将上传的tomcat解压:建目录mkdir tomcat7 ,然后tar -xvf 安装包名字 -C ./tomacat7

3.在tomcat/bin目录下执行 startup.sh(注意防火墙需要关闭): ./startup.sh

4.查看日志 tomcat/logs/catalina.out:cat ./catalina.out

也可以查看cat ./catalina.日期.log

以后的文件工程可以放到webapp下进行发布

Linux系统一般有4个主要部分:

内核、shell、文件系统和应用程序。内核、shell和文件系统一起形成了基本的操作系统结构,它们使得用户可以运行程序、管理文件并使用系统。部分层次结构如图1-1所示。

图片

图片

一、linux内核

图片

内核是操作系统的核心,具有很多最基本功能,它负责管理系统的进程、内存、设备驱动程序、文件和网络系统,决定着系统的性能和稳定性。

Linux 内核由如下几部分组成:内存管理、进程管理、设备驱动程序、文件系统和网络管理等。如图:

图片图1

系统调用接口:SCI 层提供了某些机制执行从用户空间到内核的函数调用。这个接口依赖于体系结构,甚至在相同的处理器家族内也是如此。SCI 实际上是一个非常有用的函数调用多路复用和多路分解服务。在 ./linux/kernel 中您可以找到 SCI 的实现,并在 ./linux/arch 中找到依赖于体系结构的部分。

1、内存管理

对任何一台计算机而言,其内存以及其它资源都是有限的。为了让有限的物理内存满足应用程序对内存的大需求量,Linux 采用了称为“虚拟内存”的内存管理方式。Linux 将内存划分为容易处理的“内存页”(对于大部分体系结构来说都是 4KB)。Linux 包括了管理可用内存的方式,以及物理和虚拟映射所使用的硬件机制。

不过内存管理要管理的可不止 4KB 缓冲区。Linux 提供了对 4KB 缓冲区的抽象,例如 slab 分配器。这种内存管理模式使用 4KB 缓冲区为基数,然后从中分配结构,并跟踪内存页使用情况,比如哪些内存页是满的,哪些页面没有完全使用,哪些页面为空。这样就允许该模式根据系统需要来动态调整内存使用。

为了支持多个用户使用内存,有时会出现可用内存被消耗光的情况。由于这个原因,页面可以移出内存并放入磁盘中。这个过程称为交换,因为页面会被从内存交换到硬盘上。内存管理的源代码可以在 ./linux/mm 中找到。

2、进程管理

进程实际是某特定应用程序的一个运行实体。在 Linux 系统中,能够同时运行多个进程,Linux 通过在短的时间间隔内轮流运行这些进程而实现“多任务”。这一短的时间间隔称为“时间片”,让进程轮流运行的方法称为“进程调度” ,完成调度的程序称为调度程序。

进程调度控制进程对CPU的访问。当需要选择下一个进程运行时,由调度程序选择最值得运行的进程。可运行进程实际上是仅等待CPU资源的进程,如果某个进程在等待其它资源,则该进程是不可运行进程。Linux使用了比较简单的基于优先级的进程调度算法选择新的进程。

通过多任务机制,每个进程可认为只有自己独占计算机,从而简化程序的编写。每个进程有自己单独的地址空间,并且只能由这一进程访问,这样,操作系统避免了进程之间的互相干扰以及“坏”程序对系统可能造成的危害。 为了完成某特定任务,有时需要综合两个程序的功能,例如一个程序输出文本,而另一个程序对文本进行排序。为此,操作系统还提供进程间的通讯机制来帮助完成这样的任务。Linux 中常见的进程间通讯机制有信号、管道、共享内存、信号量和套接字等。

内核通过 SCI 提供了一个应用程序编程接口(API)来创建一个新进程(fork、exec 或 Portable Operating System Interface [POSⅨ] 函数),停止进程(kill、exit),并在它们之间进行通信和同步(signal 或者 POSⅨ 机制)。

3、 文件系统

和 DOS 等操作系统不同,Linux 操作系统中单独的文件系统并不是由驱动器号或驱动器名称(如 A: 或 C: 等)来标识的。相反,和 UNIX 操作系统一样,Linux 操作系统将独立的文件系统组合成了一个层次化的树形结构,并且由一个单独的实体代表这一文件系统。Linux 将新的文件系统通过一个称为“挂装”或“挂上”的操作将其挂装到某个目录上,从而让不同的文件系统结合成为一个整体。Linux 操作系统的一个重要特点是它支持许多不同类型的文件系统。Linux 中最普遍使用的文件系统是 Ext2,它也是 Linux 土生土长的文件系统。但 Linux 也能够支持 FAT、VFAT、FAT32、MINIX 等不同类型的文件系统,从而可以方便地和其它操作系统交换数据。由于 Linux 支持许多不同的文件系统,并且将它们组织成了一个统一的虚拟文件系统.

虚拟文件系统(VirtualFileSystem,VFS):隐藏了各种硬件的具体细节,把文件系统操作和不同文件系统的具体实现细节分离了开来,为所有的设备提供了统一的接口,VFS提供了多达数十种不同的文件系统。虚拟文件系统可以分为逻辑文件系统和设备驱动程序。逻辑文件系统指Linux所支持的文件系统,如ext2,fat等,设备驱动程序指为每一种硬件控制器所编写的设备驱动程序模块。

虚拟文件系统(VFS)是 Linux 内核中非常有用的一个方面,因为它为文件系统提供了一个通用的接口抽象。VFS 在 SCI 和内核所支持的文件系统之间提供了一个交换层。即VFS 在用户和文件系统之间提供了一个交换层。

VFS在用户和文件系统之间提供了一个交换层:

图片

在 VFS 上面,是对诸如 open、close、read 和 write 之类的函数的一个通用 API 抽象。在 VFS 下面是文件系统抽象,它定义了上层函数的实现方式。它们是给定文件系统(超过 50 个)的插件。文件系统的源代码可以在 ./linux/fs 中找到。

文件系统层之下是缓冲区缓存,它为文件系统层提供了一个通用函数集(与具体文件系统无关)。这个缓存层通过将数据保留一段时间(或者随即预先读取数据以便在需要是就可用)优化了对物理设备的访问。缓冲区缓存之下是设备驱动程序,它实现了特定物理设备的接口。

因此,用户和进程不需要知道文件所在的文件系统类型,而只需要象使用 Ext2 文件系统中的文件一样使用它们。

4、设备驱动程序

设备驱动程序是 Linux 内核的主要部分。和操作系统的其它部分类似,设备驱动程序运行在高特权级的处理器环境中,从而可以直接对硬件进行操作,但正因为如此,任何一个设备驱动程序的错误都可能导致操作系统的崩溃。设备驱动程序实际控制操作系统和硬件设备之间的交互。

设备驱动程序提供一组操作系统可理解的抽象接口完成和操作系统之间的交互,而与硬件相关的具体操作细节由设备驱动程序完成。一般而言,设备驱动程序和设备的控制芯片有关,例如,如果计算机硬盘是 SCSI 硬盘,则需要使用 SCSI 驱动程序,而不是 IDE 驱动程序。

5、网络接口(NET)

提供了对各种网络标准的存取和各种网络硬件的支持。网络接口可分为网络协议和网络驱动程序。网络协议部分负责实现每一种可能的网络传输协议。众所周知,TCP/IP 协议是 Internet 的标准协议,同时也是事实上的工业标准。

Linux 的网络实现支持 BSD 套接字,支持全部的TCP/IP协议。Linux内核的网络部分由BSD套接字、网络协议层和网络设备驱动程序组成。网络设备驱动程序负责与硬件设备通讯,每一种可能的硬件设备都有相应的设备驱动程序。

图片

二、linux shell

图片

shell是系统的用户界面,提供了用户与内核进行交互操作的一种接口。它接收用户输入的命令并把它送入内核去执行,是一个命令解释器。另外,shell编程语言具有普通编程语言的很多特点,用这种编程语言编写的shell程序与其他应用程序具有同样的效果。

目前主要有下列版本的shell。

1、Bourne Shell:是贝尔实验室开发的。
2、BASH:是GNU的Bourne Again Shell,是GNU操作系统上默认的shell,大部分linux的发行套件使用的都是这种shell。
3、Korn Shell:是对Bourne SHell的发展,在大部分内容上与Bourne Shell兼容。
4、C Shell:是SUN公司Shell的BSD版本。

图片

三、****linux 文件系统

图片

文件系统是文件存放在磁盘等存储设备上的组织方法。Linux系统能支持多种目前流行的文件系统,如EXT2、 EXT3、 FAT、 FAT32、 VFAT和ISO9660。

3.1 文件类型

Linux下面的文件类型主要有:

1) 普通文件:C语言元代码、SHELL脚本、二进制的可执行文件等。分为纯文本和二进制。
2) 目录文件:目录,存储文件的唯一地方。
3) 链接文件:指向同一个文件或目录的的文件。
4) 设备文件:与系统外设相关的,通常在/dev下面。分为块设备和字符设备。

5)管道(FIFO)文件 : 提供进程建通信的一种方式
6)套接字(socket) 文件: 该文件类型与网络通信有关

可以通过ls –l, file, stat几个命令来查看文件的类型等相关信息。

3.2 Linux目录

文件结构是文件存放在磁盘等存贮设备上的组织方法。主要体现在对文件和目录的组织上。

目录提供了管理文件的一个方便而有效的途径。

Linux使用标准的目录结构,在安装的时候,安装程序就已经为用户创建了文件系统和完整而固定的目录组成形式,并指定了每个目录的作用和其中的文件类型。

完整的目录树可划分为小的部分,这些小部分又可以单独存放在自己的磁盘或分区上。这样,相对稳定的部分和经常变化的部分可单独存放在不同的分区中,从而方便备份或系统管理。目录树的主要部分有 root、/usr、/var、/home 等(图2) 。这样的布局可方便在 Linux 计算机之间共享文件系统的某些部分。

图片

图2

Linux采用的是树型结构。最上层是根目录,其他的所有目录都是从根目录出发而生成的。

微软的DOS和windows也是采用树型结构,但是在DOS和 windows中这样的树型结构的根是磁盘分区的盘符,有几个分区就有几个树型结构,他们之间的关系是并列的。最顶部的是不同的磁盘(分区),如:C,D,E,F等。

但是在linux中,无论操作系统管理几个磁盘分区,这样的目录树只有一个。从结构上讲,各个磁盘分区上的树型目录不一定是并列的。

3.3 Linux磁盘分区

一、主分区,扩展分区和逻辑分区:

linux分区不同于windows,硬盘和硬盘分区在Linux都表示为设备.

硬盘分区一共有三种:主分区,扩展分区和逻辑分区。

硬盘的分区主要分为主分区(Primary Partion)和扩展分区(Extension Partion)两种,主分区和扩展分区的数目之和不能大于四个。

主分区(Primary Partion):可以马上被使用但不能再分区。

扩展分区(Extension Partion):必须再进行分区后才能使用,也就是说它必须还要进行二次分区。

逻辑分区((Logical Partion)):由扩展分区建立起来的分区。逻辑分区没有数量上限制。

扩展分区只不过是逻辑分区的“容器”,实际上只有主分区和逻辑分区进行数据存储。

二、Linux下硬盘分区的标识

硬盘分区的标识一般使用/dev/hd[a-z]X或者/dev/sd[a-z]X来标识,其中[a-z]代表硬盘号,X代表硬盘内的分区号。

整块硬盘分区的块号标识:Linux下用hda、hdb、sda、sdb 等来标识不同的硬盘;

其中:

IDE接口硬盘:表示为/dev/hda1、/dev/hdb …;

SCSI 接口的硬盘、SATA接口的硬盘表示为/dev/sda、/dev/sdb … … ;

硬盘内的分区:如果X的值是1到4,表示硬盘的主分区(包含扩展分区);逻辑分区从是从5开始的,比如/dev/hda5肯定是逻辑分区了;

例如:

用hda1、hda2、 hda5、hda6 来标识不同的分区。其中,字母a 代表第一块硬盘,b代表第二块硬盘,依次类推。而数字1 代表一块硬盘的第一个分区、2 代表第二个分区,依次类推。1 到4 对应的是主分区(Primary Partition)或扩展分区(Extension Partition)。从5开始,对应的都是硬盘的逻辑分区(Logical Partition)。一块硬盘即使只有一个主分区,逻辑分区也是从5开始编号的,这点应特别注意。

总结:一个硬盘分区首先要大确认在哪个硬盘,然后再确认它所在硬盘内的哪个分区。

对于/dev/hda 类似的表示方法,也并不寞生吧;我们在Linux通过fdisk -l 就可以查到硬盘是/dev/hda还是/dev/hdb;

1
2
   [root@localhost ~]# fdisk -l        
 Disk /dev/hda: 80.0 GB, 80026361856 bytes   255 heads, 63 sectors/track, 9729 cylinders   Units = cylinders of 16065 * 512 = 8225280 bytes    Device Boot Start End Blocks Id System   /dev/hda1 * 1 970 7791493+ 7 HPFS/NTFS   /dev/hda2 971 9729 70356667+ 5 Extended   /dev/hda5 971 2915 15623181 b W95 FAT32   /dev/hda6 2916 4131 9767488+ 83 linux   /dev/hda7 4132 5590 11719386 83 linux   /dev/hda8 5591 6806 9767488+ 83 linux   /dev/hda9 6807 9657 22900626 83 linux   /dev/hda10 9658 9729 578308+ 82 linux swap / Solaris

请注意第一行, Disk /dev/hda: 80.0 GB, 80026361856 bytes ,这个就是表示机器中只有一个硬盘设备/dev/hda ,体积大小为 80.0G;下面的就是硬盘的分区,每个分区都有详细的信息,在这里不详细说了;

Linux下磁盘分区和目录的关系如下:

– 任何一个分区都必须挂载到某个目录上。

– 目录是逻辑上的区分。分区是物理上的区分。

– 磁盘Linux分区都必须挂载到目录树中的某个具体的目录上才能进行读写操作。

– 根目录是所有Linux的文件和目录所在的地方,需要挂载上一个磁盘分区。

3.4 linux主要目录的功用。

1
2
/bin 二进制可执行命令     
/dev 设备特殊文件   /etc 系统管理和配置文件   /etc/rc.d 启动的配置文件和脚本   /home 用户主目录的基点,比如用户user的主目录就是/home/user,可以用~user表示   /lib 标准程序设计库,又叫动态链接共享库,作用类似windows里的.dll文件   /sbin 系统管理命令,这里存放的是系统管理员使用的管理程序   /tmp 公用的临时文件存储点   /root 系统管理员的主目录(呵呵,特权阶级)   /mnt 系统提供这个目录是让用户临时挂载其他的文件系统。   /lost+found 这个目录平时是空的,系统非正常关机而留下“无家可归”的文件(windows下叫什么.chk)就在这里   /proc 虚拟的目录,是系统内存的映射。可直接访问这个目录来获取系统信息。   /var 某些大文件的溢出区,比方说各种服务的日志文件   /usr 最庞大的目录,要用到的应用程序和文件几乎都在这个目录。其中包含:    /usr/X11R6 存放X window的目录    /usr/bin 众多的应用程序    /usr/sbin 超级用户的一些管理程序    /usr/doc linux文档    /usr/include linux下开发和编译应用程序所需要的头文件    /usr/lib 常用的动态链接库和软件包的配置文件    /usr/man 帮助文档    /usr/src 源代码,linux内核的源代码就放在/usr/src/linux里    /usr/local/bin 本地增加的命令    /usr/local/lib 本地增加的库

3.5 linux文件系统

文件系统指文件存在的物理空间,linux系统中每个分区都是一个文件系统,都有自己的目录层次结构。linux会将这些分属不同分区的、单独的文件系统按一定的方式形成一个系统的总的目录层次结构。一个操作系统的运行离不开对文件的操作,因此必然要拥有并维护自己的文件系统。

1、文件系统类型:

ext2 : 早期linux中常用的文件系统 ext3 : ext2的升级版,带日志功能 RAMFS : 内存文件系统,速度很快 NFS : 网络文件系统,由SUN发明,主要用于远程文件共享 MS-DOS : MS-DOS文件系统 VFAT : Windows 95/98 操作系统采用的文件系统 FAT : Windows XP 操作系统采用的文件系统 NTFS: Windows NT/XP 操作系统采用的文件系统 HPFS : OS/2 操作系统采用的文件系统 PROC : 虚拟的进程文件系统 ISO9660 : 大部分光盘所采用的文件系统 ufsSun : OS 所采用的文件系统 NCPFS : Novell 服务器所采用的文件系统 SMBFS : Samba 的共享文件系统 XFS : 由SGI开发的先进的日志文件系统,支持超大容量文件 JFS :IBM的AIX使用的日志文件系统 ReiserFS : 基于平衡树结构的文件系统 udf: 可擦写的数据光盘文件系统

2、文件系统特性:

磁盘分区完毕后还需要进行格式化(format),之后操作系统才能够使用这个分区。格式化的目的是能使操作系统可以使用的文件系统格式(即我们上面提到文件系统类型).

每种操作系统能够使用的文件系统并不相同. 如windows 98 以前的微软操作系统主要利用的文件系统是 FAT (或 FAT16),windows 2000 以后的版本有所谓的 NTFS 文件系统,至于 Linux 的正统文件系统则为 Ext2 (Linux second extended file system, ext2fs)这一个。此外,在默认的情况下,windows 操作系统是不会认识 Linux 的 Ext2 的。

传统的磁盘与文件系统之应用中,一个分区就是只能够被格式化成为一个文件系统,所以我们可以说一个 filesystem 就是一个 partition。但是由于新技术的利用,例如我们常听到的LVM与软件磁盘阵列(software raid), 这些技术可以将一个分区格式化为多个文件系统(例如LVM),也能够将多个分区合成一个文件系统(LVM, RAID)!所以说,目前我们在格式化时已经不再说成针对 partition 来格式化了, 通常我们可以称呼一个可被挂载的数据为一个文件系统而不是一个分区喔!

那么文件系统是如何运行的呢?这与操作系统的文件数据有关。较新的操作系统的文件数据除了文件实际内容外, 通常含有非常多的属性,例如 Linux 操作系统的文件权限(rwx)与文件属性(拥有者、群组、时间参数等)。 文件系统通常会将这两部份的数据分别存放在不同的区块,权限与属性放置到 inode 中,至于实际数据则放置到 data block 区块中。另外,还有一个超级区块 (superblock) 会记录整个文件系统的整体信息,包括 inode 与 block 的总量、使用量、剩余量等。

对于一个磁盘分区来说,在被指定为相应的文件系统后,整个分区被分为 1024,2048 和 4096 字节大小的块。根据块使用的不同,可分为:

1、超级块(Superblock): 这是整个文件系统的第一块空间。包括整个文件系统的基本信息,如块大小,inode/block的总量、使用量、剩余量,指向空间 inode 和数据块的指针等相关信息。

2、inode块(文件索引节点) : 文件系统索引,记录文件的属性。它是文件系统的最基本单元,是文件系统连接任何子目录、任何文件的桥梁。每个子目录和文件只有唯一的一个 inode 块。它包含了文件系统中文件的基本属性(文件的长度、创建及修改时间、权限、所属关系)、存放数据的位置等相关信息. 在 Linux 下可以通过 “ls -li” 命令查看文件的 inode 信息。硬连接和源文件具有相同的 inode 。

3、数据块(Block) :实际记录文件的内容,若文件太大时,会占用多个 block。为了提高目录访问效率,Linux 还提供了表达路径与 inode 对应关系的 dentry 结构。它描述了路径信息并连接到节点 inode,它包括各种目录信息,还指向了 inode 和超级块。

就像一本书有封面、目录和正文一样。在文件系统中,超级块就相当于封面,从封面可以得知这本书的基本信息;inode 块相当于目录,从目录可以得知各章节内容的位置;而数据块则相当于书的正文,记录着具体内容。

Linux正统的文件系统(如ext2、3等)将硬盘分区时会划分出超级块、inode Table区块和data block数据区域。一个文件由一个超级块、inode和数据区域块组成。Inode包含文件的属性(如读写属性、owner等,以及指向数据块的指针),数据区域块则是文件内容。当查看某个文件时,会先从inode table中查出文件属性及数据存放点,再从数据块中读取数据。

图片

ext2文件系统示意图

我们将 inode 与 block 区块用图解来说明一下,如下图所示,文件系统先格式化出 inode 与 block 的区块,假设某一个文件的属性与权限数据是放置到 inode 4 号(下图较小方格内),而这个 inode 记录了文件数据的实际放置点为 2, 7, 13, 15 这四个 block 号码,此时我们的操作系统就能够据此来排列磁盘的阅读顺序,可以一口气将四个 block 内容读出来!那么数据的读取就如同下图中的箭头所指定的模样了。

图片

图 inode/block 数据存取示意图

这种数据存取的方法我们称为索引式文件系统(indexed allocation)。那有没有其他的惯用文件系统可以比较一下啊?有的,那就是我们惯用的闪盘(闪存),闪盘使用的文件系统一般为 FAT 格式。FAT 这种格式的文件系统并没有 inode 存在,所以 FAT 没有办法将这个文件的所有 block 在一开始就读取出来。每个 block 号码都记录在前一个 block 当中, 其读取方式有点像下图所示:

图片

图、FAT文件系统数据存取示意图

上图中我们假设文件的数据依序写入1->7->4->15号这四个 block 号码中, 但这个文件系统没有办法一口气就知道四个 block 的号码,他得要一个一个的将 block 读出后,才会知道下一个 block 在何处。如果同一个文件数据写入的 block 分散的太过厉害时,则我们的磁盘读取头将无法在磁盘转一圈就读到所有的数据, 因此磁盘就会多转好几圈才能完整的读取到这个文件的内容!

常常会听到所谓的“碎片整理”吧? 需要碎片整理的原因就是文件写入的 block 太过于离散了,此时文件读取的效能将会变的很差所致。 这个时候可以透过碎片整理将同一个文件所属的 blocks 汇整在一起,这样数据的读取会比较容易啊! 想当然尔,FAT 的文件系统需要经常的碎片整理一下,那么 Ext2 是否需要磁盘重整呢?

由于 Ext2 是索引式文件系统,基本上不太需要常常进行碎片整理的。但是如果文件系统使用太久, 常常删除/编辑/新增文件时,那么还是可能会造成文件数据太过于离散的问题,此时或许会需要进行重整一下的。不过,老实说,鸟哥倒是没有在 Linux 操作系统上面进行过 Ext2/Ext3 文件系统的碎片整理说!似乎不太需要啦!^_^

可以用ln命令对一个已经存在的文件再建立一个新的连接,而不复制文件的内容。连接有软连接和硬连接之分,软连接又叫符号连接。它们各自的特点是:

硬连接:原文件名和连接文件名都指向相同的物理地址。目录不能有硬连接;硬连接不能跨越文件系统(不能跨越不同的分区)文件在磁盘中只有一个拷贝,节省硬盘空间;

由于删除文件要在同一个索引节点属于唯一的连接时才能成功,因此可以防止不必要的误删除。

符号连接:用ln -s命令建立文件的符号连接符号连接是linux特殊文件的一种,作为一个文件,它的数据是它所连接的文件的路径名。类似windows下的快捷方式。

可以删除原有的文件而保存连接文件,没有防止误删除功能。

这一段的的内容过于抽象,又是节点又是数组的,我已经尽量通俗再通俗了,又不好加例子作演示。大家如果还是云里雾里的话,我也没有什么办法了,只有先记住,日后在实际应用中慢慢体会、理解了。这也是我学习的一个方法吧。

3.6 文件系统在内核中的表示

内核数据结构

Linux内核的VFS子系统可以图示如下:

图片

文件与IO: 每个进程在PCB(Process Control Block)中都保存着一份文件描述符表,文件描述符就是这个表的索引,每个表项都有一个指向已打开文件的指针,现在我们明确一下:已打开的文件在内核中用file结构体表示,文件描述符表中的指针指向file结构体。

在file结构体中维护File Status Flag(file结构体的成员f_flags)和当前读写位置(file结构体的成员f_pos)。在上图中,进程1和进程2都打开同一文件,但是对应不同的file结构体,因此可以有不同的File Status Flag和读写位置。file结构体中比较重要的成员还有f_count,表示引用计数(Reference Count),后面我们会讲到,dup、fork等系统调用会导致多个文件描述符指向同一个file结构体,例如有fd1和fd2都引用同一个file结构体,那么它的引用计数就是2,当close(fd1)时并不会释放file结构体,而只是把引用计数减到1,如果再close(fd2),引用计数就会减到0同时释放file结构体,这才真的关闭了文件。

每个file结构体都指向一个file_operations结构体,这个结构体的成员都是函数指针,指向实现各种文件操作的内核函数。比如在用户程序中read一个文件描述符,read通过系统调用进入内核,然后找到这个文件描述符所指向的file结构体,找到file结构体所指向的file_operations结构体,调用它的read成员所指向的内核函数以完成用户请求。在用户程序中调用lseek、read、write、ioctl、open等函数,最终都由内核调用file_operations的各成员所指向的内核函数完成用户请求。

file_operations结构体中的release成员用于完成用户程序的close请求,之所以叫release而不叫close是因为它不一定真的关闭文件,而是减少引用计数,只有引用计数减到0才关闭文件。对于同一个文件系统上打开的常规文件来说,read、write等文件操作的步骤和方法应该是一样的,调用的函数应该是相同的,所以图中的三个打开文件的file结构体指向同一个file_operations结构体。如果打开一个字符设备文件,那么它的read、write操作肯定和常规文件不一样,不是读写磁盘的数据块而是读写硬件设备,所以file结构体应该指向不同的file_operations结构体,其中的各种文件操作函数由该设备的驱动程序实现。

每个file结构体都有一个指向dentry结构体的指针,“dentry”是directory entry(目录项)的缩写。我们传给open、stat等函数的参数的是一个路径,例如/home/akaedu/a,需要根据路径找到文件的inode。为了减少读盘次数,内核缓存了目录的树状结构,称为dentry cache,其中每个节点是一个dentry结构体,只要沿着路径各部分的dentry搜索即可,从根目录/找到home目录,然后找到akaedu目录,然后找到文件a。dentry cache只保存最近访问过的目录项,如果要找的目录项在cache中没有,就要从磁盘读到内存中。

每个dentry结构体都有一个指针指向inode结构体。inode结构体保存着从磁盘inode读上来的信息。在上图的例子中,有两个dentry,分别表示/home/akaedu/a和/home/akaedu/b,它们都指向同一个inode,说明这两个文件互为硬链接。inode结构体中保存着从磁盘分区的inode读上来信息,例如所有者、文件大小、文件类型和权限位等。每个inode结构体都有一个指向inode_operations结构体的指针,后者也是一组函数指针指向一些完成文件目录操作的内核函数。

和file_operations不同,inode_operations所指向的不是针对某一个文件进行操作的函数,而是影响文件和目录布局的函数,例如添加删除文件和目录、跟踪符号链接等等,属于同一文件系统的各inode结构体可以指向同一个inode_operations结构体。

inode结构体有一个指向super_block结构体的指针。super_block结构体保存着从磁盘分区的超级块读上来的信息,例如文件系统类型、块大小等。super_block结构体的s_root成员是一个指向dentry的指针,表示这个文件系统的根目录被mount到哪里,在上图的例子中这个分区被mount到/home目录下。

file、dentry、inode、super_block这几个结构体组成了VFS的核心概念。对于ext2文件系统来说,在磁盘存储布局上也有inode和超级块的概念,所以很容易和VFS中的概念建立对应关系。而另外一些文件系统格式来自非UNIX系统(例如Windows的FAT32、NTFS),可能没有inode或超级块这样的概念,但为了能mount到Linux系统,也只好在驱动程序中硬凑一下,在Linux下看FAT32和NTFS分区会发现权限位是错的,所有文件都是rwxrwxrwx,因为它们本来就没有inode和权限位的概念,这是硬凑出来的。

3.6 挂载文件系统

linux系统中每个分区都是一个文件系统,都有自己的目录层次结构。linux会将这些分属不同分区的、单独的文件系统按一定的方式形成一个系统的总的目录层次结构。这里所说的“按一定方式”就是指的挂载。

将一个文件系统的顶层目录挂到另一个文件系统的子目录上,使它们成为一个整体,称为挂载。把该子目录称为挂载点.

例如要读取硬盘中的一个格式化好的分区、光盘或软件等设备时,必须先把这些设备对应到某个目录上,而这个目录就称为“挂载点(mount point)”,这样才可以读取这些设备。挂载后将物理分区细节屏蔽掉,用户只有统一的逻辑概念。所有的东西都是文件。

注意:1、挂载点必须是一个目录。

2、一个分区挂载在一个已存在的目录上,这个目录可以不为空,但挂载后这个目录下以前的内容将不可用。

对于其他操作系统建立的文件系统的挂载也是这样。但是需要理解的是:光盘、软盘、其他操作系统使用的文件系统的格式与linux使用的文件系统格式是不一样的。光盘是ISO9660;软盘是fat16或ext2;windows NT是fat16、NTFS;windows98是fat16、fat32;windows2000和windowsXP是fat16、fat32、 NTFS。挂载前要了解linux是否支持所要挂载的文件系统格式。

挂载时使用mount命令,其格式:mount [-参数] [设备名称] [挂载点]
其中常用的参数有

-t 指定设备的文件系统类型(什么提到的文件类型)
-o 指定挂载文件系统时的选项。有些也可用在/etc/fstab中。常用的有

1
2
codepage=XXX 代码页  
iocharset=XXX 字符集 ro 以只读方式挂载 rw 以读写方式挂载 nouser 使一般用户无法挂载 user 可以让一般用户挂载设备

例如:

1、挂载windows的文件系统:

1)首先我们使用sudo fdisk -l查看挂载的设备,例如最下面有:/dev/hda5
2)mkdir创建一个目录,这里的目录是作为挂在目录,就是你要把E盘挂到这个目录下:mk /mnt/winc
3)windows和linux使用的不是一个文件系统,一般情况下linux不挂载windows文件系统,所以要你手动mount:
# mount -t vfat /dev/hda5 /mnt/winc ( -t vfat指出这里的文件系统fat32)
现在就可以进入/mnt/winc等目录读写这些文件了。

2、挂载光盘:# mk /mnt/cdrom

# mount -t iso9660 /dev/cdrom /mnt/cdrom (关盘的名字一般都是cdrom,这条命令一般都通用)

3、虚拟机共享文件夹:例如在VirtualBox下,主机是Windows,Ubuntu是Guest。共分三步:

1). 首先要安装虚拟电脑工具包:在VirtualBox的菜单里选择”设备”->”安装虚拟电脑工具包”,你会发现在Ubuntu桌面上多出一个光盘图标,这张光盘默认被自动加载到了文件夹/media/cdom0,而且/cdrom自动指向这个文件夹。默认设置下文件管理器会自动打开这张光盘,可以看到里面有个”VBoxLinuxAdditions.run”文件。打开一个命令行终端,依次输入”cd /cdrom”和”sudo sh ./VBoxLinuxAdditions.run”,不含双引号,开始安装工具包。安装完毕,会用英文提示要重启Ubuntu,建议立刻重启。重启后,比较明显的变化是鼠标是共享模式,并且剪贴板也和Windows共享了。如果有这些变化,说明虚拟电脑工具包已经装成功。

2). 下一步设置共享文件夹。

在共享文件夹设置窗口中,单击右侧的”添加一个共享文件夹”,路径选择你想要共享的Windows文件夹,共享名任取一个自己喜欢的,比如”myshare”,选项read-only是指是否只允许ubuntu读这个文件夹,请根据需要选择这个选项。

3). 在ubuntu下挂载这个共享文件夹:sudo mount -t vboxsf myshare /media/share
其中”myshare”是之前取的共享文件夹的名字,”/media/share”是要挂载到的目标文件.

3.7 自动挂载windows分区

每次开机访问windows分区都要运行mount命令显然太烦琐,为什么访问其他的linux分区不用使用mount命令呢?

其实,每次开机时,linux自动将需要挂载的linux分区挂载上了。那么我们是不是可以设定让linux在启动的时候也挂载我们希望挂载的分区,如windows分区,以实现文件系统的自动挂载呢?

这是完全可以的。在/etc目录下有个fstab文件,它里面列出了linux开机时自动挂载的文件系统的列表。我的/etc/fstab文件如下:

1
2
3
4
5
6
7
8
9
10
  /dev/hda2 / ext3 defaults 1 1        
 /dev/hda1 /boot ext3 defaults 1 2   
 none /dev/pts devpts gid=5,mode=620 0 0   
 none /proc proc defaults 0 0   
 none /dev/shm tmpfs defaults 0 0   
 /dev/hda3 swap swap defaults 0 0   
 /dev/cdrom /mnt/cdrom iso9660 noauto,codepage=936,iocharset=gb2312 0 0   
 /dev/fd0 /mnt/floppy auto noauto,owner,kudzu 0 0   
 /dev/hdb1 /mnt/winc vfat defaults,codepage=936,iocharset=cp936 0 0   
 /dev/hda5 /mnt/wind vfat defaults,codepage=936,iocharset=cp936 0 0

在/etc/fstab文件里,第一列是挂载的文件系统的设备名,第二列是挂载点,第三列是挂载的文件系统类型,第四列是挂载的选项,选项间用逗号分隔。第五六列不知道是什么意思,还望高手指点。

在最后两行是我手工添加的windows下的C;D盘,加了codepage=936和iocharset=cp936参数以支持中文文件名。参数defaults实际上包含了一组默认参数:

rw 以可读写模式挂载
suid 开启用户ID和群组ID设置位
dev 可解读文件系统上的字符或区块设备
exec 可执行二进制文件
auto 自动挂载
nouser 使一般用户无法挂载
async 以非同步方式执行文件系统的输入输出操作

大家可以看到在这个列表里,光驱和软驱是不自动挂载的,参数设置为noauto。(如果你非要设成自动挂载,你要确保每次开机时你的光驱和软驱里都要有盘,呵呵。)

3.8 .软连接、硬链接

可以用ln命令对一个已经存在的文件再建立一个新的连接,而不复制文件的内容。连接有软连接和硬连接之分,软连接又叫符号连接。它们各自的特点是:

硬连接:是给文件一个副本,原文件名和连接文件名都指向相同的物理地址。目录不能有硬连接;硬连接不能跨越文件系统(不能跨越不同的分区)文件在磁盘中只有一个拷贝,节省硬盘空间;

修改其中一个,与其连接的文件同时被修改。如果删除其中任意一个其余的文件将不受影响。

由于删除文件要在同一个索引节点属于唯一的连接时才能成功,因此可以防止不必要的误删除。

符号连接(软连接):用ln -s命令建立文件的符号连接符号连接是linux特殊文件的一种,作为一个文件,它的数据是它所连接的文件的路径名。类似windows下的快捷方式。

当然删除这个连接,也不会影响到源文件,但对连接文件的使用、引用都是直接调用源文件的。

具体关系可以看下图:

图片

图5:软链接和硬链接

从图上可以看出硬链接和软链接的区别:

1:硬链接原文件和新文件的inode编号一致。而软链接不一样。

2:对原文件删除,会导致软链接不可用,而硬链接不受影响。

3:对原文件的修改,软、硬链接文件内容也一样的修改,因为都是指向同一个文件内容的。

3.9.文件目录管理命令

磁盘和文件空间 :fdisk df du

文件目录与管理:cd pwd mkdir rmdir ls cp rm mv

查看文件内容 cat、tac、more、less、head 、tail

文件目录与权限 :chmod chown chgrp umask

文件查找:which、whereis、locate、find、find

图片

四、linux 应用

图片

标准的Linux系统一般都有一套都有称为应用程序的程序集,它包括文本编辑器、编程语言、X Window、办公套件、Internet工具和数据库等。

图片

五、linux内核参数优化

图片

内核参数是用户和系统内核之间交互的一个接口,通过这个接口,用户可以在系统运行的同时动态更新内核配置,而这些内核参数是通过Linux Proc文件系统存在的。因此,可以通过调整Proc文件系统达到优化Linux性能的目的。

VMware安装centos

http://mirrors.aliyun.com/centos/7.9.2009/isos/x86_64/

DVD是基本版本(推荐使用这个)

Everything是所有

minial是最小的。后面那个数字是大小。

展开全文 >>

测试

2021-04-07

测试

首先会根据需求文档编写测试用例,用于指导如何测试,以及那些输入条件预期得到什么结果(需求文档,测试用例文档)

\1. UI测试:这是最基础的测试步骤,主要是按照需求文档,点击页面,看各个页面是否正确打开,是否与需求一致

\2. 白盒测试:此项主要是开发人员的自测,因为此项需要知道代码内部的逻辑,对照需求文档测试各个条件分支是否如预期的执行

\3. 黑盒测试:主要是测试人员完成的,此项不需要知道程序代码的内部逻辑,只需要关注输入条件,得到的输出结果是否与需求文档和测试用例相符合

\4. 边界条件测试:主要是测试各种边界值的情况,比如,只能输入正数的场景,如果输入0、或者负数是否会有相关提示

\5. 兼容性测试:按照需求文档的要求,需要兼容的操作系统平台或者浏览器种类和版本,都需要测试

\6. 性能测试:此项主要是通过测试工具,编写测试脚本,录制测试步骤,让测试工具模拟大数据量,多用户并发的操作,看看系统是否能够应付得了

\7. 用户交付测试:这是最后一个测试步骤了,主要是用户按照业务场景来模拟测试了,测试通过之后,就可以交互试用了

灰度测试,就是在某项产品或应用正式发布前,选择特定人群试用,逐步扩大其试用者数量,以便及时发现和纠正其中的问题,由“灰”到“黑”

基本测试的时候注意:

\1. 正确数据

\2. 错误数据

\3. 边界数据

//写测试类的时候,注意测试类中只调用,不创建方法。

展开全文 >>

mybatis

2021-04-07

框架一般都是有配置文件的,看官网文档最好。

Mybatis的学习:https://mybatis.org/mybatis-3/zh/getting-started.html

一、简介

  1. 基础:JDBC/Mysql/java基础/Maven/Junit

  2. 本来是aoache的一个开源项目ibatis,现在存于github

MyBatis 是一款优秀的持久层框架

它支持自定义 SQL、存储过程以及高级映射

MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作

MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

  1. maven仓库下载/github下载

  2. 持久化

数据持久化:持久化就是将程序的数据在持久状态和瞬时状态转化的过程

内存:断电即失、价格昂贵,因为有一些对象不能丢掉,所以需要持久化。

持久化:数据库(JDBC)、io文件持久化

  1. 持久层

Dao层/Service层/Controller层

完成持久化工作的代码,层界限十分明显。

  1. MyBatis的存在意义:

方便,更容易上手,灵活

传统的JDBC代码太复杂,需要简化。Mybatis是一个框架,可以自动化生成

将数据存入到数据库中,sql和代码分离,提高了可维护性;提供映射标签,支持对象和数据库的orm字段关系对应;提供对象关系映射标签,支持对象关系组件维护;提供xml标签,支持编写sql。

二、写一个程序

思路:搭建环境–导入mybatis–编写代码–测试

1.mysql中创建数据库资源

2.idea中新建一个普通的maven项目

3.删除src目录(以后只需要在父工程下创建module就会继承父工程的所有maven依赖)

4.导入maven依赖:mysql/mybatis/junit

5.创建一个新的module。

(每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。有了 SqlSessionFactory,我们可以从中获得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。)

在新module下的resource下新建一个xml配置文件,在官网上找到相关的配置复制进去,environments中可以配置多个环境。配置的url可以连接到数据库中查找,?后可以添加相关的配置,&需要用&;转义。(中间不要加中文注释)

例如:jdbc:mysql://127.0.0.1:3306/jsh_erp?useUnicode=true&characterEncoding=utf-8&useCursorFetch=true&defaultFetchSize=500&allowMultiQueries=true&rewriteBatchedStatements=true&useSSL=false

也可以不使用 XML 构建 SqlSessionFactory

6.创建一个utils工具类(查看官方文档有具体方法)

静态初始化模块sqlSessionFactory,静态方法得到SqlSession

  1. 编写代码:

以前:实体类pojo/Dao接口/接口实现类

现在:接口实现类由原来的UserDaoImpl转变为一个Mapper配置文件

8.测试:

可能存在的错误:

在MapperRegistry中,类型接口是未知的(每一个Mapper.xml都需要在Mybatis核心配置文件中注册,这里的路径是/ 不能是.)

在maven的pom.xml中Build中配置resources,防止我们的资源导出失败的问题

绑定的接口错误:namespace中的包名要和Dao/Mapper接口中的名字一致(必须要用. 不能用/)

方法名不对:id就是对应namespace中的方法名

返回类型不对:resultType是sql语句执行的返回值

参数类型:parameterType

标签不要匹配错误

NullPointerException空指针异常是因为sqlSessionFactory搞了两个

输出的xml文件中存在乱码问题:设置为UTF-8

最后用junit测试

三、CRUD

#{} 相当于是占位符 全限定名 别名

#{} 是 占位符 :动态解析 -> 预编译 -> 执行 #{} 对应的变量会自动加上单引号 #{} 能防止sql 注入 #{} 默认值 arg0、arg1、arg2 或 0、 1

${} 是 拼接符 :动态解析 -> 编译 -> 执行 ${} 对应的变量不会加上单引号 ${} 不能防止sql 注入 ${} 默认值param1、param2、param3

使用:

1.能用 #{} 的地方就用 #{},尽量少用 ${}
2.表名作参数,或者order by 排序时用 ${}
3.传参时参数使用@Param(“”)注解,@Param注解的作用是给参数命名,参数命名后就能根据名字得到参数值(相当于又加了一层密),正确的将参数传入sql语句中(一般通过#{}的方式,${}会有sql注入的问题)。如下:

1
2
Role selectById(@Param("id") String id);
List<Role> selectByNameAndOrgId(@Param("name") String name, @Param("orgId") String orgId);

增删改需要提交事务:sqlSession.commit();

参数为int可以不用写

当报错的时候,读错要从后往前读

万能的Map:如果实体类中的属性有很多个,或者数据库的表/字段/参数过多时,可能直接传对象的属性不现实。所以可以使用map随意制造参数,通过键值对来传递(相当于是再new一个map,然后put参数,参数值,调用时候直接传map。会自动获取map参数对应的而参数值)。

Map传递参数,直接再sql中取出key即可。parameterType=“map”

对象传递参数,直接再sql中取对象的属性即可。parameterType=“object”

只有一个基本类型参数的情况下,可以直接再sql中取到。

多个参数可以用map,也可以用注解。

模糊查询:拼接参数的时候,防止sql注入的问题

1.java代码执行的时候,#{value} 参数中传递通配符 %*%

2.在sql拼接中使用通配符“%”+#{value}+“%”

四、配置解析

1.核心配置文件

Mybatis-config.xml(官方建议取这个名字)

configuration(配置)

properties(属性)

settings(设置)

typeAliases(类型别名)

typeHandlers(类型处理器)

objectFactory(对象工厂)

plugins(插件)

介绍可以参考:https://blog.csdn.net/wuyuxing24/article/details/89343951

MyBatis 允许你在映射语句执行过程中(这个过程就是sqlsession)的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可

利用插件可以完成 分页,动态填入update_by,update_time,create_by,create_time,del_flag字段,拦截sql语句使增删改功能不生效 等功能。

例子:拦截sql语句使增删改功能不生效

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
@Component     //将拦截器注册进spring
@Intercepts(@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})) //method可以指定拦截器拦截的类型:update和quere
public class PermissionSql implements Interceptor { //必须实现Interceptor接口才能完成拦截器的功能

@Override
public Object intercept(Invocation invocation) throws Throwable {//拦截器的逻辑在这个方法里写
Object[] args = invocation.getArgs();
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
String className = arg.getClass().getName();
System.out.println(i + " 参数类型:" + className);
if (arg instanceof MappedStatement) {
MappedStatement ms = (MappedStatement) arg;
SqlCommandType sqlCommandType = ms.getSqlCommandType();
System.out.println("操作类型是:" + sqlCommandType.toString());
if (Constant.VIRTUAL_OPENID.contains(UserUtil.getCurrentOpenid()) && (sqlCommandType == SqlCommandType.INSERT || sqlCommandType == SqlCommandType.UPDATE || sqlCommandType == SqlCommandType.DELETE)) {
System.out.println("不执行" + sqlCommandType.toString() + "语句");
return 0; //拦截的返回值为Interger类型,所以直接返回设置为return 0
}
}
}
return invocation.proceed();//不拦截返回invocation.proceed()
}

@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}

@Override
public void setProperties(Properties properties) {//这个方法里可以将拦截的sql语句设置自定义值

}

}

给数据模型的一些通用操作属性(如:创建人、创建时间、修改人、修改时间等)自动赋值

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
/**
* 拦截器作用:给各实体对象在增加、修改时,自动添加操作属性信息。
*/
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class }) })
public class OpeInfoInterceptor implements Interceptor{

public Object intercept(Invocation invocation) throws Throwable{
Object[] args = invocation.getArgs();

System.out.println("-----------参数拦截---------------------------------------------------");
System.out.println("02 当前线程ID:"+Thread.currentThread().getId());
//遍历处理所有参数,update方法有两个参数,参见Executor类中的update()方法。
for(int i=0;i<args.length;i++){
Object arg=args[i];
String className=arg.getClass().getName();
System.out.println(i + " 参数类型:"+className);

//第一个参数处理。根据它判断是否给“操作属性”赋值。
if(arg instanceof MappedStatement){//如果是第一个参数 MappedStatement
MappedStatement ms = (MappedStatement)arg;
SqlCommandType sqlCommandType = ms.getSqlCommandType();
System.out.println("操作类型:"+sqlCommandType);
if(sqlCommandType == SqlCommandType.INSERT || sqlCommandType==SqlCommandType.UPDATE){
//如果是“增加”或“更新”操作,则继续进行默认操作信息赋值。否则,则退出
continue;
}else{
break;
}
}

//第二个参数处理。(只有第二个程序才能跑到这)
if (arg instanceof Map){//如果是map,有两种情况:(1)使用@Param多参数传入,由Mybatis包装成map。(2)原始传入Map
System.out.println("这是一个包装过的类型!");
Map map=(Map)arg;
for (Object obj : map.values()){
setProperty(obj);
}
} else{//原始参数传入
setProperty(arg);
}
}

return invocation.proceed();

}

/**
* 为对象的操作属性赋值
* @param obj
*/
private void setProperty(Object obj){
try{
//TODO: 根据需要,将相关属性赋上默认值
BeanUtils.setProperty(obj, "createrUsername", "张三");
BeanUtils.setProperty(obj, "createDT", new Date());
}catch (IllegalAccessException e){
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}

public Object plugin(Object target) {
return Plugin.wrap(target, this);
}

}

分页例子参考这篇文章:https://blog.csdn.net/liang0000zai/article/details/102862284

environments(环境配置)

environment(环境变量)

T ransactionManager(事务管理器)

dataSource(数据源)

databaseIdProvider(数据库厂商标识)

mappers(映射器)

sqlsession

SqlSession是Mybatis最重要的构建之一,可以简单的认为Mybatis一系列的配置目的是生成类似 JDBC生成的Connection对象的SqlSession对象,这样才能与数据库开启“沟通”,通过SqlSession可以实现增删改查(当然现在更加推荐是使用Mapper接口形式)

SqlSessionInterceptor 是 SqlSessionTemplate 的内部类,目的只有一个,就是处理多个 session 的db操作!

所有请求都被 invoke() 拦截,从而做相应处理:

  1. 进入请求,先生成一个新的sqlSession,为本次db操作做准备;

  2. 通过反射调用请求进来的方法,将 sqlSession 回调,进行复杂查询及结果映射;

  3. 如果需要立即提交事务,do it;

  4. 如果出现异常,包装异常信息,重新抛出;

  5. 操作完成后,关闭本次session;

sqlSession 单例,并不会影响具体的db操作控制,所以不用担心session的线程安全问题

sql语句的where问题

where后面加< if >的标签:

​ 如果if存在的话,会直接连and,导致出现 where and 的错误语句

​ 如果if不存在的话,会直接多出来where,导致错误语句

1.给where 后面加上1=1,以后的条件都可以正常and

2.mybatis 使用where 标签将所有的查询条件包括在内。
mybatis 就会将where标签后面第一个and 去掉(where只会去掉第一个多出来的and 或者 or)

where 1=0: where 后面跟一个永远不可能成立的条件1<0 , 1=0,1=2 这条sql语句结果不会返回任何数据,只有表结构,可用于快速建表

sql语句的模糊查询字符串拼接

1.利用concat进行拼接

1
2
3
SELECT max(gateway_num) from gateway WHERE gateway_num LIKE  concat('%',#{num},'%')
//这条语句的返回值是gateway_num这一列的最大值。如果需要返回这一行的值:
SELECT * from gateway WHERE gateway_num = (SELECT max(gateway_num) from gateway WHERE gateway_num LIKE concat("GFYD",'%'))

2.动态sql中的bind(未测试)

1
2
3
4
5
6
7
8
9
10
List<Hospital> getHospitalLike(@Param("selectword") String selectword);
<select id="getHospitalLike" resultType="com.hand.hand.domain.Hospital">
<bind name="bindselectword" value="'%'+selectword+'%'"></bind>
SELECT *
FROM hospital
<if test="selectword!=null">
where hid=cast(#{selectword} as signed INTEGER ) OR hname like #{bindselectword}
OR grade like #{bindselectword}
</if>
</select>
  • mybatis

展开全文 >>

spring

2021-04-07

Spring

简介

Spring 就像一个大家族,有众多衍生产品例如 Boot,Security,JPA等等。但他们的基础都是Spring 的 IOC 和 AOP,IOC提供了依赖注入的容器,而AOP解决了面向切面的编程,然后在此两者的基础上实现了其他衍生产品的高级功能。

Spring MVC是基于 Servlet 的一个 MVC 框架,主要解决 WEB 开发的问题;

而Spring Boot 是基于Spring的一套快速开发整合包,Spring Boot遵循的也是约定优于配置原则,它的目的在于实现自动配置,降低项目搭建的复杂度;

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。

Spring是一个轻量级的**控制反转(IoC)和面向切面(AOP)**的容器框架。

Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。Spring的核心是控制反转(IoC)和面向切面(AOP)。简单来说,Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架。

初衷

1、JAVA EE开发应该更加简单。

2、使用接口而不是使用类,是更好的编程习惯。Spring将使用接口的复杂度几乎降低到了零。

3、为JavaBean提供了一个更好的应用配置框架。

4、更多地强调面向对象的设计,而不是现行的技术如JAVA EE。

5、尽量减少不必要的异常捕捉。

6、使应用程序更加容易测试。

目标

1、可以令人方便愉快的使用Spring。

2、应用程序代码并不依赖于Spring APIs。

3、Spring不和现有的解决方案竞争,而是致力于将它们融合在一起。

基本组成

1、最完善的轻量级核心框架。

2、通用的事务管理抽象层。

3、JDBC抽象层。

4、集成了Toplink, Hibernate, JDO, and iBATIS SQL Maps。

5、AOP功能。

6、灵活的MVC Web应用框架。

发展

Spring框架几乎涉及到了Java企业级服务开发的所有方面,也几乎针对所有开发常用的模式、中间件、数据库进行了整合适配。

当我们写一个业务把逻辑写死写出来是比较容易的,但是把这个逻辑提取成模式进而打包成一个框架来给大家使用,这是比较难的。因为我们只有经历过足够多的场景后才能提取出普适的功能框架,大部分人才能用上,而且我们需要针对核心功能开放出可配置的部分,满足小部分人进一步定制和扩展功能的需要。

Spring框架经历了几个阶段:

1.第一个阶段推出的Core、Security、Data是把单体应用开发服务好。不仅仅提供了便捷的数据库访问、Web MVC等必要功能,而且通过AOP、IOC两大利器让我们的程序内在能够做到低耦合可扩展。

2.第二个阶段推出的Boot的意义不仅仅是加速了开发效率而且能让我们的程序从可用变为好用,应用程序核心业务逻辑可能只有70%的工作量,要让程序在线上跑的愉快还有30%的监控日志打点等工作量需要去做。

3.第三个阶段推出的Cloud的意义在于推动了微服务架构的落地。让不具备开发微服务基础套件的小型互联网公司也能享受到免费的开箱即用的微服务解决方案。其实很多人不是看了微服务的架构思想去寻找解决方案,而是了解到了Spring Cloud才去了解微服务思想从而落地的。

4.目前属于第四个阶段,大力发展Cloud Dataflow(云数据流)+容器。Dataflow的思想是不管是做实时消息处理的服务还是临时运行的任务,都可以认为是服务的组件,如果可以有一套DSL来定义这些组件之间的交互方式,然后在容器中进行自由组合、部署、伸缩,那么架构会非常灵活。下图是Dataflow管理界面的一个示意图。

Spring的发展可以看到互联网架构的发展,Spring给我们带来相当多的技术启发,从软件设计模式的启发慢慢到了架构的启发,甚至我觉得Spring是为Java开发打造了架构风格的模板,接下去Spring继续发展2到3年有望成为架构标准

image-20210922142605980

在Spring4.x中增加了新的特性:如果类只提供了一个带参数的构造方法,则不需要对对其内部的属性写@Autowired注解,Spring会自动为你注入属性。

1
2
3
4
5
//查看spring的版本
String springVersion = SpringVersion.getVersion();
String springBootVersion = SpringBootVersion.getVersion();
System.out.println(springVersion);
System.out.println(springBootVersion);

只要用了spring框架,肯定到处都是@Autowired。4.3之后的功能,如果只有一个构造方法,自动用这个构造方法注入配合lombok的@RequiredArgsConstructor使用体验很好:

我们平时开发中的bean大部分都不写构造函数,系统默认一个无参构造函数,这就符合这一条件。

![在这里插入图片描述](https://img-blog.csdnimg.cn/2019101816003785.png

此时helloService已经注入了,但是有些人会说以前加个@Autowired就行了,现在还要加个构造方法,更麻烦了,这时可以使用lombok插件,类上加@AllArgsConstructor就行了。

在这里插入图片描述

在编写代码的时候,使用@Autowired注解是,发现IDE报的一个警告,如下:

img

Spring Team recommends “Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies”.

翻译:

Spring建议”总是在您的bean中使用构造函数建立依赖注入。总是使用断言强制依赖”。

这段代码警告原来的写法是:

@Autowired
private EnterpriseDbService service;
建议后写成下面的样子:

private final EnterpriseDbService service;
@Autowired
public EnterpriseDbController(EnterpriseDbService service) {
this.service = service;
}
奇怪,为何会有这样的建议。

我们知道:@Autowired 可以对成员变量、方法以及构造函数进行注释。那么对成员变量和构造函数进行注释又有什么区别呢?

@Autowired注入bean,相当于在配置文件中配置bean,并且使用setter注入。而对构造函数进行注释,就相当于是使用构造函数进行依赖注入了吧。莫非是这两种注入方法的不同。

以下是:@Autowired和构造方法执行的顺序解析

先看一段代码,下面的代码能运行成功吗?

@Autowired
private User user;
private String school;
public UserAccountServiceImpl(){
this.school = user.getSchool();
}
答案是不能。

因为Java类会先执行构造方法,然后再给注解了@Autowired 的user注入值,所以在执行构造方法的时候,就会报错。

报错信息可能会像下面:

Exception in thread “main” org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘…’ defined in file [….class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate […]: Constructor threw exception; nested exception is java.lang.NullPointerException
报错信息说:创建Bean时出错,出错原因是实例化bean失败,因为bean时构造方法出错,在构造方法里抛出了空指针异常。

解决办法是,使用构造器注入,如下:

private User user;
private String school;
@Autowired
public UserAccountServiceImpl(User user){
this.user = user;
this.school = user.getSchool();
}
可以看出,使用构造器注入的方法,可以明确成员变量的加载顺序。

PS:Java变量的初始化顺序为:静态变量或静态语句块–>实例变量或初始化语句块–>构造方法–>@Autowired

@Autowired和构造方法执行的顺序解析

先看一段代码,下面的代码能运行成功吗?

@Autowired
private User user;
private String school;

public UserAccountServiceImpl(){
    this.school = user.getSchool();
}

答案是不能。因为Java类会先执行构造方法,然后再给注解了@Autowired 的user注入值,所以在执行构造方法的时候,就会报错。
报错信息可能会像下面:

Exception in thread “main” org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘…’ defined in file [….class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate […]: Constructor threw exception; nested exception is java.lang.NullPointerException

报错信息说:创建Bean时出错,出错原因是实例化bean失败,因为bean时构造方法出错,在构造方法里抛出了空指针异常。

解决办法是,使用构造器注入,如下:

private User user;
private String school;

@Autowired
public UserAccountServiceImpl(User user){
    this.user = user;
    this.school = user.getSchool();
}

那么最开始Spring建议,为何要将成员变量加上final类型呢?

网上有解释如下:spring配置默认的bean的scope是singleton,也就是启动后一直有。通过设置bean的scope属性为prototype来声明该对象为动态创建。但是,如果你的service本身是singleton,注入只执行一次。

@Autowired本身就是单例模式,只会在程序启动时执行一次,即使不定义final也不会初始化第二次,所以这个final是没有意义的吧。

可能是为了防止,在程序运行的时候,又执行了一遍构造函数;

或者是更容易让人理解的意思,加上final只会在程序启动的时候初始化一次,并且在程序运行的时候不会再改变。

@Autowired

@Autowired 注释可以在 setter 方法中被用于自动连接 bean,就像 @Autowired 注释,容器,一个属性或者任意命名的可能带有多个参数的方法。

可以在属性中使用 @Autowired 注释来除去 setter 方法。当时使用 为自动连接属性传递的时候,Spring 会将这些传递过来的值或者引用自动分配给那些属性

@Component

踩到一个坑,有一个接口,在这个接口的实现类里,需要用到@Autowired注解,一时大意,没有在实现类上加上@Component注解,导致了Spring报错,找不到这个类

一旦使用关于Spring的注解出现在类里,例如我在实现类中用到了@Autowired注解,被注解的这个类是从Spring容器中取出来的,那调用的实现类也需要被Spring容器管理,加上@Component

1
2
3
4
5
6
@Component("conversionImpl")
//其实默认的spring中的Bean id 为 conversionImpl(首字母小写)
public class ConversionImpl implements Conversion {
@Autowired
private RedisClient redisClient;
}

介绍

开发中难免会遇到这个这个注解@Component

@Controller 控制器(注入服务)
用于标注控制层,相当于struts中的action层

@Service 服务(注入dao)
用于标注服务层,主要用来进行业务的逻辑处理

@Repository(实现dao访问)
用于标注数据访问层,也可以说用于标注数据访问组件,即DAO组件

@Component (把普通pojo实例化到spring容器中,相当于配置文件中的 )

泛指各种组件,就是说当我们的类不属于各种归类的时候(不属于@Controller、@Services等的时候),我们就可以使用@Component来标注这个类。

@ExceptionHandler

  1. 异常处理方式一. @ExceptionHandler
  2. 异常处理方式二. 实现HandlerExceptionResolver接口
  3. 异常处理方式三. @ControllerAdvice+@ExceptionHandler

spring的启动

Spring 是一个控制反转依赖管理的容器,作为 Java Web 的开发人员,基本没有不熟悉 Spring 技术栈的,尽管在依赖注入领域,Java Web 领域不乏其他优秀的框架,如 google 开源的依赖管理框架 guice,如 Jersey web 框架等。但 Spring 已经是 Java Web 领域使用最多,应用最广泛的 Java 框架。

此文将专注讲解如何在 Spring 容器启动时实现我们自己想要实现的逻辑。我们时常会遇到在 Spring 启动的时候必须完成一些初始化的操作,如创建定时任务,创建连接池等。

本文将介绍以下几种 Spring 启动监听方式:

  • Bean 构造函数方式
  • 使用 @PostConstruct 注解
  • 实现 InitializingBean 接口
  • 监听 ApplicationListener 事件
  • 使用 Constructor 注入方式
  • 实现 SpringBoot 的 CommandLineRunner 接口
  • SmartLifecycle 机制

spring装填bean

我们知道如果我们要在一个类使用spring提供的bean对象,我们需要把这个类注入到spring容器中,交给spring容器进行管理,但是在实际当中,我们往往会碰到在一个普通的Java类中,想直接使用spring提供的其他对象或者说有一些不需要交给spring管理,但是需要用到spring里的一些对象。如果这是spring框架的独立应用程序,我们通过

ApplicationContext ac = new FileSystemXmlApplicationContext(“applicationContext.xml”);
ac.getBean(“beanId”);

这样的方式就可以很轻易的获取我们所需要的对象。

但是往往我们所做的都是Web Application,这时我们启动spring容器是通过在web.xml文件中配置,这样就不适合使用上面的方式在普通类去获取对象了,因为这样做就相当于加载了两次spring容器,而我们想是否可以通过在启动web服务器的时候,就把Application放在某一个类中,我们通过这个类在获取,这样就可以在普通类获取spring bean对象了,让我们接着往下看

普通类调用Spring bean对象:

可以参考:http://412887952-qq-com.iteye.com/blog/1479445

这里有更多这方面的介绍,比较详细

下面介绍在springboot中是如何使用的

1.在Spring Boot可以扫描的包下

写的工具类为SpringUtil,实现ApplicationContextAware接口,并加入Component注解,让spring扫描到该bean

springutil:

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
package me.shijunjie.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringUtil implements ApplicationContextAware {

private static ApplicationContext applicationContext;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(SpringUtil.applicationContext == null) {
SpringUtil.applicationContext = applicationContext;
}
System.out.println("---------------------------------------------------------------------");

System.out.println("---------------------------------------------------------------------");

System.out.println("---------------me.shijunjie.util.SpringUtil------------------------------------------------------");

System.out.println("========ApplicationContext配置成功,在普通类可以通过调用SpringUtils.getAppContext()获取applicationContext对象,applicationContext="+SpringUtil.applicationContext+"========");

System.out.println("---------------------------------------------------------------------");
}

//获取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}

//通过name获取 Bean.
public static Object getBean(String name){
return getApplicationContext().getBean(name);
}

//通过class获取Bean.
public static <T> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
}

//通过name,以及Clazz返回指定的Bean
public static <T> T getBean(String name,Class<T> clazz){
return getApplicationContext().getBean(name, clazz);
}

}

复制代码

为了测试,我们再启动的时候先通过代码方式给spring容器中注入一个bean,入下所示

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package me.shijunjie.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import me.shijunjie.entity.Demo2;

@Configuration
public class BeanConfig {
@Bean(name="testDemo")
public Demo2 generateDemo() {
Demo2 demo = new Demo2();
demo.setId(12345);
demo.setName("test");
return demo;
}
}

复制代码

然后我们编写测试controller,并从刚才写的springutil中获取这个bean

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package me.shijunjie.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import me.shijunjie.util.SpringUtil;

@RestController
@RequestMapping("/application")
public class TestApplicationController {

@RequestMapping("/test1")
public Object testSpringUtil1() {
return SpringUtil.getBean("testDemo");
}

}

复制代码

测试

启动web应用,打开浏览器输入http://localhost:8080/application/test1,测试成功

img

2 不在Spring Boot的扫描包下方式一

这种情况处理起来也很简单,先编写SpringUtil类,同样需要实现接口:ApplicationContextAware,具体编码如下:

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
package me.shijunjie.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class SpringUtil2 implements ApplicationContextAware {

private static ApplicationContext applicationContext;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(SpringUtil2.applicationContext == null) {
SpringUtil2.applicationContext = applicationContext;
}
System.out.println("---------------------------------------------------------------------");

System.out.println("---------------------------------------------------------------------");

System.out.println("---------------me.shijunjie.util.SpringUtil------------------------------------------------------");

System.out.println("========ApplicationContext配置成功,在普通类可以通过调用SpringUtils.getAppContext()获取applicationContext对象,applicationContext="+SpringUtil2.applicationContext+"========");

System.out.println("---------------------------------------------------------------------");
}

//获取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}

//通过name获取 Bean.
public static Object getBean(String name){
return getApplicationContext().getBean(name);
}

//通过class获取Bean.
public static <T> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
}

//通过name,以及Clazz返回指定的Bean
public static <T> T getBean(String name,Class<T> clazz){
return getApplicationContext().getBean(name, clazz);
}

}

使用@Bean注解,在App.java类中将SpringUtil注解进

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
package me.shijunjie.controller;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.web.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;

import me.shijunjie.util.SpringUtil2;

@ComponentScan(basePackages={"me.shijunjie"}) // 扫描该包路径下的所有spring组件
/*@EnableJpaRepositories("me.shijunjie.dao") // JPA扫描该包路径下的Repositorie
*//*@EntityScan("me.shijunjie.entity") // 扫描实体类
*/@SpringBootApplication
@EnableScheduling
public class App extends SpringBootServletInitializer{
@Bean
public SpringUtil2 getSpringUtil2() {
return new SpringUtil2();
}

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

测试(使用热部署的需要重启服务器)

启动web应用,打开浏览器输入http://localhost:8080/application/test2,测试成功

img

除此以外,也可以在App.java中使用@Import进行导入。

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
package me.shijunjie.controller;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.web.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.scheduling.annotation.EnableScheduling;

import me.shijunjie.util.SpringUtil2;

@ComponentScan(basePackages={"me.shijunjie"}) // 扫描该包路径下的所有spring组件
/*@EnableJpaRepositories("me.shijunjie.dao") // JPA扫描该包路径下的Repositorie
*//*@EntityScan("me.shijunjie.entity") // 扫描实体类
*/@SpringBootApplication
@EnableScheduling
@Import(SpringUtil2.class)
public class App extends SpringBootServletInitializer{
/*@Bean
public SpringUtil2 getSpringUtil2() {
return new SpringUtil2();
}*/

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

跑出结果和上面相同

原始构造函数

如果没有 Spring 容器,不依赖于 Spring 的实现,回归 Java 类实现本身,我们可以在静态代码块,在类构造函数中实现相应的逻辑,Java 类的初始化顺序依次是静态变量 > 静态代码块 > 全局变量 > 初始化代码块 > 构造器。

比如,Log4j 的初始化,就是在 LogManager 的静态代码块中实现的:

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
static {

Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));
repositorySelector = new DefaultRepositorySelector(h);

String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,null);

if(override == null || "false".equalsIgnoreCase(override)) {
String configurationOptionStr = OptionConverter.getSystemProperty(DEFAULT_CONFIGURATION_KEY, null);
String configuratorClassName = OptionConverter.getSystemProperty(CONFIGURATOR_CLASS_KEY, null);

URL url = null;

if(configurationOptionStr == null) {
url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
if(url == null) {
url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
}
} else {
try {
url = new URL(configurationOptionStr);
} catch (MalformedURLException ex) {
url = Loader.getResource(configurationOptionStr);
}
}

if(url != null) {
LogLog.debug("Using URL ["+url+"] for automatic log4j configuration.");
try {
OptionConverter.selectAndConfigure(url, configuratorClassName,LogManager.getLoggerRepository());
} catch (NoClassDefFoundError e) {
LogLog.warn("Error during default initialization", e);
}
} else {
LogLog.debug("Could not find resource: ["+configurationOptionStr+"].");
}
} else {
LogLog.debug("Default initialization of overridden by " + DEFAULT_INIT_OVERRIDE_KEY + "property.");
}
}

比如在构造函数中实现相应的逻辑:

1
2
3
4
5
6
7
8
9
10
@Component
public class CustomBean {

@Autowired
private Environment env;

public CustomBean() {
env.getActiveProfiles();
}
}

这里考验一下各位,上面的代码是否可以正常运行。—— 不行,构造函数中的env将会发生NullPointException异常。这是因为在 Spring 中将先初始化 Bean,也就是会先调用类的构造函数,然后才注入成员变量依赖的 Bean(@Autowired和@Resource注解修饰的成员变量),注意@Value等注解的配置的注入也是在构造函数之后。

@PostConstruct

在 Spring 中,我们可以使用@PostConstruct在 Bean 初始化之后实现相应的初始化逻辑,@PostConstruct修饰的方法将在 Bean 初始化完成之后执行,此时 Bean 的依赖也已经注入完成,因此可以在方法中调用注入的依赖 Bean。

1
2
3
4
5
6
7
8
9
10
11
@Component
public class CustomBean {

@Autowired
private Environment env;

@PostConstruce
public void init() {
env.getActiveProfiles();
}
}

与@PostConstruct相对应的,如果想在 Bean 注销时完成一些清扫工作,如关闭线程池等,可以使用@PreDestroy注解:

1
2
3
4
5
6
7
8
9
10
11
@Component
public class CustomBean {

@Autowired
private ExecutorService executor = Executors.newFixedThreadPool(1)

@PreDestroy
public void destroy() {
env.getActiveProfiles();
}
}

InitializingBean

实现 Spring 的InitializingBean接口同样可以实现以上在 Bean 初始化完成之后执行相应逻辑的功能,实现InitializingBean接口,在afterPropertiesSet方法中实现逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class CustomBean implements InitializingBean {

private static final Logger LOG
= Logger.getLogger(InitializingBeanExampleBean.class);

@Autowired
private Environment environment;

@Override
public void afterPropertiesSet() throws Exception {
LOG.info(environment.getDefaultProfiles());
}
}

ApplicationListener

我们可以在 Spring 容器初始化的时候实现我们想要的初始化逻辑。这时我们就可以使用到 Spring 的初始化事件。Spring 有一套完整的事件机制,在 Spring 启动的时候,Spring 容器本身预设了很多事件,在 Spring 初始化的整个过程中在相应的节点触发相应的事件,我们可以通过监听这些事件来实现我们的初始化逻辑。Spring 的事件实现如下:

  • ApplicationEvent,事件对象,由 ApplicationContext 发布,不同的实现类代表不同的事件类型。
  • ApplicationListener,监听对象,任何实现了此接口的 Bean 都会收到相应的事件通知。实现了 ApplicationListener 接口之后,需要实现方法 onApplicationEvent(),在容器将所有的 Bean 都初始化完成之后,就会执行该方法。

与 Spring Context 生命周期相关的几个事件有以下几个:

  • ApplicationStartingEvent: 这个事件在 Spring Boot 应用运行开始时,且进行任何处理之前发送(除了监听器和初始化器注册之外)。
  • ContextRefreshedEvent: ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法来发生。
  • ContextStartedEvent: 当使用 ConfigurableApplicationContext 接口中的 start() 方法启动 ApplicationContext 时,该事件被触发。你可以查询你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。
  • ApplicationReadyEvent: 这个事件在任何 application/ command-line runners 调用之后发送。
  • ContextClosedEvent: 当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被触发。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启。
  • ContextStoppedEvent: Spring 最后完成的事件。

因此,如果我们想在 Spring 启动的时候实现一些相应的逻辑,可以找到 Spring 启动过程中符合我们需要的事件,通过监听相应的事件来完成我们的逻辑:

1
2
3
4
5
6
7
8
9
@Component
@Slf4j
public class StartupApplicationListenerExample implements ApplicationListener<ContextRefreshedEvent> {

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
log.info("Subject ContextRefreshedEvent");
}
}

除了通过实现ApplicationListener接口来监听相应的事件,Spring 的事件机制也实现了通过@EventListener注解来监听相对应事件:

1
2
3
4
5
6
7
8
9
@Component
@Slf4j
public class StartupApplicationListenerExample {

@EventListener
public void onApplicationEvent(ContextRefreshedEvent event) {
log.info("Subject ContextRefreshedEvent");
}
}

Spring Event 是一套完善的进程内事件发布订阅机制,我们除了用来监听 Spring 内置的事件,也可以使用 Spring Event 实现自定义的事件发布订阅功能。

Constructor 注入

在学习 Spring 的注入机制的时候,我们都知道 Spring 可以通过构造函数、Setter 和反射成员变量注入等方式。上面我们在成员变量上通过@Autoware注解注入依赖 Bean,但是在 Bean 的构造函数函数中却无法使用到注入的 Bean(因为 Bean 还未注入),其实我们也是使用 Spring 的构造函数注入方式, 这也是 Spring 推荐的注入机制(在我们使用 IDEA 的时候,如果没有关闭相应的代码 Warning 机制,会发现在成员变量上的@Autoware是黄色的,也就是 idea 不建议的代码)。Spring 更推荐构造函数注入的方式:

1
2
3
4
5
6
7
8
9
10
11
12
@Component
@Slf4j
public class ConstructorBean {

private final Environment environment;

@Autowired
public LogicInConstructorExampleBean(Environment environment) {
this.environment = environment;
log.info(Arrays.asList(environment.getDefaultProfiles()));
}
}

CommandLineRunner

如果我们的项目使用的是 Spring Boot,那么可以使用 Spring Boot 提供的 CommandLineRunner 接口来实现初始化逻辑,Spring Boot 将在启动初始化完成之后调用实现了CommandLineRunner的接口的run方法:

1
2
3
4
5
6
7
8
9
@Component
@Slf4j
public class CommandLineAppStartupRunner implements CommandLineRunner {

@Override
public void run(String...args) throws Exception {
log.info("Increment counter");
}
}

并且,多个CommandLineRunner实现,可以通过@Order来控制它们的执行顺序。

SmartLifecycle

还有一种更高级的方法来实现我们的逻辑。这可以 Spring 高级开发必备技能哦。SmartLifecycle 不仅仅能在初始化后执行一个逻辑,还能再关闭前执行一个逻辑,并且也可以控制多个 SmartLifecycle 的执行顺序,就像这个类名表示的一样,这是一个智能的生命周期管理接口。

  • start():bean 初始化完毕后,该方法会被执行。
  • stop():容器关闭后,spring 容器发现当前对象实现了 SmartLifecycle,就调用 stop(Runnable), 如果只是实现了 Lifecycle,就调用 stop()。
  • isRunning:当前状态,用来判你的断组件是否在运行。
  • getPhase:控制多个 SmartLifecycle 的回调顺序的,返回值越小越靠前执行 start() 方法,越靠后执行 stop() 方法。
  • isAutoStartup():start 方法被执行前先看此方法返回值,返回 false 就不执行 start 方法了。
  • stop(Runnable):容器关闭后,spring 容器发现当前对象实现了 SmartLifecycle,就调用 stop(Runnable), 如果只是实现了 Lifecycle,就调用 stop()。
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
@Component
public class SmartLifecycleExample implements SmartLifecycle {

private boolean isRunning = false;

@Override
public void start() {
System.out.println("start");
isRunning = true;
}

@Override
public int getPhase() {
// 默认为 0
return 0;
}

@Override
public boolean isAutoStartup() {
// 默认为 false
return true;
}

@Override
public boolean isRunning() {
// 默认返回 false
return isRunning;
}

@Override
public void stop(Runnable callback) {
System.out.println("stop(Runnable)");
callback.run();
isRunning = false;
}

@Override
public void stop() {
System.out.println("stop");

isRunning = false;
}

}

Spring @Aspect、@Before、@After 注解实现 AOP 切面功能

Spring AOP 注解概述
1、Spring 的 AOP 功能除了在配置文件中配置一大堆的配置,比如切入点、表达式、通知等等以外,使用注解的方式更为方便快捷,特别是 Spring boot 出现以后,基本不再使用原先的 beans.xml 等配置文件了,而都推荐注解编程。

@Aspect 切面声明,标注在类、接口(包括注解类型)或枚举上。
@Pointcut 切入点声明,即切入到哪些目标类的目标方法。
value 属性指定切入点表达式,默认为 “”,用于被通知注解引用,这样通知注解只需要关联此切入点声明即可,无需再重复写切入点表达式
@Before 前置通知, 在目标方法(切入点)执行之前执行。
value 属性绑定通知的切入点表达式,可以关联切入点声明,也可以直接设置切入点表达式
注意:如果在此回调方法中抛出异常,则目标方法不会再执行,会继续执行后置通知 -> 异常通知。
@After 后置通知, 在目标方法(切入点)执行之后执行
@AfterReturning 返回通知, 在目标方法(切入点)返回结果之后执行,在 @After 的后面执行
pointcut 属性绑定通知的切入点表达式,优先级高于 value,默认为 “”
@AfterThrowing 异常通知, 在方法抛出异常之后执行, 意味着跳过返回通知
pointcut 属性绑定通知的切入点表达式,优先级高于 value,默认为 “”
注意:如果目标方法自己 try-catch 了异常,而没有继续往外抛,则不会进入此回调函数
@Around 环绕通知:目标方法执行前后分别执行一些代码,类似拦截器,可以控制目标方法是否继续执行。
通常用于统计方法耗时,参数校验等等操作。
环绕通知早于前置通知,晚于返回通知
2、上面这些 AOP 注解都是位于如下所示的 aspectjweaver 依赖中:

img

3、对于习惯了 Spring 全家桶编程的人来说,并不是需要直接引入 aspectjweaver 依赖,因为 spring-boot-starter-aop 组件默认已经引用了 aspectjweaver 来实现 AOP 功能。换句话说 Spring 的 AOP 功能就是依赖的 aspectjweaver !

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.1.4.RELEASE</version>
<dependency>

@Aspect 快速入门
1、@Aspect 常见用于记录日志、异常集中处理、权限验证、Web 参数校验、事务处理等等

2、要想把一个类变成切面类,只需3步:

1)在类上使用 @Aspect 注解使之成为切面类

2)切面类需要交由 Sprign 容器管理,所以类上还需要有 @Service、@Repository、@Controller、@Component 等注解
2)在切面类中自定义方法接收通知

3、AOP 的含义就不再累述了,下面直接上示例:

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
import org.apache.commons.lang3.time.StopWatch;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.SourceLocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;

/**

* 切面注解 Aspect 使用入门

* 1、@Aspect:声明本类为切面类

* 2、@Component:将本类交由 Spring 容器管理

* 3、@Order:指定切入执行顺序,数值越小,切面执行顺序越靠前,默认为 Integer.MAX_VALUE
*

* @author wangMaoXiong

* @version 1.0

* @date 2020/8/20 19:22
*/
@Aspect
@Order(value = 999)
@Component
public class AspectHelloWorld {
private static final Logger LOG = LoggerFactory.getLogger(AspectHelloWorld.class);

/**

* @Pointcut :切入点声明,即切入到哪些目标方法。value 属性指定切入点表达式,默认为 ""。

* 用于被下面的通知注解引用,这样通知注解只需要关联此切入点声明即可,无需再重复写切入点表达式

* <p>

* 切入点表达式常用格式举例如下:

* - * com.wmx.aspect.EmpService.*(..)):表示 com.wmx.aspect.EmpService 类中的任意方法

* - * com.wmx.aspect.*.*(..)):表示 com.wmx.aspect 包(不含子包)下任意类中的任意方法

* - * com.wmx.aspect..*.*(..)):表示 com.wmx.aspect 包及其子包下任意类中的任意方法

* </p>

* value 的 execution 可以有多个,使用 || 隔开.
*/
@Pointcut(value =
"execution(* com.wmx.hb.controller.DeptController.*(..)) " +
"|| execution(* com.wmx.hb.controller.EmpController.*(..))")
private void aspectPointcut() {

}

/**

* 前置通知:目标方法执行之前执行以下方法体的内容。

* value:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式

* <br/>

* * @param joinPoint:提供对连接点处可用状态和有关它的静态信息的反射访问<br/> <p>

* * * Object[] getArgs():返回此连接点处(目标方法)的参数,目标方法无参数时,返回空数组

* * * Signature getSignature():返回连接点处的签名。

* * * Object getTarget():返回目标对象

* * * Object getThis():返回当前正在执行的对象

* * * StaticPart getStaticPart():返回一个封装此连接点的静态部分的对象。

* * * SourceLocation getSourceLocation():返回与连接点对应的源位置

* * * String toLongString():返回连接点的扩展字符串表示形式。

* * * String toShortString():返回连接点的缩写字符串表示形式。

* * * String getKind():返回表示连接点类型的字符串

* * * </p>
*/
@Before(value = "aspectPointcut()")
public void aspectBefore(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
Signature signature = joinPoint.getSignature();
Object target = joinPoint.getTarget();
Object aThis = joinPoint.getThis();
JoinPoint.StaticPart staticPart = joinPoint.getStaticPart();
SourceLocation sourceLocation = joinPoint.getSourceLocation();
String longString = joinPoint.toLongString();
String shortString = joinPoint.toShortString();

LOG.debug("【前置通知】" +
"args={},signature={},target={},aThis={},staticPart={}," +
"sourceLocation={},longString={},shortString={}"
, Arrays.asList(args), signature, target, aThis, staticPart, sourceLocation, longString, shortString);
}

/**

* 后置通知:目标方法执行之后执行以下方法体的内容,不管目标方法是否发生异常。
* value:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式
*/
@After(value = "aspectPointcut()")
public void aspectAfter(JoinPoint joinPoint) {
LOG.debug("【后置通知】kind={}", joinPoint.getKind());
}

/**

* 返回通知:目标方法返回后执行以下代码
* value 属性:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式
* pointcut 属性:绑定通知的切入点表达式,优先级高于 value,默认为 ""
* returning 属性:通知签名中要将返回值绑定到的参数的名称,默认为 ""
*
* @param joinPoint :提供对连接点处可用状态和有关它的静态信息的反射访问
* @param result :目标方法返回的值,参数名称与 returning 属性值一致。无返回值时,这里 result 会为 null.
*/
@AfterReturning(pointcut = "aspectPointcut()", returning = "result")
public void aspectAfterReturning(JoinPoint joinPoint, Object result) {
LOG.debug("【返回通知】,shortString={},result=", joinPoint.toShortString(), result);
}

/**

* 异常通知:目标方法发生异常的时候执行以下代码,此时返回通知不会再触发
* value 属性:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式
* pointcut 属性:绑定通知的切入点表达式,优先级高于 value,默认为 ""
* throwing 属性:与方法中的异常参数名称一致,
*
* @param ex:捕获的异常对象,名称与 throwing 属性值一致
*/
@AfterThrowing(pointcut = "aspectPointcut()", throwing = "ex")
public void aspectAfterThrowing(JoinPoint jp, Exception ex) {
String methodName = jp.getSignature().getName();
if (ex instanceof ArithmeticException) {
LOG.error("【异常通知】" + methodName + "方法算术异常(ArithmeticException):" + ex.getMessage());
} else {
LOG.error("【异常通知】" + methodName + "方法异常:" + ex.getMessage());
}
}

/**

* 环绕通知

* 1、@Around 的 value 属性:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式

* 2、Object ProceedingJoinPoint.proceed(Object[] args) 方法:继续下一个通知或目标方法调用,返回处理结果,如果目标方法发生异常,则 proceed 会抛异常.

* 3、假如目标方法是控制层接口,则本方法的异常捕获与否都不会影响目标方法的事务回滚

* 4、假如目标方法是控制层接口,本方法 try-catch 了异常后没有继续往外抛,则全局异常处理 @RestControllerAdvice 中不会再触发
*

* @param joinPoint

* @return

* @throws Throwable
*/
@Around(value = "aspectPointcut()")
public Object handleControllerMethod(ProceedingJoinPoint joinPoint) throws Throwable {
this.checkRequestParam(joinPoint);

StopWatch stopWatch = StopWatch.createStarted();
LOG.debug("【环绕通知】执行接口开始,方法={},参数={} ", joinPoint.getSignature(), Arrays.asList(joinPoint.getArgs()).toString());
//继续下一个通知或目标方法调用,返回处理结果,如果目标方法发生异常,则 proceed 会抛异常.
//如果在调用目标方法或者下一个切面通知前抛出异常,则不会再继续往后走.
Object proceed = joinPoint.proceed(joinPoint.getArgs());

stopWatch.stop();
long watchTime = stopWatch.getTime();
LOG.debug("【环绕通知】执行接口结束,方法={}, 返回值={},耗时={} (毫秒)", joinPoint.getSignature(), proceed, watchTime);
return proceed;
}

/**

* 参数校验,防止 SQL 注入
*
* @param joinPoint
*/
private void checkRequestParam(ProceedingJoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
if (args == null || args.length <= 0) {
return;
}
String params = Arrays.toString(joinPoint.getArgs()).toUpperCase();
String[] keywords = {"DELETE ", "UPDATE ", "SELECT ", "INSERT ", "SET ", "SUBSTR(", "COUNT(", "DROP ",
"TRUNCATE ", "INTO ", "DECLARE ", "EXEC ", "EXECUTE ", " AND ", " OR ", "--"};
for (String keyword : keywords) {
if (params.contains(keyword)) {
LOG.warn("参数存在SQL注入风险,其中包含非法字符 {}.", keyword);
throw new RuntimeException("参数存在SQL注入风险:params=" + params);
}
}
}
}

img

如上所示在不修改原来业务层代码的基础上,就可以使用 AOP 功能,在目标方法执行前后或者异常时都能捕获然后执行。

execution 切点表达式
1、@Pointcut 切入点声明注解,以及所有的通知注解都可以通过 value 属性或者 pointcut 属性指定切入点表达式。

2、切入点表达式通过 execution 函数匹配连接点,语法:execution([方法修饰符] 返回类型 方法路径(参数类型) [异常类型])

3、切入点表达式的写法比较灵活,比如:* 号表示任意一个,.. 表示任意多个,还可以使用 &&、||、! 进行逻辑运算.实际开发中常用:

execution(* com.wmx.aspect.EmpServiceImpl.findEmpById(Integer)) 匹配 com.wmx.aspect.EmpService 类中的 findEmpById 方法,且带有一个 Integer 类型参数。
execution(* com.wmx.aspect.EmpServiceImpl.findEmpById(*)) 匹配 com.wmx.aspect.EmpService 类中的 findEmpById 方法,且带有一个任意类型参数。
execution(* com.wmx.aspect.EmpServiceImpl.findEmpById(..)) 匹配 com.wmx.aspect.EmpService 类中的 findEmpById 方法,参数不限
execution(* grp.basic3.se.service.SEBasAgencyService3.editAgencyInfo(..)) || execution(* grp.basic3.se.service.SEBasAgencyService3.adjustAgencyInfo(..)) 匹配 editAgencyInfo 方法或者 adjustAgencyInfo 方法
execution(* com.wmx.aspect.EmpService.*(..)) 匹配 com.wmx.aspect.EmpService 类中的任意方法
execution(* com.wmx.aspect..(..)) 匹配 com.wmx.aspect 包(不含子包)下任意类中的任意方法
execution(* com.wmx.aspect...(..)) 匹配 com.wmx.aspect 包及其子包下任意类中的任意方法
execution(* grp.pm..Controller.(..)) 匹配 grp.pm 包下任意子孙包中以 “Controller” 结尾的类中的所有方法
  • execution:用于匹配方法执行的连接点;
  • within:用于匹配指定类型内的方法执行;
  • this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
  • target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
  • args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;
  • @within:用于匹配所以持有指定注解类型内的方法;
  • @target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
  • @args:用于匹配当前执行的方法传入的参数持有指定注解的执行;
  • @annotation:用于匹配当前执行方法持有指定注解的方法;

SpEL表达式

SpEL(Spring Expression Language),即Spring表达式语言,是比JSP的EL更强大的一种表达式语言

它可以在运行时查询和操作数据,尤其是数组列表型数据,因此可以缩减代码量,优化代码结构

SpEL有三种用法,一种是在注解@Value中;一种是XML配置;最后一种是在代码块中使用Expression

1.@Value

1
2
3
4
//@Value能修饰成员变量和方法形参
//#{}内就是表达式的内容
@Value("#{表达式}")
public String arg;

2.配置

1
2
3
4
<bean id="xxx" class="com.java.XXXXX.xx">
<!-- 同@Value,#{}内是表达式的值,可放在property或constructor-arg内 -->
<property name="arg" value="#{表达式}">
</bean>
  1. 代码块中使用
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
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class SpELTest {

public static void main(String[] args) {

//创建ExpressionParser解析表达式
ExpressionParser parser = new SpelExpressionParser();
//表达式放置
Expression exp = parser.parseExpression("表达式");
//执行表达式,默认容器是spring本身的容器:ApplicationContext
Object value = exp.getValue();

/**如果使用其他的容器,则用下面的方法*/
//创建一个虚拟的容器EvaluationContext
StandardEvaluationContext ctx = new StandardEvaluationContext();
//向容器内添加bean
BeanA beanA = new BeanA();
ctx.setVariable("bean_id", beanA);

//setRootObject并非必须;一个EvaluationContext只能有一个RootObject,引用它的属性时,可以不加前缀
ctx.setRootObject(XXX);

//getValue有参数ctx,从新的容器中根据SpEL表达式获取所需的值
Object value = exp.getValue(ctx);
}
}

4.#{…}和${…}

  • #{…} 用于执行SpEl表达式,并将内容赋值给属性
  • ${…} 主要用于加载外部属性文件中的值
  • #{…} 和${…} 可以混合使用,但是必须#{}外面,${}在里面
1
2
3
4
5
6
7
// 如果属性文件没有spelDefault.value,则会报错
// @Value("${spelDefault.value}")
// private String spelDefault2;

// 使用default.value设置值,如果不存在则使用默认值
@Value("${spelDefault.value:127.0.0.1}")
private String spelDefault;
1
2
3
4
5
6
7
// SpEL:调用字符串Hello World的concat方法
@Value("#{'Hello World'.concat('!')}")
private String helloWorld;

// SpEL: 调用字符串的getBytes方法,然后调用length属性
@Value("#{'Hello World'.bytes.length}")
private String helloWorldbytes;
1
2
3
4
5
6
7
8
9
10
${...}和#{...}可以混合使用,如下文代码执行顺序:通过${server.name}从属性文件中获取值并进行替换,然后就变成了 执行SpEL表达式{‘server1,server2,server3’.split(‘,’)}

// SpEL: 传入一个字符串,根据","切分后插入列表中, #{}和${}配置使用(注意单引号,注意不能反过来${}在外面,#{}在里面)
@Value("#{'${server.name}'.split(',')}")
private List<String> servers;

// SpEL: 注意不能反过来${}在外面,#{}在里面,这个会执行失败
@Value("${#{'HelloWorld'.concat('_')}}")
private List<String> servers2;
因为spring执行${}是时机要早于#{}。在本例中,Spring会尝试从属性中查找#{‘HelloWorld’.concat(‘_’)},那么肯定找不到,由上文已知如果找不到,然后报错。所以${}在外面,#{}在里面是非法操作
  • spring

展开全文 >>

VUE

2021-04-07

VUE简介

Vue.js(读音 /vjuː/, 类似于 view) 是一套构建用户界面的渐进式框架。

Vue 只关注视图层, 采用自底向上增量开发的设计。

Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

VUE安装

1.独立版本

我们可以在 Vue.js 的官网上直接下载最新版本, 并用 <script> 标签引入。

2.使用 CDN 方法

以下推荐国外比较稳定的两个 CDN,国内还没发现哪一家比较好,目前还是建议下载到本地。

  • Staticfile CDN(国内) : https://cdn.staticfile.org/vue/3.0.5/vue.global.js
  • unpkg:https://unpkg.com/vue@next, 会保持和 npm 发布的最新的版本一致。
  • cdnjs : https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.5/vue.global.js

3.NPM 方法

因为需要使用 npm 安装 Vue CLI,而 npm 是集成在 Node.js 中的,所以第一步我们需要安装 Node.js。在官网下载安装完node.js后,查看node版本。同时npm也安装好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查看版本
$ npm -v
2.3.0

#将 npm 更新至最新版本
npm -g install npm

#安装cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org

# 升级或安装 cnpm
npm install cnpm -g

#升级 npm
cnpm install npm -g

在用 Vue.js 构建大型应用时推荐使用 cnpm 安装,cnpm 能很好地和 Webpack 或 Browserify 模块打包器配合使用:

1
2
# 最新稳定版
$ cnpm install vue@next

命令行工具

Vue.js 提供一个官方命令行工具,可用于快速搭建大型单页应用。

1
2
3
4
5
6
7
# 全局安装 vue-cli
$ cnpm install -g @vue/cli
npm install -g vue-cli

# 安装完后查看版本
$ vue --version
@vue/cli 4.5.11

注意:vue-cli 3.x 和 vue-cli 2.x 使用了相同的 vue 命令,如果你之前已经安装了 vue-cli 2.x,它会被替换为 Vue-cli 3.x。

安装 @vue/cli-int:

1
$ cnpm i -g @vue/cli-init

node版本

npm版本

vue版本

vue-cli版本

前端版本会有不兼容问题

注意:Vue.js 不支持 IE8 及其以下 IE 版本。

4.安装yarn

1.使用msi安装:

首先进入yarn的官网 https://yarn.bootcss.com/docs/install/#windows-stable,然后选择稳定版进行下载。然后双击安装即可

2.通过npm命令进行安装:

执行命令npm install yarn -g (后面的可选参数-g,g代表global全局安装的意思)的安装语句时,会将安装的模块安装到C:\Users\用户名\AppData\Roaming\npm路径中。

yarn和npm都是包管理工具,但是yarn是崭新的,经过重新设计的npm客户端,于2016年10月发布,相比于npm,yarn在运行速度上有显著的提升,安装时间变少,功能上也有很多改进

创建VUE项目

直接使用命令行构建项目

1.新建一个文件夹,自行选择位置。

2.文件夹里打开cmd,输入 vue init webpack 项目名字 (这里 webpack 是以 webpack 为模板指生成项目,还可以替换为 pwa、simple 等参数)

接下来会让你确认项目名字,描述,作者,build,是否安装vue-router(选是Y),是否安装es-lint(代码质量)

3.可以看到文件夹里生成了VUE项目

4.在项目里打开命令行,执行命令 npm run dev

使用webstorm

按步骤新建项目。可以在控制台上输入命令。也可以在package.json 文件上点击右键,选择 show npm scripts,双击命令运行。也可以在右上角选择npm命令运行。

VUE的文件

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
vue-cource // 项目名称
|__ node_modules // 项目中的node依赖包
|__ public // public文件夹放公共文件
| |__ favicon.ico // 标签栏的小图标
| |__ index.html // 模板文件,最后打包后的index.html模板就是这个文件
|
|__ src // 项目主文件
| |__ api // api文件夹,项目的接口js文件都可以写在这里
| |__ assets // 静态资源(图片、字体图标文件)
| | |__ img // 图片文件夹
| | |__ font // 字体图标文件夹
| |
| |__ components // 组件
| |__ config // 项目的配置(全局变量js文件)
| | |__ index.js
| |
| |__ directive // vue的自定义指令文件夹
| | |__ index.js
| |
| |__ lib // 全局方法放在这里
| | |__ util.js // 与业务结合的工具方法
| | |__ tools.js // 与业务无关的工具方法(例如日期转换时间戳方法)
| |
| |__ mock // 模拟数据文件夹
| | |__ index.js
| |
| |__ router // 路由文件夹
| | |__ index.js // 路由配置文件
| | |__ router.js // 拆分出来的routes数组
| |__ store // Vuex配置文件夹
| | |__ module // 模块文件夹
| | |__ plugin // Vuex插件文件夹
| | |__ actions.js // 异步调用接口方法都写在这里
| | |__ getters.js // Vuex的计算属性都写在这里
| | |__ index.js // Vuex主要配置管理文件
| | |__ mutations.js // Vuex中的修改state的方法,都写在这里
| | |__ state.js // Vuex的变量文件夹
| |
| |__ views // 页面都写在这个文件夹中
| |__ App.vue // 基础组件。项目入口文件,我们也可以直接将组件写这里,而不使用 components 目录。
| |__ main.js // 项目入口文件(项目引入全局插件都在这里引入)项目的核心文件
|
|__ .browserslistrc // 浏览器兼容
|__ .editorconfig // 编译器配置文件
|__ .eslintrc.js // 配置ESlint规则文件
|__ .gitignore // git提交的忽略文件
|__ babel.config.js // babel的配置文件,babel可以将es6、es7等等装换成es5兼容的代码
|__ package-lock.json // 锁定安装时的包的版本号,并且需要上传到git,以保证其他人在npm install时大家的依赖能保证一致
|__ package.json // 项目名称、项目版本、项目描述、项目运行的一些脚本(依赖)
|__ postcss.config.js // css自动补充一些兼容性代码的配置(-webkit-、-moz-、-ms-之类的)
|__ README.en.md // 英文版项目说明
|__ README.md // 中文版项目说明
|__ vue.config.js // 配置文件

public/index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover">
<title></title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

就是一个普普通通的 html 文件,让它不平凡的是 <div id="app"></div> ,下面有一行注释,构建的文件将会被自动注入,也就是说我们编写的其它的内容都将在这个 div 中展示。

还有不普通的一点是,整个项目只有这一个 html 文件,所以这是一个 单页面应用,当我们打开这个应用,表面上可以有很多页面,实际上它们都只不过在一个 div 中。

src/App.vue

这个文件称为“根组件”,因为其它的组件又都包含在这个组件中

vue 文件是一种自定义文件类型,在结构上类似 html,一个 .vue 文件即是一个 vue 组件

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
<template>
<div id="app" v-wechat-title="$route.meta.title">
<router-view />
</div>
</template>

<script>
export default {
name: 'App'
}
</script>

<style>
body {
-webkit-font-smoothing: antialiased;
padding: 0;
margin: 0;
font-family: 'PingFang SC';
height: 100%;

}
#app {
height: 100%;
}
</style>

这里的<div id="app">,id=app 只是跟下面的 css 对应.

  • 所有文章
  • 关于我

tag:

  • 杂谈
  • 经历
  • 与人沟通
  • 项目合作
  • 搜索技巧
  • 百度
  • 谷歌
  • 书
  • 收藏
  • 打包
  • swagger
  • springfox
  • mybatis
  • Portainer.io
  • Typora
  • 快捷键
  • 压缩算法
  • PBOOTCMS
  • Eclipse
  • 新环境
  • 软件
  • 软件说明
  • WebRTC
  • 类型转换
  • JVM
  • 接口
  • 抽象类
  • 内部类
  • if
  • 逻辑判断
  • for循环
  • Tomcat
  • http
  • vite
  • WEB
  • servlet
  • JSP
  • JPA
  • json
  • 数据库
  • SQL
  • 小技巧
  • 小科普
  • git
  • Gitee
  • hexo
  • node.js
  • ElasticSearch
  • 基础概念
  • 面向对象
  • mysql
  • 索引
  • 锁
  • 事务
  • Navicat
  • JQuery
  • mqtt
  • 代理
  • 工具类代码
  • 轮子
  • 消息中间件
  • 设计规范
  • 编程规范
  • RESTful
  • HATEOAS
  • java概述
  • 历史版本
  • 安装
  • MySQL基础概念
  • HTML
  • 日志
  • PO-BO-VO,DTO-DAO与POJO
  • spring
  • maven
  • C
  • IDEA
  • 插件
  • 微信
  • Redis
  • ngnix
  • MyBatis-Plus
  • npm
  • vue
  • SpringBoot

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

一起加油吧!