开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

【Spring Cloud Alibaba系列】 Nacos

发表于 2021-10-08
启动Nacos服务

分别进入3台机器的bin目录,执行sh startup.sh或者startup.cmd -m cluster 命令启动服务。服务启动成功后,在${nacos_home}/logs/start.out下可获取日志,表示服务启动成功。

1
erlang复制代码INFO Nacos started successfully in cluster mode.

通过http://${nacos_cluster_ip}:8848/nacos来访问Nacos控制台,在节点列表下可看到集群由哪些节点组成及节点的状态。

Nacos实现原理

通过官方的Nacos架构图来分析具体模块组成

image-20211003215359631.png

  • Provider APP:服务提供者
  • Consumer APP:服务消费者
  • Nacos Server:Nacos 服务提供者,包含OpenAPI(功能访问入口)、Config Service(配置服务)、Naming Service(名字服务)、Consistency Protocol(一致性协议,用来实现Nacos集群节点数据同步、注意使用了Raft算法)
  • Nacos Console:Nacos 控制台

总体上,服务提供者通过Virtual IP 访问Nacos Server高可用集群,基于OpenAPI完成服务的注册和服务的查询,Nacos Server可支持主备模式,因此底层采用数据一致性算法来完成节点之间的数据同步,而服务消费者也是同理。

注册中心原理

服务注册功能主要体现在如下方面:

  • 服务实例在启动时注册到服务注册表,并在关闭时注销。
  • 服务消费者查询服务注册表,获得可用实例。
  • 服务注册中心需要调用服务实例的健康检查API来验证它是否能够处理请求。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

基于java实现学科竞赛管理系统【Springboot+my

发表于 2021-10-08

本文正在参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

java精品毕设【大学生学科竞赛项目管理系统.mp4】
)​

论文设计参考:

一、 绪论 7

1.1 研究背景 7

1.2研究目标 7

1.3项目的可行性研究设计 8

二、 需求分析 8

2.1系统功能概述 8

2.2系统运行环境 8

2.3功能需求描述 9

三、 系统设计 9

3.1开发与设计的总体思想 9

3.2系统模块总体架构构图 9

3.3模块设计 10

3.4系统流程描述 10

3. 5项目源码架构 11

四、 系统实现 12

4.1 程序主要类 12

4.1.1用户实体类 13

4.1.2菜单类 14

4.1.3角色类 15

4.1.4项目申请类 16

4.1.4项目经费类 17

4.1.5项目公告类 17

4.2 主要框架和技术介绍 17

4.2.1 spring 18

4.2.2 SpringBoot简介 18

4.2.2 mybatis 18

4.2.2 jQuery 19

4.2.2 element UI 19

4.3系统功能主要实现模块截图 19

4.3.1登陆页面 19

4.3.2 主页面 21

4.3.4部分关键源码展示: 28

4.4数据库主要表设计 31

4.4.0数据库表ER图 32

4.4.1用户表设计 32

4.4.2角色表设计 32

4.4.3菜单表设计 33

4.4.4竞赛公告表设计 33

4.4.5竞赛项目表设计 33

4.4.6竞赛项目报名表设计 34

4.4.7竞赛项目经费表设计 34

4.4.8个人赛报名表设计 34

4.4.8团队赛报名表设计 35

4.4.6数据库sql文件 35

五.系统开发总结心得与体会 75

六、参考献文 76

**系统功能设计**

(1)登录:输入账号密码和验证码登录;

(2)用户信息模块

(3)菜单模块

(4)角色模块

(5)项目竞赛活动申请模块

(6)项目竞赛经费申请模块

(7)项目竞赛活动管理审批模块

(8)项目个人赛报名模块

(9)项目团队赛报名模块

(10)项目结题统计模块

(11)通知公告模块

)​​

主要功能截图:

​​

用户登录:输入帐号密码和验证码登录。登录后,根据用户权限显示不同的菜单,灵活控制角色。

)​

管理员功能模块:用户管理、竞赛报名信息管理、经费申请信息和x审核信息管理、项目结题信息、管理角色管理、菜单管理、权限管理、、立项申请管理等具体模块管理

)​​

用户管理:用户列表显示,数据的添加,以及删除修改等。

​​

)​​

角色管理:将角色与用户绑定,灵活控制角色菜单,显示菜单权限。可以创建多个角色

​​

菜单消息管理:

​​

系统日志监控:aop切面编程。实现日志记录操作。

)​​

业务模块功能

立项申请功能

)​​​

立项审核:

)​​

活动经费、预算申请

)​​

)​​

立项申请个人赛以及团队赛管理,录入报名相关信息字段

)​​

)​​

)​​

项目结题管理信息:

​​

项目完成之后、选择项目进行资金使用统计结题。录入金额

​​

)​​

项目结题统计:

​​

数据库表设计:

用户表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sql复制代码CREATE TABLE `NewTable` (
`user_id` bigint(20) NOT NULL AUTO_INCREMENT ,
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名' ,
`salt` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '盐' ,
`email` varchar(1
`password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码' ,
00) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱' ,
`mobile` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号' ,
`status` tinyint(4) NULL DEFAULT NULL COMMENT '状态 0:禁用 1:正常' ,
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间' ,
PRIMARY KEY (`user_id`),
UNIQUE INDEX `username` (`username`) USING BTREE
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8mb4_general_ci
COMMENT='系统用户'
AUTO_INCREMENT=3
ROW_FORMAT=COMPACT

菜单表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sql复制代码CREATE TABLE `NewTable` (
`menu_id` bigint(20) NOT NULL AUTO_INCREMENT ,
`parent_id` bigint(20) NULL DEFAULT NULL COMMENT '父菜单ID,一级菜单为0' ,
`name` varchar(50) SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '菜单名称' ,
`url` varchar(200) SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '菜单URL' ,
`perms` varchar(500) SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '授权(多个用逗号分隔,如:user:list,user:create)' ,
`type` int(11) NULL DEFAULT NULL COMMENT '类型 0:目录 1:菜单 2:按钮' ,
`icon` varchar(50) SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '菜单图标' ,
`order_num` int(11) NULL DEFAULT NULL COMMENT '排序' ,
PRIMARY KEY (`menu_id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8mb4_general_ci
COMMENT='菜单管理'
AUTO_INCREMENT=69
ROW_FORMAT=COMPACT
;

立项申请:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
sql复制代码CREATE TABLE `NewTable` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`name` varchar(255) SET utf8 COLLATE NULL NULL ,
`zsdw` varchar(255) SET utf8 COLLATE NULL ,
`type` varchar(255) SET utf8 COLLATE utf8_general_ci NULL NULL ,
`principal` varchar(255) SET utf8 COLLATE NULL DEFAULT NULL ,
`phone` varchar(255) SET utf8 COLLATE NULL NULL ,
`start_time` datetime NULL DEFAULT NULL ,
`ent_time` datetime NULL DEFAULT NULL ,
`majozhuban` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
`sponsor` varchar(255) SET utf8 COLLATE utf8_general_ci NULL NULL ,
`undertaeker` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL NULL ,
`remark` varchar(255) SET utf8 COLLATE utf8_general_ci NULL NULL ,
`audit_stu` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL NULL ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
ROW_FORMAT=COMPACT
;

个人赛:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sql复制代码CREATE TABLE `NewTable` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`name` varchar(255) SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
`college` varchar(255) SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
`class_name` varchar(255) SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
`grade` varchar(255) SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
`major` varchar(255) SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
`email` varchar(255) SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
`phone` varchar(255) SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
`title` varchar(255) SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
AUTO_INCREMENT=12
ROW_FORMAT=COMPACT
;

经费申请:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sql复制代码CREATE TABLE `NewTable` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`zhuche` double NULL NULL ,
`rate` double NULL NULL ,
`train` double NULL NULL ,
`guidance` double NULL NULL ,
`haocai` double NULL NULL ,
`bonus` double NULL NULL ,
`other` double NULL ,
`total` double NULL NULL ,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL NULL ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
AUTO_INCREMENT=10
ROW_FORMAT=COMPACT
;

主要代码实现:

**用户权限过滤:**

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
java复制代码
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
shiroFilter.setLoginUrl("/login.html");
shiroFilter.setUnauthorizedUrl("/");

filterMap.put("/statics/**", "anon");
filterMap.put("/login.html", "anon");
filterMap.put("/sys/login", "anon");
filterMap.put("/favicon.ico", "anon");
filterMap.put("/captcha.jpg", "anon");
filterMap.put("/**", "authc");
shiroFilter.setFilterChainDefinitionMap(filterMap);

Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/swagger/**", "anon");
filterMap.put("/v2/api-docs", "anon");
filterMap.put("/swagger-ui.html", "anon");
filterMap.put("/webjars/**", "anon");
filterMap.put("/swagger-resources/**", "anon");



return shiroFilter;
}

**登录模块验证:**

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
java复制代码/**
* 登录
*/
@ResponseBody
@RequestMapping(value = "/sys/login")
public R login(String username, String password, String captcha) {
String kaptcha = ShiroUtils.getKaptcha(Constants.KAPTCHA_SESSION_KEY);
if(!captcha.equalsIgnoreCase(kaptcha)){
return R.error("验证码不正确。。。。。。");
}

try{
Subject subject = ShiroUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);//md5+Jiayan
subject.login(token);
}catch (UnknownAccountException e) {
return R.error(e.getMessage());
}catch (IncorrectCredentialsException e) {
return R.error("账号或密码不正确。。。。");
}catch (LockedAccountException e) {
return R.error("账号已被锁定,请联系管理。。。员");
}catch (AuthenticationException e) {
return R.error("账户验证失败。。。。");
}

return R.ok();
}

**统一异常处理:**

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
java复制代码

/**
* 异常处理器
*
* @author Mark sunlightcs@gmail.com
*/
@RestControllerAdvice
public class RRExceptionHandler {
private Logger logger = LoggerFactory.getLogger(getClass());

/**
* 处理自定义异常
*/
@ExceptionHandler(RRException.class)
public R handleRRException(RRException e){
R r = new R();
r.put("code", e.getCode());
r.put("msg", e.getMessage());

return r;
}



@ExceptionHandler(AuthorizationException.class)
public R handleAuthorizationException(AuthorizationException e){
logger.error(e.getMessage(), e);
return R.error("没有权限,请联系管理员授权");
}

@ExceptionHandler(Exception.class)
public R handleException(Exception e){
logger.error(e.getMessage(), e);
return R.error();
}

@ExceptionHandler(DuplicateKeyException.class)
public R handleDuplicateKeyException(DuplicateKeyException e){
logger.error(e.getMessage(), e);
return R.error("数据库中已存在该记录");
}
}

分页查询

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
java复制代码

package io.renren.common.utils;

import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.renren.common.xss.SQLFilter;
import org.apache.commons.lang.StringUtils;

import java.util.Map;

/**
* 查询参数
*
* @author Mark sunlightcs@gmail.com
*/
public class Query<T> {

public IPage<T> getPage(Map<String, Object> params) {
return this.getPage(params, null, false);
}

public IPage<T> getPage(Map<String, Object> params, String defaultOrderField, boolean isAsc) {
//分页参数
long curPage = 1;
long limit = 10;

//分页对象
Page<T> page = new Page<>(curPage, limit);

//分页参数
params.put(Constant.PAGE, page);

if(params.get(Constant.PAGE) != null){
curPage = Long.parseLong((String)params.get(Constant.PAGE));
}
if(params.get(Constant.LIMIT) != null){
limit = Long.parseLong((String)params.get(Constant.LIMIT));
}



//排序字段
//防止SQL注入(因为sidx、order是通过拼接SQL实现排序的,会有SQL注入风险)
String orderField = SQLFilter.sqlInject((String)params.get(Constant.ORDER_FIELD));
String order = (String)params.get(Constant.ORDER);

//前端字段排序
if(StringUtils.isNotEmpty(orderField) && StringUtils.isNotEmpty(order)){
if(Constant.ASC.equalsIgnoreCase(order)) {
return page.addOrder(OrderItem.asc(orderField));
}else {
return page.addOrder(OrderItem.desc(orderField));
}
}

//没有排序字段,则不排序
if(StringUtils.isBlank(defaultOrderField)){
return page;
}

//默认排序
if(isAsc) {
page.addOrder(OrderItem.asc(defaultOrderField));
}else {
page.addOrder(OrderItem.desc(defaultOrderField));
}

return page;
}
}

好了,今天就到这儿吧,我是小奥、下期见~~

大家点赞、收藏、关注、评论啦 、

打卡 文章 更新 70/ 100天

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

面试官一口气问了MySQL事务、锁和MVCC,我

发表于 2021-10-08

面试官:你是怎么理解InnoDB引擎中的事务的?

候选者:在我的理解下,事务可以使「一组操作」要么全部成功,要么全部失败

候选者:事务其目的是为了「保证数据最终的一致性」。

候选者:举个例子,我给你发支付宝转了888块红包。那自然我的支付宝余额会扣减888块,你的支付宝余额会增加888块。

候选者:而事务就是保证我的余额扣减跟你的余额增添是同时成功或者同时失败的,这样这次转账就正常了

面试官:嗯,那你了解事务的几大特性吗?

候选者:嗯,就是ACID嘛,分别是原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。

候选者:原子性指的是:当前事务的操作要么同时成功,要么同时失败。原子性由undo log日志来保证,因为undo log记载着数据修改前的信息。

候选者:比如我们要 insert 一条数据了,那undo log 会记录的一条对应的 delete 日志。我们要 update 一条记录时,那undo log会记录之前的「旧值」的update记录。

候选者:如果执行事务过程中出现异常的情况,那执行「回滚」。InnoDB引擎就是利用undo log记录下的数据,来将数据「恢复」到事务开始之前

候选者:一致性我稍稍往后讲,我先来说下隔离性

面试官:嗯…

候选者:隔离性指的是:在事务「并发」执行时,他们内部的操作不能互相干扰。如果多个事务可以同时操作一个数据,那么就会产生脏读、重复读、幻读的问题。

候选者:于是,事务与事务之间需要存在「一定」的隔离。在InnoDB引擎中,定义了四种隔离级别供我们使用:

候选者:分别是:read uncommit(读未提交)、read commit (读已提交)、repeatable read (可重复复读)、serializable (串行)

候选者:不同的隔离级别对事务之间的隔离性是不一样的(级别越高事务隔离性越好,但性能就越低),而隔离性是由MySQL的各种锁来实现的,只是它屏蔽了加锁的细节。

候选者:持久性指的就是:一旦提交了事务,它对数据库的改变就应该是永久性的。说白了就是,会将数据持久化在硬盘上。

候选者:而持久性由redo log 日志来保证,当我们要修改数据时,MySQL是先把这条记录所在的「页」找到,然后把该页加载到内存中,将对应记录进行修改。

候选者:为了防止内存修改完了,MySQL就挂掉了(如果内存改完,直接挂掉,那这次的修改相当于就丢失了)。

候选者:MySQL引入了redo log,内存写完了,然后会写一份redo log,这份redo log记载着这次在某个页上做了什么修改。

候选者:即便MySQL在中途挂了,我们还可以根据redo log来对数据进行恢复。

候选者:redo log 是顺序写的,写入速度很快。并且它记录的是物理修改(xxxx页做了xxx修改),文件的体积很小,恢复速度也很快。

候选者:回头再来讲一致性,「一致性」可以理解为我们使用事务的「目的」,而「隔离性」「原子性」「持久性」均是为了保障「一致性」的手段,保证一致性需要由应用程序代码来保证

候选者:比如,如果事务在发生的过程中,出现了异常情况,此时你就得回滚事务,而不是强行提交事务来导致数据不一致。

面试官:嗯,挺好的,讲了蛮多的

面试官:刚才你也提到了隔离性嘛,然后你说在MySQL中有四种隔离级别,能分别来介绍下吗?

候选者:嗯,为了讲清楚隔离级别,我顺带来说下MySQL锁相关的知识吧。

候选者:在InnoDB引擎下,按锁的粒度分类,可以简单分为行锁和表锁。

候选者:行锁实际上是作用在索引之上的(索引上次已经说过了,这里就不赘述了)。当我们的SQL命中了索引,那锁住的就是命中条件内的索引节点(这种就是行锁),如果没有命中索引,那我们锁的就是整个索引树(表锁)。

候选者:简单来说就是:锁住的是整棵树还是某几个节点,完全取决于SQL条件是否有命中到对应的索引节点。

候选者:而行锁又可以简单分为读锁(共享锁、S锁)和写锁(排它锁、X锁)。

候选者:读锁是共享的,多个事务可以同时读取同一个资源,但不允许其他事务修改。写锁是排他的,写锁会阻塞其他的写锁和读锁。

候选者:我现在就再回到隔离级别上吧,就直接以例子来说明啦。

面试官:嗯…

候选者:首先来说下read uncommit(读未提交)。比如说:A向B转账,A执行了转账语句,但A还没有提交事务,B读取数据,发现自己账户钱变多了!B跟A说,我已经收到钱了。A回滚事务【rollback】,等B再查看账户的钱时,发现钱并没有多。

候选者:简单的定义就是:事务B读取到了事务A还没提交的数据,这种用专业术语来说叫做「脏读」。

候选者:对于锁的维度而言,其实就是在read uncommit隔离级别下,读不会加任何锁,而写会加排他锁。读什么锁都不加,这就让排他锁无法排它了。

候选者:而我们又知道,对于更新操作而言,InnoDB是肯定会加写锁的(数据库是不可能允许在同一时间,更新同一条记录的)。而读操作,如果不加任何锁,那就会造成上面的脏读。

候选者:脏读在生产环境下肯定是无法接受的,那如果读加锁的话,那意味着:当更新数据的时,就没办法读取了,这会极大地降低数据库性能。

候选者:在MySQL InnoDB引擎层面,又有新的解决方案(解决加锁后读写性能问题),叫做MVCC(Multi-Version Concurrency Control)多版本并发控制

候选者:在MVCC下,就可以做到读写不阻塞,且避免了类似脏读这样的问题。那MVCC是怎么做的呢?

候选者:MVCC通过生成数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取

候选者:回到事务隔离级别下,针对于 read commit (读已提交) 隔离级别,它生成的就是语句级快照,而针对于repeatable read (可重复读),它生成的就是事务级的快照。

候选者:前面提到过read uncommit隔离级别下会产生脏读,而read commit (读已提交) 隔离级别解决了脏读。思想其实很简单:在读取的时候生成一个”版本号”,等到其他事务commit了之后,才会读取最新已commit的”版本号”数据。

候选者:比如说:事务A读取了记录(生成版本号),事务B修改了记录(此时加了写锁),事务A再读取的时候,是依据最新的版本号来读取的(当事务B执行commit了之后,会生成一个新的版本号),如果事务B还没有commit,那事务A读取的还是之前版本号的数据。

候选者:通过「版本」的概念,这样就解决了脏读的问题,而「版本」其实就是对应快照的数据。

候选者:read commit (读已提交) 解决了脏读,但也会有其他并发的问题。「不可重复读」:一个事务读取到另外一个事务已经提交的数据,也就是说一个事务可以看到其他事务所做的修改。

候选者:不可重复读的例子:A查询数据库得到数据,B去修改数据库的数据,导致A多次查询数据库的结果都不一样【危害:A每次查询的结果都是受B的影响的】

候选者:了解MVCC基础之后,就很容易想到repeatable read (可重复复读)隔离级别是怎么避免不可重复读的问题了(前面也提到了)。

候选者:repeatable read (可重复复读)隔离级别是「事务级别」的快照!每次读取的都是「当前事务的版本」,即使当前数据被其他事务修改了(commit),也只会读取当前事务版本的数据。

候选者:而repeatable read (可重复复读)隔离级别会存在幻读的问题,「幻读」指的是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。

候选者:在InnoDB引擎下的的repeatable read (可重复复读)隔离级别下,快照读MVCC影响下,已经解决了幻读的问题(因为它是读历史版本的数据)

候选者:而如果是当前读(指的是 select * from table for update),则需要配合间隙锁来解决幻读的问题。

候选者:剩下的就是serializable (串行)隔离级别了,它的最高的隔离级别,相当于不允许事务的并发,事务与事务之间执行是串行的,它的效率最低,但同时也是最安全的。

面试官:嗯,可以的。我看你提到了MVCC了,不妨来说下他的原理?

候选者:MVCC的主要是通过read view和undo log来实现的

候选者:undo log前面也提到了,它会记录修改数据之前的信息,事务中的原子性就是通过undo log来实现的。所以,有undo log可以帮我们找到「版本」的数据

候选者:而read view 实际上就是在查询时,InnoDB会生成一个read view,read view 有几个重要的字段,分别是:trx_ids(尚未提交commit的事务版本号集合),low_limit_id(下一次要生成的事务ID值),low_limit_id(尚未提交版本号的事务ID最小值)以及creator_trx_id(当前的事务版本号)

候选者:在每行数据有两列隐藏的字段,分别是DB_TRX_ID(记录着当前ID)以及DB_ROLL_PTR(指向上一个版本数据在undo log 里的位置指针)

候选者:铺垫到这了,很容易就发现,MVCC其实就是靠「比对版本」来实现读写不阻塞,而版本的数据存在于undo log中。

候选者:而针对于不同的隔离级别(read commit和repeatable read),无非就是read commit隔离级别下,每次都获取一个新的read view,repeatable read隔离级别则每次事务只获取一个read view

面试官:嗯,OK的。细节就不考究了,今天就到这里吧。

本文总结:

  • 事务为了保证数据的最终一致性
  • 事务有四大特性,分别是原子性、一致性、隔离性、持久性
+ 原子性由undo log保证
+ 持久性由redo log 保证
+ 隔离性由数据库隔离级别供我们选择,分别有read uncommit,read commit,repeatable read,serializable
+ 一致性是事务的目的,一致性由应用程序来保证
  • 事务并发会存在各种问题,分别有脏读、重复读、幻读问题。上面的不同隔离级别可以解决掉由于并发事务所造成的问题,而隔离级别实际上就是由MySQL锁来实现的
  • 频繁加锁会导致数据库性能低下,引入了MVCC多版本控制来实现读写不阻塞,提高数据库性能
  • MVCC原理即通过read view 以及undo log来实现

欢迎关注我的微信公众号【Java3y】来聊聊Java面试


【对线面试官-移动端】系列 一周两篇持续更新中!

【对线面试官-电脑端】系列 一周两篇持续更新中!

原创不易!!求三连!!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

国产Linux发行版系统再添一员,颜值完全不输苹果!

发表于 2021-10-08

当今市面上Linux操作系统的种类实在是太多了,各种各样的Linux发行版及其衍生系统数不胜数,常常让人看得眼花缭乱。

在我们之前《常用Linux发行版操作系统大盘点》那篇文章里,我们曾经就以包管理器的角度,把当下使用比较主流的一些Linux发行版系统做过一个简单分类:

包管理器名称 常用代表性指令 代表系统举例
DPKG dpkg、apt Debain、Ubuntu 等
RPM rpm、yum RedHat、CentOS 等
Pacman pacman Arch、Manjaro 等
DNF dnf Fedora 等
Zypper zypper SUSE 等
Portage emerge Gentoo 等

因为单谈Linux的概念实在是太泛了。

严格来讲,Linux只能说是一种操作系统的内核,所以我们通常所说的 “Linux操作系统” 一般指的是采用Linux作为内核的操作系统,有很多发行版,包括商业发行版本和免费发行版本。

本文在GitHub开源仓库「编程之路」 github.com/rd2coding/R… 中已经收录,里面有我整理的6大编程方向(岗位)的自学路线+知识点大梳理、面试考点、我的简历、几本硬核pdf笔记,以及我的程序员人生,欢迎star。


说回到国产Linux操作系统,其实有很多优秀的代表,典型被大家熟知的比如大名鼎鼎的Deepin和Kylin都是不错的选择。

正好前几天在网上又看到一款非常火热的开源Linux桌面系统,名为CutefishOS,最近讨论得挺多的。

实不相瞒,当时第一眼看到的时候主要就被CutefishOS的界面所吸引住了,所以忍不住进去多看了几眼。

乍看起来,感觉这界面有一股浓郁的果味,第一眼看差点被当成macOS没分辨出来。

CutefishOS这个系统有一个很便捷的设计那就是在界面顶部有所谓的全局菜单,上面集中了某个应用程序的所有功能,节省屏幕空间的同时还能完成各项操作需求。

除此之外,界面底部的Dock栏,就更颇有几分macOS的味道了,连图标都有内味,它也带来的是很多便捷的交互和体验。


CutefishOS是一款具有现代风格设计与实用的 Linux 桌面操作系统,也是基于开源的 Linux 内核打造的。

按照官方的说法,其在参考了大量优秀的移动端设计的基础上,CutefishOS可以带来更好的用户使用体验,不管是用户交互还是视觉效果,都非常的简洁高效。比如像浅色和深色模式,各种系统设置选项,文件、文件夹以及资源管理器操作等等。

这还不算,重要的是各方面细节拉满,不管是图标还是界面元素,颜值都是杠杠的。


除此之外,CutefishOS还内置了大量的常用应用,常见的像文本编辑器、浏览器、文件管理器、计算器、Terminal终端命令行、Setting设置等,尤其是一些原生的 CuteFish 应用,用户可以获得更加统一的操作界面和交互体验。

而且目前来说类似于像网易云音乐、常见输入法、WPS、网盘、编辑器、常见浏览器等常用的办公、娱乐、开发软件也都支持,也有不少开源爱好者积极参与着其生态的建设。


抱着好奇心去它的论坛里看了一下,发现有不少Linux爱好者在CutefishOS论坛里发帖交流,目前来看还是有不少尝鲜的人,于此同时也提出了不少问题和建议。


目前官网的下载菜单还没放下载链接,不过倒是有一些第三方社区各自打包的版本,有兴趣的同学可以尝试一下。

https://cutefish-ubuntu.github.io/download/

最后也附上CutefishOS的GitHub托管地址:

https://github.com/cutefishos

本文在GitHub开源仓库「编程之路」 github.com/rd2coding/R… 中已经收录,里面有我整理的6大编程方向(岗位)的自学路线+知识点大梳理、面试考点、我的简历、几本硬核pdf笔记,以及我的程序员人生,部分内容如下↓↓↓,欢迎star。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

不就是搭个博客吗?其实很简单的

发表于 2021-10-07

本文已参与「掘力星计划」赢取创作大礼包,挑战创作激励金。

你好呀,我是歪歪。

中秋节的时候我不是发了一篇叫做《终于,我通宵把自己的博客怼上线了!!!》的文章嘛。

www.whywhy.vip/

有好多小伙伴都来问我关于搭建博客的事情,这篇文章就来写一写吧,分享一下我的经验。

首先说一下我弄博客的大概的步骤吧。

  • 1.买服务器。
  • 2.找博客模板。
  • 3.买域名,完成域名备案。

然后总费用是 985.8 元。

其中大头是服务器的钱 811.8 元,但是我找了一个老铁,用的他的邀请链接,然后我自己找了一个阿里云新号买服务器,他把返现都给了我。

这样其实实际花的钱是 835 元的样子,其中服务器是大头,占了 660 元。

听起来还挺贵的,但是你看:

2C4G 3M 宽带的配置用 3 年啊,朋友。

3 年 660 元,一年就是 220 元,按照 365 天算,一天只要 6 毛钱。再说了,2024 年还是闰年啊,一年可是 366 天,白赚一天。

这波四舍五入相当于不要钱。

只是说三年后续费就显得稍微贵一点了:

但是没关系啊,到时候再搞个新号重新再买三年不就好了,把博客做个迁移也是很简单的事情。

白嫖怪实锤了。

服务器

好了,扯远了。

先说服务器在哪买。

我卖的是阿里云的服务器,当然了,你也可以去看华为云、百度云或者腾讯云都可以。

而且,我这里也没有阿里云返现,当时没有报名参加这个活动,是我疏忽了。所以你也别找我要什么邀请链接了,我也没有。

我就以阿里云为例了。

打开下面的网站:

www.aliyun.com/activity/da…

我买的就是这一款:

按照下面的配置,算出来的价格就是 811.8 元:

但是前提是你得是阿里云的新用户才能享受这个价格。

接下来就是创建订单,立即购买就完事了。

然后你就可以登录阿里云控制台,找到云服务器 ECS 的选项,看到自己的公网IP:

接着你就可以搞个链接工具,比如 xshell 连接到服务器上去,方便后续操作:

在这步操作之前,应该是有一个叫你设置账号密码的过程,你自己设置好就行。

xshell 链接的默认端口是 22,所以记得在安全组里面把该端口打开:

包括你后面要搭建博客的时候涉及到的博客相关的接口都需要打开,打开之后再重启一下防火墙,就不再赘述了。

选博客模板

现在我们服务器有了,就只是需要把自己的博客放到服务器上去就行。

但是选择什么模板呢?

比较出名的就有 Wordpress、Hexo、Jekyll、halo 等等。

甚至我买的阿里云服务器里面就有现成的搭建 Wordpress 博客的教程,跟着教程一步步的操作就可以了:

但是我最终选择的是 halo 博客。

halo.run/

因为它的快速开始功能太快速了,真的是快儿他妈给快儿开门,快到家了。快到我都拍手叫好了。

比如我在 windows 系统下,只需要下载他们的 jar 包,然后直接 run 起来,本地就可以访问了。

但是需要注意的是,从 1.4.3 开始,Halo 最低支持的 JRE 版本为 11,所以如果你本地跑不起来,请升级一下版本。

然后你访问 http://127.0.0.1:8090 就是博客地址。

访问 http://127.0.0.1:8090/admin 就是博客的管理后台,文章发布、主题优化什么的都在管理后台里面。

是真正的开箱即用。

我第一次试验的时候,全程不超过 1 分钟就搭建好了。

当即就拍板,决定就用 halo 了。

你也可以在本地,试一下,看看有没有惊艳到你。

然后你也可以在本地试验一下它的主题功能,目前只有 22 个开源的主题,但是也很不错了,基本上可以选择出一款自己喜欢的主题。

唯一美中不足的地方就是毕竟是别人封装好了的主题,肯定有一些自己不太喜的地方。如果自己想改造,那么需要一定的前端技能。

但是,都已经是白嫖了,还要啥自行车啊,跑起来再说。

其实,在本地都能跑起来了,搭建博客就是把这一套东西仍到服务器上而已,没有什么非常困难的地方。

而把博客的这个 jar 扔到服务器上去执行这个操作,在 halo 的官方文档上也算是写的非常的清晰明白了。

docs.halo.run/zh/install/…

我选用的是 Linux 的方式:

这里面直接是手摸手的傻瓜式教学,每一步干啥都给你写的清清楚楚的。

只是需要提示一下这个地方,安全组、安全组、安全组:

另外,我还发现了这样一个神奇的地方:

我之前没发现,写这篇文章的时候看到了,就点进去看了一眼,我都愣住了。

这是交互式教学啊,牛逼了!

从官方的介绍上看,是 halo 和腾讯云有合作,所以出了这个交互式教程,大家可以看看:

好,就算你连这个交互式教学都不想去弄,告诉你一个终极操作:

docs.halo.run/install/pre…

反正都是买,在哪买不是买,在 halo 这里买了还能成为甲方,舒服的。

哦,对了。

halo 默认的数据库采用的是 H2,具体原因,作者也在这里介绍了:

bbs.halo.run/d/17-halo

我觉得他说的很有道理,但是我还是选择 MySQL,毕竟知根知底,用着心里稳当一些。

至于怎么在 Linux 服务器上安装一个 MySQL,你可以看看之前说的这个地方:

里面就是有一步教你怎么搞一个 MySQL 服务器出来:

网上的教程也是相当的多,就不赘述了。

域名备案

域名备案,首先这玩意你得有个域名。

我的域名是这里买的:

wanwang.aliyun.com/domain

域名有贵有便宜的,我的便宜,买了三年,100 元钱。

虽然是 vip 结尾的,但是这价格还要啥自行车。

我倒是想买 com 结尾的,但是价格劝退啊,贫穷使我清醒:

所以,域名就看你自己想要什么样的,一般来说都不会太贵。

接下来就是 ICP 备案了。

备案的目的就是你要拿到这个东西:

这个东西很重要的,没有备案的话,你强行把域名挂上去,一会就被和谐了。

ICP 备案其实是一个很简单的过程,但是时间很长啊。

我备案就花了 9 天时间:

等域名备案完成之后,你就到域名解析的地方,把你的域名和公网 ip 绑定起来,就齐活了:

就可以从之前 ip 加端口的访问模式切换为域名的访问模式了。

图床

最后再说一下图床的问题吧。

啥是图床呢?

比如大家都用 Markdown 格式写文章,那么怎么插入一个图片呢?

就是这个语法:![](https://xxxx.png)

那么问题就是 xxxx.png 这个东西哪里来呢?

我之前是白嫖博客园的图床,就是写文章的时候把图片上传到它的文章编辑页面,相当于把图片上传到它的服务器上了。

写好之后,全选、复制、粘贴就可以搬运到各个其他平台。

但是有一段时间发现博客园的图片会出现不稳定的情况,访问文章的时候图片全都打不开。

所以我就去白嫖掘金的图床,后来发现掘金的图片默认加了水印,如果搬运到其他平台,会把水印也默认带上。

没办法,我就花了一会时间自己搞了一个图床服务器。

其实很早之前就想弄的,但是一直觉得麻烦,自己偷懒了就没弄。

前几十篇文章,我都是直接在微信公众号的编辑页面写,然后再搬运到其他平台。微信的图片就是加了防盗链,所以不能直接复制粘贴,我就一张张的去搬运。

哎,现在想起来也是创作路上走过的一大弯路啊。

应该最开始的时候就把图床弄好,就没有这回事了。

我的图床用的是腾讯云的对象存储,是按流量收费,下面就是我每个月在图床上的消费,整体还是很便宜的:

当然了你也可以去买七牛图床、阿里云OSS、又拍云图床这些,选择面也是很丰富的。

买了图床后,你就去下载一个叫做 PicGo 的软件,在里面按照自己买的图床对应的参数进行配置,就好了:

关于 PicGo 网上也有很多教程,可以翻一下,其实就是个小工具,也不复杂,挺好用的。

管理后台

最后给大家看看 halo 的管理后台有些啥功能吧。

首先进来就是一个仪表盘:

在这里可以看到一些博客相关的数据信息。

接着就是发布文章按钮的位置:

写文章的时候也是 Markdown 格式:

其实这些就是核心功能了,另外就是一个主题选择的功能:

在这里可以安装自己喜欢的主题。

好了,大概就是这些东西。

祝你玩得开心。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【云计算】弹性公网IP

发表于 2021-10-07

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

在云计算领域中,对于云虚拟服务器,裸金属服务器,虚拟IP(VIP)等如果需要具备公网通信的能力,一般来说是要连接设置一个公网IP,但公网IP不能单独持有,不能多个公网IP共享网络带宽,以及不支持弹性的插拔等特殊要求,所以很多云厂商都提供了另一个升级的产品,那就是弹性公网IP。

弹性公网IP(Elastic IP)提供独立的公网IP资源,包括公网IP地址与公网出口带宽服务。

弹性灵活

弹性公网IP可以与弹性云服务器、裸金属服务器、虚拟IP、弹性负载均衡、NAT网关等资源灵活地绑定及解绑,如下图所示。

image.png
弹性灵活还表现在支持独立购买与持有,即:

  • 支持单独购买一个弹性公网IP,而不是与其它计算资源或存储资源绑定购买。
  • 支持单独持有一个弹性公网IP,作为您账户下一个独立的资源存在。

多种计费模式

并且其拥有多种灵活的计费方式(比如按需、按带宽、按流量计费,包年包月价格),可以满足各种业务场景的需要。

共享带宽

相较于公网IP,弹性公网IP可以加入共享带宽,提高带宽资源的利用率,大幅降低公网使用成本。

即开即用

即开即用,支持便捷的绑定和解绑操作,以及能调整弹性公网IP的带宽值并且支持实时生效等。

除了上面提到的主要能力,弹性公网IP还能提供一些精品线路,比如精品线路通过国内运营商公网直连中国内地,无需绕行国际运营商出口,这样能大幅降低网络延迟。

image.png
其也支持IDC(Internet Data Center)迁移上云,迁移自有IPV4地址上云,支持超大弹性带宽复用。使用这样的方式,能做到业务改造少;公网IP地址不变,将上云改造成本、及业务迁移停机造成的损失减至最低等等。

image.png
还有一些小众的能力,比如支持IPv6转换等等。

目前国内大多数云服务提供商都能提供弹性公网IP,包括阿里云、腾讯云、天翼云、移动云等等,当然国外的产品也提供了这样的能力,包括AWS、Azure、GCP等等,感兴趣的同学,可以上这些云厂商官网去学习学习。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

如何评价代码质量?

发表于 2021-10-07

代码质量的评价有很强的主观性

最常用的评价标准有可维护性、可读性、、可扩展性、灵活性、简洁性、可复用性、可测试性。

一、可维护性

我们先来看看几个概念

维护:是指修改bug、修改捞的代码、添加新的代码之类的工作;

代码易维护:指不破坏原有代码设计、不引入新的 bug 的情况下,能够快速地修改或者添加代码;

代码不易维护:指修改或者添加代码需要冒着极大的引入新 bug 的风险,并且需要花费很长的时间才能完成。

我们可以从侧面上给出一个比较直观但又比较准确的感受。

如果 bug 容易修复、修改、添加功能也能轻松完成,那我们就可以主观的认为代码对我们来说是易维护的。

是否易维护本来就是针对维护的人来说的,不同水平的人对于同一份代码的维护能力也是不相同的,而且对系统的熟悉程度也占很大的主观因素。

二、可读性

如何评价一段代码的可读性呢?

很难给出一个覆盖所有评价指标的列表,比如是否符合编码规范、命名是否达意、注释是否详尽、函数是否长短合适、模块是否清晰、是否符合高内聚低耦合等等。

实际上,code review 是一个很好的检测代码可读性的手段。如果别人可以轻松读懂你写的代码,那说明你的代码可读性很好;如果同事读你的代码时,有很多疑问,那就说明你的代码可读性有待提高了。

三、可扩展性

在不修改或少量修改原有代码的情况下,通过扩展的方式添加新的功能代码。

说直白点就是,代码预留了一些功能扩展点,你可以把新功能代码,直接插到扩展点上,而不需要一位添加一个功能而大东干戈,改动大量的原始代码。

开闭原则

添加一个新的功能,应该是通过在已有代码基础上扩展代码(新增模块、类、方法、属性等),而非修改已有代码(修改模块、类、方法、属性等)的方式来完成。

关于定义,我们有两点要注意。第一点是,开闭原则并不是说完全杜绝修改,而是以最小的修改代码的代价来完成新功能的开发。第二点是,同样的代码改动,在粗代码粒度下,可能被认定为“修改”;在细代码粒度下,可能又被认定为“扩展”。

四、灵活性

灵活这个词的含义非常宽泛,很多场景下都可以使用功能。如果一段代码易扩展、易复用或者易用,都可以称字段代码写得比较灵活

五、简洁性

KISS 原则:“Keep It Simple, Stupid” 。尽量保持代码简单。

KISS 原则是保持代码可读和可维护的重要手段。KISS 原则中的“简单”并不是以代码行数来考量的。代码行数越少并不代表代码越简单,我们还要考虑逻辑复杂度、实现难度、代码的可读性等。而且,本身就复杂的问题,用复杂的方法解决,并不违背 KISS 原则。除此之外,同样的代码,在某个业务场景下满足 KISS 原则,换一个应用场景可能就不满足了。

KISS原则

对于如何写出满足 KISS 原则的代码,有几条指导原则:

  • 不要使用同事可能不懂的技术来实现代码;
  • 不要重复造轮子,要善于使用已经有的工具类库;
  • 不要过度优化。

六、可复用性

可以简单地理解为,尽量减少重复代码的编写。

代码可复用性和 DRY (Don’t Repeat Yourself/不要重复自己)这条设计原则的关系挺紧密的。

DRY原则

三种代码重复的情况:实现逻辑重复、功能语义重复、代码执行重复。

  • 实现逻辑重复,但功能语义不重复的代码,并不违反 DRY 原则。
  • 实现逻辑不重复,但功能语义重复的代码,也算是违反 DRY 原则。
  • 除此之外,代码执行重复也算是违反 DRY 原则。

提高代码可复用性的一些方法,有以下 7 点。

  • 减少代码耦合
  • 满足单一职责原则
  • 模块化
  • 业务与非业务逻辑分离
  • 通用代码下沉
  • 继承、多态、抽象、封装
  • 应用模板等设计模式

七、可测试性

代码可测试性的好坏,能从侧面上非常准确地反应代码质量的好坏。

代码的可测试差,比较难写单元测试,那基本上就能说明代码设计得有问题。

1. 什么是代码的可测试性?

粗略地讲,所谓代码的可测试性,就是针对代码编写单元测试的难易程度。对于一段代码,如果很难为其编写单元测试,或者单元测试写起来很费劲,需要依靠单元测试框架中很高级的特性,那往往就意味着代码设计得不够合理,代码的可测试性不好。

2. 编写可测试性代码的最有效手段

依赖注入是编写可测试性代码的最有效手段。通过依赖注入,我们在编写单元测试的时候,可以通过mock(模拟)的方法解依赖外部服务,这也是我们在编写单元测试的过程中最有技术挑战的地方。

3. 常见的 Anti-Patterns(反面模式)

常见的测试不友好的代码有下面这 5 种:

  • 代码中包含未决行为逻辑(所谓的未决行为逻辑就是,代码的输出是随机或者说不确定的,比如,跟时间、随机数有关的代码。)
  • 滥用可变全局变量
  • 滥用静态方法
  • 使用复杂的继承关系
  • 高度耦合的代码

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

k3s-简单集群搭建(v1215+k3s2)

发表于 2021-10-07

摘要:本文主要介绍介绍k3s的环境搭建和简单使用,使用的是最新版本,下面将以基于docker作为容器来介绍安装步骤

一、基本环境准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bash复制代码#禁用**iptables**和**firewalld**服务

#关闭firewalld服务
systemctl stop firewalld
systemctl disable firewalld
#关闭iptables服务
systemctl stop iptables
systemctl disable iptables

# 关闭 swap
swapoff -a && sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab

#禁用**selinux**
sed -i 's/enforcing/disabled/' /etc/selinux/config

# 生效命令
sysctl --system

二、docker环境准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bash复制代码wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo
yum list docker-ce --showduplicates | sort -r
#安装指定版本
yum install docker-ce-20.10.7

# 添加阿里云 yum 源, 可从阿里云容器镜像管理中复制镜像加速地址
cat <<EOF > /etc/docker/daemon.json
{
"registry-mirrors": [
"https://registry.docker-cn.com",
"http://hub-mirror.c.163.com",
"https://docker.mirrors.ustc.edu.cn"
]
}
EOF

#启动docker
systemctl enable docker && systemctl start docker

三、k3s环境搭建(单机)

使用k3s默认容器安装命令如下curl -sfL https://get.k3s.io | sh -

使用docker容器安装命令如下curl -sfL https://get.k3s.io | sh -s - server --docker

安装完成后会显示如下的说明

image.png

如果想像使用k8s那样使用kubectl命令,则需要创建软连接或者更新配置

1
2
3
4
5
6
7
bash复制代码# 第一种方式
echo "alias kubectl='k3s kubectl'" >> /etc/profile
source /etc/profile

#第二种方式
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
source /etc/profile

image.png

四、集群环境搭建 work节点加入

查看master节点的token(K3S_TOKEN)
cat /var/lib/rancher/k3s/server/node-token

image.png
curl -sfL https://get.k3s.io | K3S_URL=https://myserver:6443 K3S_TOKEN=XXX INSTALL_K3S_EXEC="--docker" sh -

到此环境就安装结束了,部署一个应用看看

五、部署demo

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
yaml复制代码vim k8s-springboot.yaml

apiVersion: v1
kind: Namespace
metadata:
name: k8s-springboot
---
apiVersion: v1
kind: Service
metadata:
name: springboot-demo-nodeport
namespace: k8s-springboot
spec:
type: NodePort
ports:
- port: 8080
targetPort: 8080
nodePort: 30001
selector:
app: springboot-demo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: springboot-demo
namespace: k8s-springboot
spec:
selector:
matchLabels:
app: springboot-demo
replicas: 1
template:
metadata:
labels:
app: springboot-demo
spec:
containers:
- name: springboot-demo
image: huzhihui/springboot:1.0.0
ports:
- containerPort: 8080

# 部署
kubectl apply -f k8s-springboot.yaml

#查看部署pod状态
kubectl get pods --all-namespaces

页面访问

image.png

traefik使用

Traefik is an open-source Edge Router that makes publishing your services a fun and easy experience. It receives requests on behalf of your system and finds out which components are responsible for handling them.(我人为这个和Ingress提供了相同的功能,作为外部服务访问的入口)

trace-dashboard监控面板使用

  • 删除原来的trace-dashboard

image.png

运行kubectl delete IngressRoute traefik-dashboard -n kube-system删除后,我们需要创建一个新的traefik-dashboard

路径为:/var/lib/rancher/k3s/server/manifests 文件名称: traefik-dashboard.yaml 不用执行,k3s会自动扫描该文件下的文件进行自动更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
yaml复制代码apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: traefik-dashboard
namespace: kube-system
spec:
entryPoints:
- web
routes:
- match: Host(`traefik.local.huzhihui.com`) && (PathPrefix(`/dashboard`) || PathPrefix(`/api`))
kind: Rule
services:
- name: api@internal
kind: TraefikService

创建好后如下显示 kubectl get IngressRoute -A
image.png
页面访问 http://traefik.local.huzhihui.com/dashboard/#/

image.png

我们之前创建了springboot.yaml的实例,现在先把该实例删除,运行kubectl delete -f springboot.yaml

创建新的k8s-springboot.yaml 运行 kubectl apply -f k8s-springboot.yaml

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
yaml复制代码apiVersion: v1
kind: Namespace
metadata:
name: k8s-springboot
---
apiVersion: v1
kind: Service
metadata:
name: k8s-springboot-demo-service
namespace: k8s-springboot
spec:
type: ClusterIP
ports:
- port: 8080
targetPort: 8080
protocol: TCP
selector:
app: k8s-springboot-demo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: k8s-springboot-demo
namespace: k8s-springboot
spec:
selector:
matchLabels:
app: k8s-springboot-demo
replicas: 1
template:
metadata:
labels:
app: k8s-springboot-demo
spec:
containers:
- name: k8s-springboot-demo
image: huzhihui/springboot:1.0.0
ports:
- containerPort: 8080
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: k8s-springboot-demo-ingress-route
namespace: k8s-springboot
spec:
entryPoints:
- web
routes:
- match: Host(`springboot-demo.local.huzhihui.com`) && PathPrefix(`/`)
kind: Rule
services:
- name: k8s-springboot-demo-service
port: 8080

image.png

浏览器访问http://springboot-demo.local.huzhihui.com/

image.png

如果想通过默认的traefik通过路径前缀进行转发则需要按照如下配置

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
yaml复制代码---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: k8s-springboot-demo-ingress
namespace: k8s-springboot
spec:
entryPoints:
- web
routes:
- match: PathPrefix(`/a`)
kind: Rule
services:
- name: k8s-springboot-demo-service
port: 80
middlewares:
- name: a-stripprefix
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: a-stripprefix
namespace: k8s-springboot
spec:
stripPrefix:
prefixes:
- /a

上面的配置你访问则和nginx配置的反向代理一样会截取/a的路径,直接定位到实际的服务

image.png

Ingress使用

如果觉得IngressRoute使用不太明了,也可以继续在traefik中使用Ingress

新建文件k3s-springboot-ingress.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
yaml复制代码kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: k8s-springboot-demo-ingress
namespace: k8s-springboot
spec:
rules:
- host: springboot-demo-ingress.local.huzhihui.com
http:
paths:
- path: /
pathType: Exact
backend:
service:
name: k8s-springboot-demo-service
port:
number: 8080

image.png

image.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Spring 实操 PostProcessor 流程及能

发表于 2021-10-07

首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜

文章合集 : 🎁 juejin.cn/post/694164…

Github : 👉 github.com/black-ant

CASE 备份 : 👉 gitee.com/antblack/ca…

一 . 前言

BeanPostProcessor 是 Spring 的核心组件之一 , Bean 实现 BeanPostProcessor 可以实现很多复杂的功能

二 . PostProcessor 的结构

2.1 接口方法

该接口中主要提供了2种 , 其中提供了前置调用和后置调用 . 还可以看到 , 这里通过 default 修饰 , 所以并不是强制重写的

1
2
3
4
5
6
7
8
9
10
11
java复制代码public interface BeanPostProcessor {

default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}

default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}

}

2.2 常见实现类

这里可以看到 , AOP , 定时 , 配置等都实现了相关的集成

system-BeanPostProcessor.png

三 . Spring 源码深入分析

3.1 使用案例

这一部分来看一下 Spring 内部是如何使用 PostProcessor 特性的 , 这一部分以 ScheduledAnnotationBeanPostProcessor 为例.

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
java复制代码@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
// 可以看到 , 前置处理并没有太多处理 , 直接返回
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {

if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||
bean instanceof ScheduledExecutorService) {
// Ignore AOP infrastructure such as scoped proxies.
return bean;
}

// Step 1 : 开始特殊处理
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
if (!this.nonAnnotatedClasses.contains(targetClass) &&
AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {
// Step 2 : 获取 Method 对应 Scheduled 的集合
Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
(MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
Set<Scheduled> scheduledAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(
method, Scheduled.class, Schedules.class);
return (!scheduledAnnotations.isEmpty() ? scheduledAnnotations : null);
});
if (annotatedMethods.isEmpty()) {
this.nonAnnotatedClasses.add(targetClass);
}
else {
// Step 3 : 方法不为空 , 执行 Process
annotatedMethods.forEach((method, scheduledAnnotations) ->
scheduledAnnotations.forEach(scheduled -> processScheduled(scheduled, method, bean)));
}
}
return bean;
}

PS : 后面就不看了 , 主要就是通过一个 ScheduledTask 运行 Runable 对象

从具体的使用上 , 不难看出 , 他是在创建Bean的时候去做补充的操作 , 那么下面来看一下具体的处理流程

这个流程在Bean 初始化流程 中进行了全面的跟踪 , 这里更关注其中的一些细节

3.2 Before/After 调用流程

入口一 : AbstractAutowireCapableBeanFactory # initializeBean 调用

  • applyBeanPostProcessorsBeforeInitialization
  • applyBeanPostProcessorsAfterInitialization
    其中流程也比较简单 , for 循环所有的 BeanPostProcessors 进行处理

3.3 BeanPostProcessors 的管理

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码// 在 AbstractBeanFactory 中有一个集合用于管理 BeanPostProcessor
private final List<BeanPostProcessor> beanPostProcessors = new BeanPostProcessorCacheAwareList();

// 不论是哪种类型 ,都会通过以下2种方式进行调用 , 先删除, 后添加
public void addBeanPostProcessors(Collection<? extends BeanPostProcessor> beanPostProcessors) {
this.beanPostProcessors.removeAll(beanPostProcessors);
this.beanPostProcessors.addAll(beanPostProcessors);
}

public void addBeanPostProcessor(BeanPostProcessor beanPostProcessor) {
this.beanPostProcessors.remove(beanPostProcessor);
this.beanPostProcessors.add(beanPostProcessor);
}

类型一 : 手动添加的流程

可以通过手动添加的方式 ,实现 BeanPostProcessor 的添加 , 参考对象 AbstractApplicationContext

1
2
3
4
5
6
java复制代码protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// .............
// Configure the bean factory with context callbacks.
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
// .............
}

类型二 : Refresh 环节添加

哈哈 , 又到了这个地方 , 之前看 Refresh 代码的时候就注意到了这个地方 , 会注册相关的 BeanPostProcessors

1
2
3
4
java复制代码protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
// Step 1 : 调用 PostProcessorRegistrationDelegate 进行注册
PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
}

源码注释非常清晰 ,这里就是简单翻译了一下

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
java复制代码public static void registerBeanPostProcessors(
ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {

// postProcessor 数组列表
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);

// processor 计数
int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
//注册BeanPostProcessorChecker,当bean在BeanPostProcessor实例化期间创建时,即当bean不符合所有BeanPostProcessors处理的条件时,该checker会记录信息消息
beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));

// 区别 PriorityOrdered, internalPostProcessor
List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();

// 有序化
List<String> orderedPostProcessorNames = new ArrayList<>();
List<String> nonOrderedPostProcessorNames = new ArrayList<>();

for (String ppName : postProcessorNames) {
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
priorityOrderedPostProcessors.add(pp);
// 运行时用于合并bean定义的后处理器回调接口
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
// 如果实现了 Ordered 接口 , 则需要有序处理
else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
orderedPostProcessorNames.add(ppName);
}
else {
nonOrderedPostProcessorNames.add(ppName);
}
}

// 首先,注册实现prioritordered的BeanPostProcessors
sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);

// 接下来,注册实现Ordered的BeanPostProcessors
List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
for (String ppName : orderedPostProcessorNames) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
orderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
sortPostProcessors(orderedPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, orderedPostProcessors);

// 现在,注册所有常规的BeanPostProcessors
List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
for (String ppName : nonOrderedPostProcessorNames) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
nonOrderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);

// 最后,重新注册所有内部BeanPostProcessors
sortPostProcessors(internalPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, internalPostProcessors);

// 将用于检测内部bean的后处理器重新注册为ApplicationListeners,将其移动到处理器链的末端
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
}

四 . 深入定制

4.1 添加 BeanPostProcessor

方法一 : 手动添加
PS : 当然 , 这种方式过于复杂 , 适用场景不多

1
2
3
4
5
6
7
8
9
java复制代码@Autowired
private DefaultListableBeanFactory beanFactory;

@Bean(initMethod = "initMethod")
public CommonService getCommonService() {
// 通过 BeanFactory 进行添加
beanFactory.addBeanPostProcessor(new CustomizePostProcessor());
return new CommonService();
}

方法二 : 直接继承接口

上面分析的时候也看到 , 实现了接口就自动添加了

五 .问题补充

5.1 与四种常见的初始化方式有什么区别 ?

回顾一下四种初始化运行的方式 :

  • 实现 InitializingBean 接口方法 afterPropertiesSet
  • 实现 ApplicationRunner 接口方法 run(ApplicationArguments args)
  • 方法标注注解 @PostConstruct
  • @Bean(initMethod = “initMethod”) 通过注解指定

调用的区别

1
2
3
4
5
6
7
8
9
10
11
java复制代码: ------> this is @PostConstruct <-------
: ------> this is InitializingBean <-------
: ------> this is in @Bean(initMethod = "initMethod") <-------
: ------> this is postProcessBeforeInitialization <-------
: ------> this is postProcessAfterInitialization <-------
: ------> this is postProcessBeforeInitialization <-------
: ------> this is postProcessAfterInitialization <-------
: ------> this is postProcessBeforeInitialization <-------
: ------> this is postProcessAfterInitialization <-------
: Started DemoApplication in 0.822 seconds (JVM running for 2.597)
: ------> this is ApplicationRunner :getCommonService <-------
  • 调用次数 : 四种方法只会调用一次 ,而 postProcessBeforeInitialization 和 postProcessAfterInitialization 会在每个Bean创建的时候调用
  • 调用时机 : 集中在每个 Bean initializeBean 环节调用

六. 我们能用它干什么 ?

这里不讨论是否真的有场景会使用 ,只是发散思维 , 去思考如何利用他的特点去做点什么

6.1 结合设计模式

代理是 postProcess 最合适的使用之一 , AOP 即使用了他的特性进行的代理 , 我们可以模拟 AOP , 去做一个更偏向于业务的静态代理模式 .

同时也可以实现装饰器模式 , 对 Bean 的处理进行加强 .

Step 1 : 准备接口和实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码// 统一接口
public interface Sourceable {
void method();
}


// 对应实现类 (实际业务类)
@Service
public class Source implements Sourceable {

@Override
public void method() {
System.out.println("the original method!");
}
}

Step 2 : 准备中间类

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
java复制代码public class AopProxyImpl implements Sourceable {

private Sourceable source;

public AopProxyImpl(Sourceable source) {
super();
// 注意 ,代理模式是在代理类中创建一个对象
// this.source = new Source();

// 修饰模式是传入对应的对象
this.source = source;
}

@Override
public void method() {
before();
source.method();
atfer();
}

private void atfer() {
System.out.println("after proxy!");
}

private void before() {
System.out.println("before proxy!");
}
}

Step 3 : BeanPostProcessor 进行处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码@Service
public class AopPostProcessor implements BeanPostProcessor {

private Logger logger = LoggerFactory.getLogger(this.getClass());

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
logger.info("------> AopPostProcessor postProcessBeforeInitialization <-------");
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
logger.info("------> AopPostProcessor postProcessAfterInitialization <-------");

if (bean instanceof Sourceable) {
logger.info("------> AopPostProcessor build Aop <-------");
AopProxyImpl proxy = new AopProxyImpl((Sourceable)bean);
return proxy;
}

return bean;
}
}

补充 :

  • 这里如果没有 , 则一定会抛出 BeanNotOfRequiredTypeException , 因为 Spring 会去坚持类是否匹配
  • 这里我们属于静态代理 , 其实还可以更活用的使用 cglib 动态代理
  • 还有很多种整合设计模式的方式可以灵活使用

6.2 Manager 管理指定类型对象

这种方式是在 postProcessor 中对指定的Bean 进行一个管理记录

大概的使用方式是准备一个BeanManager , 然后在 postProcessor 中进行管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码@Component
public class BeanManager {

// 对特殊的Bean 进行分组
private static Map<String, TypeBean> typeOne = new ConcurrentHashMap();
private static Map<String, TypeBean> typeTwo = new ConcurrentHashMap();

// PS : 这种方式适合处理更复杂的场景 , 简单场景可以考虑通过 ApplicationContext.getBean 获取对应的类
}


public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
logger.info("------> ManagerPostProcessor postProcessAfterInitialization <-------");

if (bean instanceof TypeBean) {
logger.info("------> AopPostProcessor build Aop <-------");
beanManager.getTypeOne().put(beanName, (TypeBean) bean);
}

return bean;
}

// PS : 实际上和注入的是同一个对象
------> typeOne.get("typeBeanImpl") == (typeBean) :true <-------

补充 :

这是最简单的使用方式 , 相对更复杂的还可以整合注解 , 整合接口或者父类 , 或者仅记录class信息等方式 ,达到自己的业务效果

6.3 注入特殊属性

从图片中我们可以看到 , BeanPostProcessor 是在 PopulateBean 环节之后进行处理的 , 那么我们可以通过这个环节 , 对 Bean 中的属性进行修饰 , 常见的使用想法包括 :

  • 为特定属性设置动态代理
  • 从 Remote 端获取属性 , 并且设置

这个就比较好理解了 , 实际上这个时候包括RestTemplate 都已经加载完成 , JDBC 也可以使用 , 在这个时候完全可以从远端获取配置信息

1
2
3
4
5
6
7
8
java复制代码// 作用一 : 
使用 JDBC (JPA 应该也已经加载可用) , 从数据库获取配置 , 判断当前类是否有特定注解 , 刷新注解的配置

// 作用二 :
调用远端配置中心 , 自定义刷新配置

// 作用三 :
刷新特殊的属性或者对象等等

**特殊对象的刷新有多种任意的使用 , 可以根据自己的业务灵活运用 **

6.4 其他思路

这里只是抛砖引玉 , 欢迎大佬们提出自己的想法

  • 重构属性
  • 定制化过程中对类进行覆盖

总结

image.png

注意点 :

  • applyBeanPostProcessorsBeforeInitialization 主要在 initializeBean 环节调用
  • applyBeanPostProcessorsAfterInitialization 除了initializeBean外还在多个环节被调用 , 包括 getSingletonFactoryBeanForTypeCheck 等等几个 Factory 去实例化的过程中
  • 避免循环 , ManagerPostProcessor 不会处理自己
  • BeanPostProcessor 在每个 bean 创建时都会调用 , 过多会影响启动效率
  • BeanPostProcessor 主要在populateBean 之后 , 注意前后顺序

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

2 Spring 框架基础

发表于 2021-10-07

1.Spring框架

Spring 框架概述

Spring 框架模块概述

Module Artifact 描述
AOP spring-aop Spring基于代理的AOP框架
Aspects spring-aspects 用于 Spring 的基于 AspectJ 的切面
Beans spring-beans Spring 的核心 bean 工厂支持
Context spring-context 应用程序上下文运行时实现;还包含调度和远程支持类
Context spring-context-indexer 支持提供应用程序中使用的bean的一个静态索引;提高启动性能
Context spring-context-support 用于将第三方库与 Spring 集成的支持类
Core spring-core 核心实用程序
Expression Language spring-expression Spring 表达式语言 (SpEL) 的类
Instrumentation spring-instrument 与一个 Java 代理一起使用的检测类
JCL spring-jcl 用于公共日志记录的 Spring 特定替换
JDBC spring-jdbc JDBC 支持包,包括数据源设置类和 JDBC 访问支持
JMS spring-jms JMS 支持包,包括同步 JMS 访问和消息侦听器容器
ORM spring-orm ORM 支持包,包括对 Hibernate 5+ 和 JPA 的支持
Messaging spring-messaging Spring消息传递抽象;JMS 和 WebSocket 使用
OXM spring-oxm XML 支持包,包括对对象到 XML 映射的支持;还包括对 JAXB、JiBX、XStream 和 Castor 的支持
Test spring-test 测试支持类
Transactions spring-tx 交易基础设施类;包括 JC one 集成和 DAO 支持类
Web spring-web 适用于任何网络环境的核心网络包
WebFlux spring-webflux Spring WebFlux 支持包包括对 Netty 和 Undertow 等多个反应式运行时的支持
Servlet spring-webmvc 在一个 Servlet 环境中使用的 Spring MVC 支持包包括对常见视图技术的支持
WebSocket spring-websocket Spring WebSocket 支持包包括对通过 WebSocket 协议进行通信的支持

Spring Framework 模块依赖项

2.依赖注入

有七种方法可以解耦这种依赖构建逻辑。

  • 工厂模式
  • 服务定位器模式
  • 依赖注入
    • 基于构造函数
    • 基于二传手(Setter based)
    • 基于现场
  • 上下文查找

1.假设:使用来自 JNDI(Java 命名和目录接口)的上下文查找。构造函数代码需要知道如何进行查找和处理异常。

MoneyTransferService Implementation with Contextualized LookupMoneyTransferService Implementation with Contextualized Lookup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码package com.xxx.prospringmvc.moneytransfer.jndi;
import javax.naming.InitialContext;
import javax.naming.NamingException;
//other import statements omitted.
public class JndiMoneyTransferServiceImpl implements MoneyTransferService {
private AccountRepository accountRepository;
private TransactionRepository transactionRepository;
public JndiMoneyTransferServiceImpl() {
try {
InitialContext context = new InitialContext();
this.accountRepository = (AccountRepository) context.lookup("accountRepository");
this.transactionRepository = (TransactionRepository) context.lookup("transactionRepository");
} catch (NamingException e) {
throw new IllegalStateException(e);
}
}
//transfer method omitted, same as listing 2-1
}

*结论:*前面的代码不是特别干净;例如,想象来自不同上下文的多个依赖项。代码很快就会变得混乱,并且越来越难以进行单元测试。

2.解决上述的问题:

为了解决对象构造函数中的构造/查找逻辑,我们可以使用依赖注入。我们只是将对象传递给它完成工作所需的依赖项。这使我们的代码干净、解耦且易于测试。依赖注入是一个对象指定它们所使用的依赖的过程。IoC 容器使用该规范;当它构造一个对象时,它也会注入它的依赖项。这样,我们的代码更干净,我们不再用构造逻辑给类增加负担。更容易维护和更容易进行单元和/或集成测试。测试更容易,因为我们可以注入一个存根或模拟对象来验证我们对象的行为。

A MoneyTransferService Implementation with Constructor-Based Dependency Injection
one MoneyTransferService Implementation with Constructor-Based Dependency Injection

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码package com.xxx.prospringmvc.moneytransfer.constructor;
// import statements ommitted
public class MoneyTransferServiceImpl implements MoneyTransferService {
private final AccountRepository accountRepository;
private final TransactionRepository transactionRepository;
public MoneyTransferServiceImpl(AccountRepository accountRepo,
TransactionRepository transactionRepo) {
this.accountRepository = accountRepo;
this.transactionRepository = transactionRepo;
}
//transfer method omitted, same as listing 2-1
}

*总结:*顾名思义,基于构造函数的依赖注入使用构造函数来注入对象中的依赖项。使用了基于构造函数的依赖注入。它有一个构造函数,它接受两个对象作为参数,当我们构造com.xxx.prospringmvc. moneytransfer. constructor. MoneyTransferServiceImpl的实例时,我们需要将所需的依赖项交给它。

3.补充setter

基于 setter 的依赖注入使用setter方法来注入依赖。JavaBeans 规范定义了 setter 和 getter 方法。如果我们有一个名为的方法setAccountService,我们设置了一个名称为的属性 accountService. 属性名称是使用方法名称创建的,减去“set”并且第一个字母小写(完整规范在 JavaBeans 规范中。展示了一个基于 setter 的依赖注入的例子。属性的 getter 和 setter 都不是强制性的。属性可以是只读的(仅定义了getter方法)或只写的(仅定义了setter方法)。显示了 setter 方法,因为我们只需要编写属性;在内部,我们可以直接引用该字段。

A MoneyTransferService Implementation with Setter-Based Dependency Injection
one MoneyTransferService Implementation with Setter-Based Dependency Injection

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码package com.xxx.prospringmvc.moneytransfer.setter;
// imports ommitted
public class MoneyTransferServiceImpl implements MoneyTransferService {
private AccountRepository accountRepository;
private TransactionRepository transactionRepository;
public void setAccountRepository(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
public void setTransactionRepository(TransactionRepository transactionRepo) {
this.transactionRepository = transactionRepository;
}
//transfer method omitted, same as listing 2-1
}

4.最终:使用注解

最后,还有使用注解的基于字段的依赖注入。我们不需要指定构造函数参数或 setter 方法来设置依赖项以使其工作。我们首先定义一个可以保存依赖项的类级字段。接下来,我们在该字段上添加注释以表达我们将该依赖项注入到我们的对象中的意图。Spring 接受几种不同的注解:@Autowired, @Resource*, 和 *@Inject. 所有这些注释或多或少都以相同的方式工作。

}
A MoneyTransferService Implementation with Field-Based Dependency Injection
one MoneyTransferService Implementation with Field-Based Dependency Injection

1
2
3
4
5
6
7
8
9
10
kotlin复制代码package com.xxx.prospringmvc.moneytransfer.annotation;
import org.springframework.beans.factory.annotation.Autowired;
//other imports omitted
public class MoneyTransferServiceImpl implements MoneyTransferService {
@Autowired
private AccountRepository accountRepository;
@Autowired
private TransactionRepository transactionRepository;
//transfer method omitted, same as listing 2.1
}

总结:@Autowired 和 @Inject 可以放在方法和构造函数上来表达依赖注入配置,即使有多个参数!当对象只有一个构造函数时,可以省略注释。

3.应用上下文

应用程序上下文的作用

应用程序上下文负责管理其中定义的 bean。

它还支持更复杂的操作,例如将 AOP 应用于其中定义的 bean。

Spring 提供了几种不同的 ApplicationContext 实现。这些实现中的每一个都提供相同的功能,但在加载应用程序上下文配置的方式上有所不同。

各种ApplicationContext实现(简化)

应用上下文概述

Implementation Location 文件类型
ClassPathXmlApplicationContext Classpath XML
FileSystemXmlApplicationContext File system XML
AnnotationConfigApplicationContext Classpath Java
XmlWebApplicationContext Web Application Root XML
AnnotationConfigWebApplicationContext Web Application Classpath Java

**举个例子:**让我们看一个基于 Java 的配置文件com.xxx.prospringmvc.moneytransfer.annotation.ApplicationContextConfiguration类

类中使用了两个注释:org.springframework.context.annotation.Configuration和org.springframework.context.annotation.Bean。

第一个构造型将我们的类定义为一个配置文件.

第二个表示该方法的结果用作一个工厂来创建一个 bean。bean 的名称默认为方法名称。

The ApplicationContextConfiguration Configuration File

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码package com.xxx.prospringmvc.moneytransfer.annotation;
import com.xxx.prospringmvc.moneytransfer.repository.AccountRepository;
import com.xxx.prospringmvc.moneytransfer.repository.MapBasedAccountRepository;
import com.xxx.prospringmvc.moneytransfer.repository.MapBasedTransactionRepository;
import com.xxx.prospringmvc.moneytransfer.repository.TransactionRepository;
import com.xxx.prospringmvc.moneytransfer.service.MoneyTransferService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ApplicationContextConfiguration {
@Bean
public AccountRepository accountRepository() {
return new MapBasedAccountRepository();
}
@Bean
public TransactionRepository transactionRepository() {
return new MapBasedTransactionRepository();
}
@Bean
public MoneyTransferService moneyTransferService() {
return new MoneyTransferServiceImpl();
}
}

注意点:

1.配置类可以是抽象的;但是,它们不能是final。为了解析类,Spring 可能会创建配置类的一个动态子类。

2.有一个班级只有 @Configuration注释是不够的。我们还需要一些东西来引导我们的应用程序上下文。我们使用它来启动我们的应用程序。

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
java复制代码package com.xxx.prospringmvc.moneytransfer.annotation;
import com.xxx.prospringmvc.ApplicationContextLogger;
import com.xxx.prospringmvc.moneytransfer.domain.Transaction;
import com.xxx.prospringmvc.moneytransfer.service.MoneyTransferService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.math.BigDecimal;
public class MoneyTransferSpring {
private static final Logger logger =
LoggerFactory.getLogger(MoneyTransferSpring.class);
/**
* @param args
*/
public static void main(String[] args) {
ApplicationContext ctx =
new AnnotationConfigApplicationContext(ApplicationContextConfiguration.class);
transfer(ctx);
ApplicationContextLogger.log(ctx);
}
private static void transfer(ApplicationContext ctx) {
MoneyTransferService service =
ctx.getBean("moneyTransferService", MoneyTransferService.class);
Transaction transaction = service.transfer("123456", "654321", new BigDecimal("250.00"));
logger.info("Money Transfered: {}", transaction);
}
}

这个类通过创建一个实例来引导我们的配置org.springframework.context.annotation.AnnotationConfigApplicationContext并将包含我们的配置的类传递给它

*总结:*最后,请注意应用程序上下文可以在层次结构中。我们可以有一个应用程序上下文作为另一个上下文的父级,一个应用程序上下文只能有一个父级,但它可以有多个子级。子上下文可以访问父上下文中定义的 bean;但是,父 bean 不能访问子上下文中的 bean。

这个特性允许我们将我们的应用程序 bean(例如,服务、存储库和基础设施)与我们的 Web bean(例如,请求处理程序和视图)分开。进行这种分离会很有用。例如,假设多个 servlet 需要重用相同的应用程序 bean。我们可以简单地重用已经存在的实例,而不是为每个 servlet 重新创建它们。这可能是当有一个 servlet 处理 Web UI 和另一个处理 Web 服务时。

ApplicationContext的层次

3.1.资源加载

不同的ApplicationContext实现和默认资源加载机制。但是,这并不意味着您只能从默认位置加载资源。您还可以通过包含适当的前缀从特定位置加载资源

前缀概述

Prefix Location
classpath: The root of the classpath
file: File system
http: Web application root

Ant 风格的正则表达式

Expression Description
classpath:/META-INF/spring/*.xml 从 META-INF/spring 目录中的类路径加载所有具有 XML 文件扩展名的文件
file:/var/conf/*/.properties 从 /var/conf 目录和所有子目录加载具有属性文件扩展名的所有文件

3.2.组件扫描

此功能使 Spring 能够扫描您的类路径以查找注释为org.springframework.stereotype.Component (或专门的注释之一.

如 @Service, @Repository, @Controller, 或者org.springframework.context.annotation.Configuration

如果我们想启用组件扫描,org.springframework.context.annotation.ComponentScan注释使我们能够做到这一点。这个注解需要放在我们的配置类上才能启用组件扫描。

1
2
3
4
5
6
7
8
9
10
11
java复制代码package com.xxx.prospringmvc.moneytransfer.scanning;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @author Marten Deinum
*/
@Configuration
@ComponentScan(basePackages = {
"com.xxx.prospringmvc.moneytransfer.scanning",
"com.xxx.prospringmvc.moneytransfer.repository" })
public class ApplicationContextConfiguration {}

*总结:*通过不指定一个包或使用太广泛的一个包(如com.xxx)来扫描整个类路径被认为是一种不好的做法。这可能会导致扫描大部分或所有类,这会严重影响应用程序的启动时间。

3.3.范围

默认情况下,一个 Spring 应用程序上下文中的所有 bean 都是单例。一个 bean 只有一个实例,它用于整个应用程序。

范围概述

Prefix 描述
singleton 默认范围。在整个应用程序中创建和共享一个 bean 的一个实例。
prototype 每次需要一个特定的 bean 时,都会返回一个新的 bean 实例。
thread bean 在需要时创建并绑定到当前正在执行的线程。如果线程死了,bean 就会被销毁。
request bean 在需要时创建并绑定到传入的javax.servlet.ServletRequest的生命周期。如果请求结束,则销毁 bean 实例。
session bean 在需要时创建并存储在javax.servlet.HttpSession 中。当会话被销毁时,bean 实例也会被销毁。
globalSession bean 在需要时创建并存储在全局可用的会话中(在 Portlet 环境中可用)。如果没有这样的会话可用,范围将恢复为会话范围功能。
application 这个范围与单例范围非常相似;但是,具有此范围的 bean 也在javax.servlet.ServletContext 中注册。

3.4.配置文件

配置文件可以轻松地为不同环境创建应用程序的不同配置。例如,我们可以为本地环境、测试和部署到 CloudFoundry 创建单独的配置文件。这些环境中的每一个都需要一些特定于环境的配置或 bean。您可以考虑数据库配置、消息传递解决方案和测试环境,以及某些 bean 的存根。

我们在开发Spring Boot应用时,通常同一套程序会被安装到不同环境,比如:开发、测试、生产等。其中数据库地址、服务器端口等等配置都不同,如果每次打包时,都要修改配置文件,那么非常麻烦。profile功能就是来进行动态配置切换的。
1)profile配置方式

​ ·多profile文件方式

​ ·yml多文档方式
2)profile激活方式
​ ·配置文件

​ ·虚拟机参数

​ ·命令行参数

image-20211007100009851

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
java复制代码package com.xxx.prospringmvc.moneytransfer.annotation.profiles;
import com.xxx.prospringmvc.moneytransfer.annotation.MoneyTransferServiceImpl;
import com.xxx.prospringmvc.moneytransfer.repository.AccountRepository;
import com.xxx.prospringmvc.moneytransfer.repository.MapBasedAccountRepository;
import com.xxx.prospringmvc.moneytransfer.repository.MapBasedTransactionRepository;
import com.xxx.prospringmvc.moneytransfer.repository.TransactionRepository;
import com.xxx.prospringmvc.moneytransfer.service.MoneyTransferService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
public class ApplicationContextConfiguration {
@Bean
public AccountRepository accountRepository() {
return new MapBasedAccountRepository();
}
@Bean
public MoneyTransferService moneyTransferService() {
return new MoneyTransferServiceImpl();
}
@Configuration
@Profile(value = "test")
public static class TestContextConfiguration {
@Bean
public TransactionRepository transactionRepository() {
return new StubTransactionRepository();
}
}
@Configuration
@Profile(value = "local")
public static class LocalContextConfiguration {
@Bean
public TransactionRepository transactionRepository() {
return new MapBasedTransactionRepository();
}
}
}

要启用配置文件,我们需要告诉应用程序上下文哪些配置文件处于活动状态。要激活某些配置文件,我们需要设置一个名为spring.profiles.active(在 Web 环境中,这可以是 servlet 初始化参数或 Web 上下文参数)。这是一个逗号分隔的字符串,包含活动配置文件的名称。如果我们现在添加一些(在本例中为静态内部)类org.springframework.context.annotation.Configuration 和 org.springframework.context.annotation.Profile注释,则仅处理与活动配置文件之一匹配的类。所有其他类都将被忽略。

下面的代码显示了一些示例引导程序代码。通常,我们不会从引导程序代码中设置活动配置文件。相反,我们使用系统变量的组合来设置我们的环境。这使我们能够让我们的应用程序保持不变,但仍然可以灵活地更改我们的运行时配置。

使用java 代码来进行配置

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
java复制代码package com.xxx.prospringmvc.moneytransfer.annotation.profiles;
import com.xxx.prospringmvc.ApplicationContextLogger;
import com.xxx.prospringmvc.moneytransfer.domain.Transaction;
import com.xxx.prospringmvc.moneytransfer.service.MoneyTransferService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.math.BigDecimal;
/**
* @author Marten Deinum
*/
public class MoneyTransferSpring {
private static final Logger logger = LoggerFactory.getLogger(MoneyTransferSpring.class);
/**
* @param args
*/
public static void main(String[] args) {
System.setProperty("spring.profiles.active", "test");
AnnotationConfigApplicationContext ctx1 =
new AnnotationConfigApplicationContext(ApplicationContextConfiguration.class);
transfer(ctx1);
ApplicationContextLogger.log(ctx1);
System.setProperty("spring.profiles.active", "local");
AnnotationConfigApplicationContext ctx2 =
new AnnotationConfigApplicationContext(ApplicationContextConfiguration.class);
transfer(ctx2);
ApplicationContextLogger.log(ctx2);
}
private static void transfer(ApplicationContext ctx) {
MoneyTransferService service = ctx.getBean("moneyTransferService", MoneyTransferService.class);
Transaction transaction = service.transfer("123456", "654321", new BigDecimal("250.00"));
logger.info("Money Transfered: {}", transaction);
}
}

我们也可以通过

application.yml 来进行配置

image-20211007100130004

image-20211007100155025

这时候我们会发现他就改变了

image-20211007100246541

3.5.启用功能

Spring 框架提供了比依赖注入更多的灵活性;它还提供了许多我们可以启用的不同功能。我们可以使用注解来启用这些功能。

org.springframework.web.servlet.config.annotation.EnableWebMvc和org.springframework.web.reactive.config.EnableWebFlux注释提供的特性。

Spring Boot 会自动启用其中一些功能;这取决于在类路径上检测到的类。

注释启用的功能概述

Annotation 描述 Spring Boot 启动检测
org.springframework.context.annotation.EnableAspectJAutoProxy 支持处理构造型为 org.aspectj.lang.annotation.Aspect 的 bean。 Yes
org.springframework.scheduling.annotation.EnableAsync 启用对使用org.springframework.scheduling.annotation.Async或javax.ejb.Asynchronous注释处理 bean 方法的支持。 No
org.springframework.cache.annotation.EnableCaching 使用 org.springframework.cache.annotation.Cacheable 注释启用对 bean 方法的支持。 Yes
org.springframework.context.annotation.EnableLoadTimeWeaving 启用对加载时编织的支持。默认情况下,Spring 使用一种基于代理的 AOP 方法;然而,这个注释使我们能够切换到加载时编织。一些 JP one 提供商需要它。 No
org.springframework.scheduling.annotation.EnableScheduling 启用对注释驱动调度的支持,让我们解析使用 org.springframework.scheduling.annotation.Scheduled 注释注释的 bean 方法。 No
org.springframework.beans.factory.aspectj.EnableSpringConfigured 支持对非 Spring 管理的 bean 应用依赖注入。通常,此类 bean 使用org.springframework.beans.factory.annotation.Configurable注释进行注释。此功能需要加载时或编译时编织,因为它需要修改类文件。 No
org.springframework.transaction.annotation.EnableTransactionManagement 启用注释驱动的事务支持,使用org.springframework.transaction.annotation.Transactional或javax.ejb.TransactionAttribute来驱动事务。 Yes
org.springframework.web.servlet.config.annotation.EnableWebMvc 支持具有请求处理方法的强大且灵活的注释驱动控制器。此功能使用org.springframework.stereotype.Controller注释检测 bean,并将方法与org.springframework.web.bind.annotation.RequestMapping注释绑定到 URL。 Yes
org.springframework.web.reactive.config.EnableWebFlux 使用 Spring Web MVC 中的知名概念支持强大且灵活的反应式 Web 实现,并在可能的情况下对其进行扩展。 Yes

"../images/300017_2_En_2_Chapter/300017_2_En_2_Fige_HTML.jpg" 有关这些功能的更多信息,我们建议您查看不同注释的 Java 文档和专门的参考指南章节。

3.6.面向切面编程

核心AOP 概念

Concept 描述
Aspect 一个横切关注点的模块化。通常,这是一个带有org.aspectj.lang.annotation.Aspect注释的Java 类。
Join Point 一个程序执行过程中的一点。这可以是执行一种方法、分配一个字段或处理异常。在 Spring 中,一个连接点总是执行一种方法!
Advice 方面在一个特定连接点采取的特定操作。有几种类型的通知:before,after,after throwing,after return和around。在 Spring 中,通知被称为**拦截器,**因为我们正在拦截方法调用。
Pointcut 一个匹配连接点的谓词。该通知与一个切入点表达式相关联,并在与该切入点匹配的任何连接点处运行。Spring 默认使用 AspectJ 表达式语言。连接点可以使用org.aspectj.lang.annotation.Pointcut注释编写。

现在让我们看看事务管理以及 Spring 如何使用 AOP 围绕方法应用事务。

事务通知或拦截器是org.springframework.transaction.interceptor.TransactionInterceptor. 这个建议是围绕方法放置的org.springframework.transaction.annotation.Transactional注解。

为此,Spring 在实际对象周围创建了一个包装器,称为代理。代理就像一个封闭的对象,但它允许添加(动态)行为(在这种情况下,是方法的事务性)。

一种代理方法调用
所述org.springframework.transaction.annotation.EnableTransactionManagement注释寄存器含有切入点(作用于豆org.springframework.transaction.annotation.Transactional注释)。至此,拦截器就可以使用了。用于启用功能的其他注释的工作方式类似;他们注册 bean 以启用所需的功能,包括大多数功能的 AOP(以及代理创建)。

4.网络应用

那么所有这些技术如何应用于一个Web 应用程序 呢?例如,应用程序上下文如何发挥一种作用?那么提到的所有其他事情呢?

在开发一个 Web 应用程序时,有实际的业务逻辑(例如,服务、存储库和基础设施信息)和基于 Web 的 bean。这些东西应该是分开的,所以我们需要有多个应用上下文和关系。

我们还需要引导我们的应用程序的代码,否则什么也不会发生。在本示例中,我们使用一个MoneyTransferSpring类和一个 main 方法来启动应用程序上下文。这不是我们在一个网络环境中可以做到的。Spring 附带两个可以引导应用程序的组件:org.springframework.web.servlet.DispatcherServlet和org.springframework.web.context.ContextLoaderListener。两个组件都引导并配置应用程序上下文。

我们看一下配置的类 DispatcherServlet. 这是com.xxx.prospringmvc.bookstore.web.BookstoreWebApplicationInitializer类.我们的 Servlet 3.0+ 容器检测到这个类,并初始化我们的应用程序。我们创造了DispatcherServlet 并通过它 org.springframework.web.context.support.AnnotationConfigWebApplicationContext. 接下来,我们将 servlet 映射到所有内容(“/”)并告诉它在启动时加载。

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
java复制代码package com.xxx.prospringmvc.bookstore.web;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import com.xxx.prospringmvc.bookstore.web.config.WebMvcContextConfiguration;
public class BookstoreWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(final ServletContext servletContext) throws ServletException {
registerDispatcherServlet(servletContext);
}
private void registerDispatcherServlet(final ServletContext servletContext) {
WebApplicationContext dispatcherContext =
createContext(WebMvcContextConfiguration.class);
DispatcherServlet dispatcherServlet =
new DispatcherServlet(dispatcherContext);
ServletRegistration.Dynamic dispatcher =
servletContext.addServlet("dispatcher", dispatcherServlet);
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("*.htm");
}
private WebApplicationContext createContext(final Class<?>... annotatedClasses) {
AnnotationConfigWebApplicationContext context =
new AnnotationConfigWebApplicationContext();
context.register(annotatedClasses);
return context;
}
}

让我们通过添加一个让事情变得更有趣 ContextLoaderListener类,以便我们可以拥有父上下文和子上下文。新注册的监听器使用com.xxx.prospringmvc.bookstore.config.InfrastructureContextConfiguration来确定要加载哪些 bean。已经配置好的DispatcherServlet 自动检测由加载的应用程序上下文 ContextLoaderListener.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码package com.xxx.prospringmvc.bookstore.web;
import org.springframework.web.context.ContextLoaderListener;
import com.xxx.prospringmvc.bookstore.config.InfrastructureContextConfiguration;
// other imports omitted, see listing 2-11
public class BookstoreWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(final ServletContext servletContext) throws ServletException {
registerListener(servletContext);
registerDispatcherServlet(servletContext);
}
// registerDispatcherServlet method ommitted see Listing 2-11
// createContext method omitted see Listing 2-11
private void registerListener(final ServletContext servletContext) {
AnnotationConfigWebApplicationContext rootContext =
createContext(InfrastructureContextConfiguration.class);
servletContext.addListener(new ContextLoaderListener(rootContext));
}
}

下面的代码是我们的主要应用程序上下文。它包含我们的服务和存储库的配置。此清单还显示了我们的 JPA 实体管理器,包括其基于注释的事务支持。

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
java复制代码package com.xxx.prospringmvc.bookstore.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = {
"com.xxx.prospringmvc.bookstore.service",
"com.xxx.prospringmvc.bookstore.repository"})
public class InfrastructureContextConfiguration {
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean();
emfb.setDataSource(dataSource);
emfb.setJpaVendorAdapter(jpaVendorAdapter());
return emfb;
}
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build();
}
}

关于WebApplicationInitializer和DispatcherServlet 不懂的请查看tomcat如何配置控制器的请求路径

要配置核心分发控制器Servlet,即DispatcherServlet,传统做法是直接使用xml进行配置,如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
xml复制代码<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/dispatcher-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

本质上 就是将xml 变成了javabean

5.Spring Boot

前面提到的所有内容也适用于 Spring Boot。Spring Boot 构建并扩展了 Spring Framework 的功能。然而,它确实让事情变得容易多了。默认情况下,Spring Boot 会自动配置它在类路径上找到的功能。当 Spring Boot 检测到 Spring MVC 类时,它会启动 Spring MVC。当它找到一个DataSource实现时,它会引导它。

可以通过向application.properties或application.yml文件添加属性来完成自定义。您可以通过它配置数据源、视图处理和服务器端口等。

另一种选择是手动配置事物,就像在一个常规 Spring 应用程序中所做的那样。当 Spring Boot 检测到一项功能的预配置部分时,它通常会避免自动配置该功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码package com.xxx.prospringmvc.bookstore;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
@SpringBootApplication
public class BookstoreApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(BookstoreApplication.class);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(BookstoreApplication.class);
}
}

6.总结

涵盖了 Spring Core 的基本知识。我们回顾了依赖注入并简要介绍了依赖注入的三个不同版本。我们还介绍了基于构造函数、基于 setter 和基于注解的依赖注入。

接下来,我们进入 Spring 世界并检查org.springframework.context.ApplicationContext,包括它们在我们的应用程序中扮演的角色。我们还解释了不同的应用程序上下文(例如,基于 XML 或 Java 的)以及它们中的每一个的资源加载。在我们的 Web 环境中,我们在org.springframework.web.context.WebApplicationContext接口的实现中使用应用程序上下文的一个专门版本。我们还介绍了默认情况下应用程序上下文中的 bean 是如何单例作用域的。幸运的是,Spring 为我们提供了额外的作用域,例如request、session、globalSession、prototype、application和线程。

为了在不同的环境中使用不同的配置,Spring 还包含配置文件。我们简要解释了如何启用配置文件以及如何使用它们。当我们测试它并将其部署到 Cloud Foundry 时,我们会在我们的示例应用程序中使用配置文件。

我们还深入研究了 Spring 需要几个启用注释来启用某些功能的方式。这些注释在应用程序上下文中注册了额外的 bean,以启用所需的功能。大多数这些特性依赖于 AOP 的启用(例如,声明性事务管理)。Spring 创建代理以将 AOP 应用于在我们的应用程序上下文中注册的 bean。

最后,我们快速浏览了 Spring Boot 以及它如何让我们作为软件开发人员的生活变得轻松。Spring Boot 使用自动配置来配置在类路径上检测到的功能。它在需要的地方建立并扩展了 Spring 框架。

下一章着眼于 MVC Web 应用程序的架构、不同的层以及它们在我们的应用程序中的作用。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…506507508…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%