Vim 基本操作

1.打开文件

安装 vim,进入相应文件夹,输入

$ vim filename

即可打开相应的文件,我们这里打开一个名为 vimtest.txt 的文件。

K、J、H、K 分别对应着上下左右,当然,小键盘左边的方向键也是可以实现光标移动的。

2.插入字符

现在我们想要在文件的末尾插入一行字符:helloworld !

把光标移动到文件末尾,然后按下字母键 i 。我们可以看到光标从覆盖整个字符变成了一个白色的下划线。

然后在文件的末尾输入我们想要输入的字符:helloworld!

现在我们已经输入进去一行字符,那么,怎么保存退出呢?

首先按下【ESC】进入命令模式,然后输入一个命令:

1
:wq

然后我们就成功地保存退出了。

再次打开这个文件,发现我们刚才保存的内容仍然是存在的。

学会了插入,删除也是同理,至少现在我们已经能够将 vim 当做记事本用了。但是,如果我们仅仅掌握这点功能的话,还不如使用 notepad++ 来得方便,所以接下来我们介绍一些高级点的技能。

3.字符搜索

输入:

1
/hello

即可完成在全文中对字符串 hello 的搜索。

输入:

1
n

即可跳转到下一处。当然你也可以按 i 进入插入模式来修改字符,不过如果你想替换全文中所有的 hello 的话,那么 :s 也许是一个不错的选择。

1
:s/hello/hi/g

如图所示,我们已经把文中所有的 hello 替换成了 hi 。这貌似也没什么大不了,毕竟我用 notepad++ 也可以实现同样的工作。那么下面的问题可就是 notepad++ 实现不了的了。

我们现在要把前三行所有的 i 替换成 w

注意,我们只替换前三行。嗯,这个问题对于 notepad++ 来说可能有点变态,不过对于 vim ……

1
:1,3s/i/w/g

只有前三行的 i 被替换成了 w。命令结尾的 g 表示会对所有符合情况的 i 进行操作,否则只替换第一个。

4.分屏操作

说到 vim,就一定不能够错过它的分屏功能。输入

1
:sp

就能得到上下两个屏幕,这对于只能显示黑框框的 shell 来说确实是很必要的功能。

当然,如果两个屏幕只能看一个文件,那还不如一个屏幕,选择一个屏幕(如果没有鼠标的话可以用)

1
Ctrl + w + j //(j 的意思是“下”)

来实现切换,然后输入:

1
:open car.txt

来实现文档的切换。

vim 还有一个类似的功能叫做 newtab,可以像浏览器一样显示标签,具体我就不在赘述了,有兴趣的话可以查阅后面的表格。

5.常用功能一览

5.1.跳转

1
2
3
4
5
6
7
8
9
10
11
12
* 当光标停留在一个单词上,* 键会在文件内搜索该单词,并跳转到下一处;
# 当光标停留在一个单词上,# 在文件内搜索该单词,并跳转到上一处;
(/) 移动到 前/后 句 的开始;
{/} 跳转到 当前/下一个 段落 的开始。
g_ 到本行最后一个不是 blank 字符的位置。
fa 到下一个为 a 的字符处,你也可以fs到下一个为s的字符。
t, 到逗号前的第一个字符。逗号可以变成其它字符。
3fa 在当前行查找第三个出现的 a。
F/T 和 f 和 t 一样,只不过是相反方向;
gg 将光标定位到文件第一行起始位置;
G 将光标定位到文件最后一行起始位置;
NG或Ngg 将光标定位到第 N 行的起始位置。

5.2.搜索

1
2
3
4
/str1 正向搜索字符串 str1;
n 继续搜索,找出 str1 字符串下次出现的位置;
N 继续搜索,找出 str1 字符串上一次出现的位置;
?str2 反向搜索字符串 str2 。

5.3.删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
rc 用 c 替换光标所指向的当前字符;
nrc 用 c 替换光标所指向的前 n 个字符;
5rA 用 A 替换光标所指向的前 5 个字符;
x 删除光标所指向的当前字符;
nx 删除光标所指向的前 n 个字符;
3x 删除光标所指向的前 3 个字符;
dw 删除光标右侧的字;
ndw 删除光标右侧的 n 个字;
3dw 删除光标右侧的 3 个字;
db 删除光标左侧的字;
ndb 删除光标左侧的 n 个字;
5db 删除光标左侧的 5 个字;
dd 删除光标所在行,并去除空隙;
ndd 删除(剪切) n 行内容,并去除空隙;
3dd 删除(剪切) 3 行内容,并去除空隙;

5.4.替换

1
2
3
4
5
6
7
8
9
10
11
12
13
s 用输入的正文替换光标所指向的字符;
S 删除当前行,并进入编辑模式;
ns 用输入的正文替换光标右侧 n 个字符;
nS 删除当前行在内的 n 行,并进入编辑模式;
cw 用输入的正文替换光标右侧的字;
cW 用输入的正文替换从光标到行尾的所有字符(同 c$ );
ncw 用输入的正文替换光标右侧的 n 个字;
cb 用输入的正文替换光标左侧的字;
ncb 用输入的正文替换光标左侧的 n 个字;
cd 用输入的正文替换光标的所在行;
ncd 用输入的正文替换光标下面的 n 行;
c$ 用输入的正文替换从光标开始到本行末尾的所有字符;
c0 用输入的正文替换从本行开头到光标的所有字符。

5.5.复制粘贴

1
2
3
4
5
6
7
yy 复制当前行到内存缓冲区;
nyy 复制 n 行内容到内存缓冲区;
5yy 复制 5 行内容到内存缓冲区;
“+y 复制 1 行到操作系统的粘贴板;
“+nyy 复制 n 行到操作系统的粘贴板。
p 小写字母 p,将缓冲区的内容粘贴到光标的后面;
P 大写字母 P,将缓冲区的内容粘贴到光标的前面。

5.6.正则表达式

1
2
3
4
5
6
7
8
9
10
^ 放在字符串前面,匹配行首的字;
$ 放在字符串后面,匹配行尾的字;
\< 匹配一个字的字头;
\> 匹配一个字的字尾;
. 匹配任何单个正文字符;
[str] 匹配 str 中的任何单个字符;
[^str] 匹配任何不在 str 中的单个字符;
[a-b] 匹配 a 到 b 之间的任一字符;
* 匹配前一个字符的 0 次或多次出现;
\ 转义后面的字符。

6.参考链接

vim 入门基础

vim 官方文档

Spring中的AOP

1.什么是AOP

AOP(面向切面编程)给我们提供了很好的非入侵式(在不修改源代码的情况下)的手段为现有的类增加新的功能。本文通过演示 Spring AOP 是如何使用的来介绍什么是 AOP

我们会首先演示如何使用基于注解的 Spring AOP,然后演示如何使用基于 XMLSpring AOP。最后对 Spring AOP 进行简要的总结。

首先我们需要定义一个目标对象(接口),这个对象在调用某些方法时(之前或之后)会被拦截,然后执行我们自己的代码。

1
2
3
4
package concert;
public interface Performance {
public void perform();
}

假定上面的对象就是我们的目标类。我们希望在该类调用 perform() 方法之前和之后分别输出一些提示,但是处于某些原因,我们又不想修改它的源代码。为了实现这一目标,我们需要使用 AOP 来达到我们的目的。

2.基于注解的 Spring AOP

下面是一个 Audience 类,它在本文中作为一个切面来使用。也就是说,在 Performance 类调用 perform() 前后输出的提示,其实是由 Audience 提供的:

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
package concert;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public calss Audience {
// 把 Performance 的 perform 方法定义为名为 performance 的切点
@Pointcut("execution(** concert.Performance.perform(..))")
public void performance() {}
// 表演之前输出
@Before("performance()")
public void silencePhones() {
System.out.println("Siliencing phones.");
}
// 表演之后输出
@AfterReturning("performance()")
public void clap() {
System.out.println("Claping.");
}
// 表演失败(抛出异常)输出
@AfterThrowing(“Performance()”)
public void refund() {
System.out.println("Demanding a refund");
}
}

为了使 Spring 能够识别 @Aspect 注解,我们需要在 JavaConfig 中启用 AspectJ 注解的自动代理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package concert;
import org.aspectj.lang.annotation.Bean;
import org.aspectj.lang.annotation.ComponentScan;
import org.aspectj.lang.annotation.Configuration;
import org.aspectj.lang.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class ConcertConfig {
@Bean
public Audience audience() {
return new Audience();
}
}

3.基于 XML 的 Spring AOP

使用注解的 AOP 能够在不侵入目标类的情况下,对目标类的方法进行监控。但是,如果切面 Audience 的代码中有许多注解。试想,如果我们连切面的代码都不想修改,或者无法修改切面的源代码,我们又该如何使用 AOP 呢?XML 为我们提供了一种解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package concert;
public calss Audience {
public void silencePhones() {
System.out.println("Siliencing phones.");
}
public void clap() {
System.out.println("Claping.");
}
public void refund() {
System.out.println("Demanding a refund");
}
}

我们去掉了 Audience 中所有的注解,当然这样的一个类是无法实现 AOP 的,因此我们需要在XML 中进行配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<aop:config>
<aop:aspect ref="audience">
<aop:pointcut
id="performance"
expression="execution(** concert.Performance.perform(..))" />
<aop:before
pointcut-ref="performance"
method="soliencePhones"/>
<aop:after-returning
pointcut-ref="performance"
method="clap"/>
<aop:after-throwing
pointcut-ref="performance"
method="refund"/>
</aop:aspect>
</aop:config>

这样一来,我们就可以在两个类都是完全的 pojo 类(纯 java 代码实现的普通类)的情况下实现 aop 了。

Eclipse搭建SpringBoot(五)使用拦截器验证用户登录

1.使用拦截器验证用户登录的手段

目前网上提供的拦截器验证用户登录主要分为两种手段:

把拦截器声明成一个注解加到 Controller 上

如:springboot(八)拦截器之验证登录

在 WebConfig.java 中添加拦截器相关规则,

如:SpringBoot学习笔记(六):配置拦截器,控制登录跳转

本文中采用第二种方案,因为它实现起来比较简单。

2.实现步骤

2.1.创建拦截器

在项目中添加 com.yun.interceptor 包,并在其中添加 MyInterceptor 类,其内容如下:

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
package com.yun.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class MyInterceptor implements HandlerInterceptor {
Logger logger = LoggerFactory.getLogger(MyInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// TODO Auto-generated method stub
//获取session
HttpSession session = request.getSession(true);
//判断用户ID是否存在,不存在就跳转到登录界面
if(session.getAttribute("userId") == null){
logger.info("------:跳转到login页面!");
response.sendRedirect(request.getContextPath()+"/login");
return false;
}else{
session.setAttribute("userId", session.getAttribute("userId"));
return true;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// TODO Auto-generated method stub
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// TODO Auto-generated method stub
}
}

2.2.添加 WebConfig.java 文件

在项目中创建 com.yun.config 包,创建 WebConfig.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
36
37
38
39
40
41
42
43
44
45
46
package com.yun.config;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ResourceUtils;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import com.yun.interceptor.MyInterceptor;
@Configuration
@EnableWebMvc
@ComponentScan
public class WebConfig extends WebMvcConfigurerAdapter implements ApplicationContextAware {
private ApplicationContext applicationContext;
public WebConfig(){
super();
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations(ResourceUtils.CLASSPATH_URL_PREFIX+"/static/");
registry.addResourceHandler("/templates/**").addResourceLocations(ResourceUtils.CLASSPATH_URL_PREFIX+"/templates/");
super.addResourceHandlers(registry);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//拦截规则:除了login,其他都拦截判断
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").excludePathPatterns("/login");
super.addInterceptors(registry);
}
}

2.3.修改 YunApplication.java 文件

在 YunApplication.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
36
37
38
39
40
41
42
43
package com.yun;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.yun.service.LoggerService;
import com.yun.service.ScopeService;
import com.yun.service.UserService;
/*
* 控制器使用示例
*/
@SpringBootApplication
@Controller
public class YunApplication {
@ResponseBody
@RequestMapping("/")
public String greeting() {
return "Hello World!";
}
// 下面是需要添加到 YunApplication.java 中的代码
// 本文件中的其他内容只是为了说明这段代码应该添加到什么位置
@ResponseBody
@RequestMapping("/login")
public String login(HttpSession session) {
session.setAttribute("userId", "jackie");
return "login";
}
// 需要添加的代码到此为止
}

3.登录测试

本文中主要强调利用拦截器验证登录与否,因此 login 页面就直接帮助用户以 jackie 的身份登录了。

首先,在浏览器中输入:

1
localhost:8080

然后敲击回车访问,发现网站自动跳转到 login 页面:

再次输入:

1
localhost:8080

然后回车访问,发现已经可以正常地访问了:

4.参考链接

springboot(八)拦截器之验证登录

SpringBoot学习笔记(六):配置拦截器,控制登录跳转

Spring中bean的生命周期

1.传统 Java 应用 bean 的生命周期

  1. 使用 Java 关键字 new 进行 bean 实例化。
  2. 使用这个 bean
  3. Java 自动进行垃圾回收。

2.Spring 中 bean 的生命周期

  1. 实例化。
  2. 填充属性。
  3. 调用 BeanNameAwaresetBeanName() 方法。
  4. 调用 BeanFactoryAwaresetBeanFactory() 方法。
  5. 调用 ApplicationContextAwaresetApplicationContext() 方法。
  6. 调用 BeanPostProcessor 的预初始化方法。
  7. 调用 InitalizingBeanafterPropertiesSet() 方法。
  8. 调用自定义的初始化方法。
  9. 调用 BeanPostProcessor 的初始化后方法。
  10. 使用 bean。
  11. 调用 DisposableBeandestory() 方法。
  12. 调用自定义的销毁方法。

3.Spring 中 bean 生命周期的解释

3.1.bean 生命周期的本质

就一个对象而言,其声明周期可以概括为:

  1. 创建(实例化 + 初始化)
  2. 使用
  3. 销毁

Spring 中的 bean 而言,Spring 只是提供了一些接口,允许开发者在:实例化初始化销毁

的前后进行一些操作。

3.2.bean 相关方法分类

  1. Bean自身方法init-method/destroy-method,通过为配置文件bean定义中添加相应属性指定相应执行方法。
  2. Bean级生命周期接口BeanNameAwareBeanFactoryAware、和InitializingBean 。这些接口的方法,每个 bean 选择实现,可选择各自的个性化操作。
  3. 容器级生命周期接口方法BeanPostProcessor 等 ,一般称它们的实现类为“后处理器”,这些接口是每个 bean 实例化或初始化时候都会调用。

3.3.简单描述

bean 的生命周期,主要就是在实例化填充属性之后,让 bean 知道自己的名称工厂上下文 ,并在自定义的初始化方法前后调用预初始化(前)、填充属性后(前)初始化后(后)方法。

4.参考链接

Spring Bean生命周期详解

Spring中bean的作用域的实验

本文是 Spring中bean的作用域 的相关实验,目的是演示 bean 的作用域是如何使用的。

1.实验内容

我们要进行一个实验,看看 bean 的作用域是否能整的响起描述的那样运作。为此,我们选了三种作用域:单例请求会话。我们为每一中作用域建立一个 Service,这样我们就能看到何时会创建新的 bean 来注入。

值得一提的是,因为我们的 Service 类并没有实现接口,因此我们就不能使用基于接口的 JDK 动态代理。所以我们就只好使用基于 CgLib 的动态代理。

在 Spring 中,两种动态代理的设置方式分别为:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 基于 CgLib 的动态代理
@Service
@Scope(
value=WebApplicationContext.SCOPE_REQUEST,
proxyMode=ScopedProxyMode.TARGET_CLASS)
public class RequestService { ... }
// 基于 JDK 的动态代理
@Service
@Scope(
value=WebApplicationContext.SCOPE_REQUEST,
proxyMode=ScopedProxyMode.INTERFACES)
public class RequestService { ... }

本实验中我们使用的是前者,

2.实验代码

com.yun.service 包下创建四个 Service,分别名为 ScopeServiceSingletonServiceSingletonServiceSessionService。并修改 YunApplication 中的内容,其内容分别如下:

2.1.ScopeService.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
36
37
38
39
40
41
42
43
44
package com.yun.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Service
public class ScopeService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private SingletonService singletonService;
@Autowired
private SessionService sessionService;
@Autowired
private RequestService requestService;
public String getScopeId() {
logger.info("=========================================");
logger.info("requestService id is:" + requestService.getId());
logger.info("sessionService id is:" + sessionService.getId());
logger.info("singletonService id is:" + singletonService.getId());
return "request id is : " + requestService.getId() + " | " +
"session id is : " + sessionService.getId() + " | " +
"singleton id is : " + singletonService.getId();
}
public void setScopeId(Integer id) {
requestService.setId(id);
sessionService.setId(id);
singletonService.setId(id);
logger.info("=========================================");
logger.info("requestService id set:" + id);
logger.info("sessionService id set:" + id);
logger.info("singletonService id set:" + id);
}
}

2.2.SingletonService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.yun.service;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
@Service
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class SingletonService {
private Integer id;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}

2.3.RequestService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.yun.service;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Service;
import org.springframework.web.context.WebApplicationContext;
@Service
@Scope(
value=WebApplicationContext.SCOPE_REQUEST,
proxyMode=ScopedProxyMode.TARGET_CLASS)
public class RequestService {
private Integer id;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}

2.4.SessionService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.yun.service;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.web.context.WebApplicationContext;
@Service
@Scope(
value=WebApplicationContext.SCOPE_SESSION,
proxyMode=ScopedProxyMode.TARGET_CLASS)
public class SessionService {
private Integer id;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}

2.5.YunApplication.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
package com.yun;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.yun.service.ScopeService;
@SpringBootApplication
@Controller
public class YunApplication {
@Autowired
private ScopeService scopeService;
@ResponseBody
@RequestMapping("/setscopeid/{id}")
public String setScopeId(@PathVariable("id") Integer id) {
scopeService.setScopeId(id);
return "id is set by " + id;
}
@ResponseBody
@RequestMapping("/getscopeid")
public String getScopeId() {
return scopeService.getScopeId();
}
}

3.实验结果

3.1.本地测试

3.1.1.设定 bean 的属性值

在浏览器输入:

1
http://localhost:8080/setscopeid/25

得到结果如图所示:

3.1.2.读取 bean 的属性值

在浏览器输入:

1
http://localhost:8080/getscopeid

可见,作用域是 request 的属性值已经读取不到了,但是 session 和 singleton 的仍然能够读取到。

3.2.远程测试

3.2.1.获取本机 ip

为了测试 session 作用域的效果,我们使用另一台设备对我们的应用进行访问。在那之前,我们需要知道本机的 ip 地址。在控制台输入:

1
ipconfig

如图所示,我的本机 ip 为 10.29.138.44。

3.2.2.远程访问

如果我要在另一台设备上访问我的主机,那么我就需要在浏览器输入:

1
10.29.138.44:8080/getscopeid

可见 session 在另一台机器上已经不能用了,然而 singleton 依旧保持着原来的值。

3.2.3.在本机上使用不同的 session

当然,如果你不想使用另一台计算机的话,在同一计算机上的另一个浏览器也是不同的 session

或者你把原来的浏览器重启一下:

Eclipse搭建SpringBoot(四)使用LogBack

1.为什么使用 Logback

我们在使用调试 java 代码的时候,一般使用:

1
System.out.println("Hello worold!");

来输出一些信息到控制台。

但是在实际开发中,一般使用日志记录工具来完成这件事。这么做的目的主要是利用日志的不同级别,来过滤掉不想要的信息。Logback 一般使用一下四个级别:

1
2
3
4
logger.debug("This is a debug message");
logger.info("This is an info message");
logger.warn("This is a warn message");
logger.error("This is an error message");

2.如何使用Logback

Logback 的使用仅需要很简单的配置:

2.1.引入相关包

下面是一个使用 Logback 的简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.yun.service;
import org.springframework.stereotype.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Service
public class LoggerService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public void run() {
logger.debug("This is a debug message");
logger.info("This is an info message");
logger.warn("This is a warn message");
logger.error("This is an error message");
}
}

输出结果为:

为什么没有 debug 的信息呢?这是因为默认的日志级别只到 info。为了输出 debug 的信息,我们需要进行一些额外的配置。

2.2.控制输出级别

application.yml 中添加如下配置:

1
2
3
4
logging:
file: yun.log
level:
ROOT : debug

再运行一下,就能看到有 debug 信息的输出结果了:

蓝牙协议入门(二)传输层协议

1.传输层协议

传输层位于蓝牙系统的底层,负责蓝牙设备间,互相确认对方的位置,以及建立和管理蓝牙设备间的物理链路。其又分为射频层、基带层和链路管理层三部分。

2.射频层协议

2.1.工作频率

蓝牙工作在 2.4 GHz ISM 频段上,蓝牙采用跳频扩谱技术主动的避免工作频段受干扰。我国的蓝牙频率在 2.402 GHz~2.483 GHz,蓝牙每个频道的宽度为1 MHz,为了减少带外辐射的干扰,保留上、下保护为3.5 MHz2 MHz79 个跳频点中至少 75 个伪随机码跳动,30 s 内任何一个频点使用时长不能超过 0.4 s

地理位置 ISM频段范围 射频信道频率
中国、美国、欧洲 2400.0~2483.5MHz F=(2402+k)MHz,k在0、1、……78中随机取值
法国 2446.5~2483.5MHz F=(2454+k)MHz,k在0、1、……22中随机取值
日本 2471.0~2497.0MHz F=(2473+k)MHz,k在0、1、……22中随机取值
西班牙 2445.0~2475.0MHz F=(2449+k)MHz,k在0、1、……22中随机取值

2.2.跳频技术、发射功率、时隙

  • 发射功率:蓝牙发射功率分三级:一级功率100 mW( 20 dBm);二级功率 2.5 mW( 4 dBm);三级功率1 mW( 0 dBm);
  • 物理信道:蓝牙物理信道有伪随机序列控制的 79 个跳频点构成,不同跳频序列代表不同的信道。
  • 时隙:蓝牙跳频速率为1600 次/s,每个时间为 625 us (1 s/1600) 称为一个时隙;

3.基带层协议

蓝牙发送数据时,基带部分将来自高层的数据进行信道编码,向下发给射频进行发送;接收数据时,将解调恢复空中数据并上传给基带,基带进行信道编码传送给上层。

3.1.蓝牙地址

蓝牙设备编码采取小端模式,即高位在后面。它的前 24 位是制造商分配的产品编号(LAP,低地址部分),中间8 位是 SIG 给制造商分配的编号(UAP,高地址部分),最后 16 位保留无效(NAP,无效地址部分)。

3.2.蓝牙时钟

每个蓝牙设备都有一个独立运行的内部系统时钟,称为本地时钟(Local Clock),决定定时器的收发跳频。为了与其他设备同步,本地时钟要加一个偏移量(offset),提供给其他设备同步。 具体说来,蓝牙系统中的时钟分为以下几种:

  • CLKN:本地时钟:
  • CLKE:预计时钟,扫描寻呼过程中用到;
  • CLK:设备实际运行的时钟。

这三者之间存在着相关关系,具体说来 CLKECLKCLKN 加上一个偏移量得到的。

  • 主设备CLK = CLKN
  • 从设备CLK = CLKN + offset

    3.3.蓝牙物理链路

通信设备间物理层的数据连接通道就是物理链路。

  • ACL(Asynchronous Connectionless)异步无连接链路;对时间要求不敏感的数据通信,如文件数据、控制信令等。
  • SCOSynochronous Connection Oriented)同步面向连接链路;对时间比较敏感的通信,如:语音;最多只支持3条 SCO 链路,不支持重传。

3.4.蓝牙基带分组

基带分组至少包括:接入码、分组头、有效载荷;

  • 接入码用于同步、直流、载频泄漏偏置补偿标识;
  • 分组头包含链路信息,确保纠正较多的错误。

详细分组类型如下:

分组类别 Type (b3b2b1b0) 时隙 SCO ACL
链路控制分组 0000 1 NULL NULL
0001 POLL POLL
0010 FHS FHS
0011 DM1 DM1
单时隙分组 0100 1 未定义 NULL
0101 HV1
0110 HV2
0111 HV3
1000 DV
1001 NULL AUX1
三时隙分组 1010 3 未定义 DM3
1011
1100 未定义
1101
五时隙分组 1110 5 未定义 DM5
1111
  • ACL分组形式为:D(M|H)(1|3|5),D代表数据分组,M代表用2/3比例的 FEC 的中等速率分组;H 代表不使用纠错码的高速率分组;1、3、5分别代表分组所占用的时隙数目;

    ​DM1、DM3、DM5、DH1、DH3、DH5

  • SCO分组形式为:HV(1|2|3)。HV代表高质量语言分组,1、2、3 有效载荷所采用的纠错码方法。11/3比例 FEC,设备2个时隙发送一个单时隙分组;22/3 比例 FEC ,设备4个时隙发送一个单时隙分组;3为不使用纠错码,设备6个时隙发送一个单时隙分组。

    ​HV1、HV2、HV3

3.4.1.ACL分组

类型 有效载荷头/字节 用户有效载荷/字节 FEC CRC 对称最大速率/kbps 非对称速率/kbps
前向 后向
DM1 1 0~17 2/3 108.8 108.8 108.8
DH1 1 0~27 172.8 172.8 172.8
DM3 2 0~121 2/3 258.1 387.2 54.4
DH3 2 0~183 390.4 585.6 86.4
DM5 2 0~224 2/3 286.7 477.8 36.3
MH5 2 0~339 433.9 723.2 57.6
AUX1 1 0~29 185.6 185.6 185.6

3.4.2.SCO分组

类型 有效载荷头/字节 用户有效载荷/字节 FEC CRC 有效载荷长度 同步速率/kbps 占用Tsco数目/语言长度
HV1 10 1/3 240位 64 2/1.25ms
HV2 20 2/3 4/2.5ms
HV3 30 6/3.75ms
6/3.75ms 1D 10+(0-9)D 2/3D 有D 64+57.6D

注释:D 表示只对数据段有用,DV 表示分组包含数据段,也包含语言段。

X.参考链接

蓝牙核心技术概述(三): 蓝牙协议规范(射频、基带链路控制、链路管理)

Spring中bean的作用域

1.为什么要用到 Spring 的作用域

默认情况下,Spring 中所有的 bean 都是以单例(singleton)模式创建的。但是在有些情况下,这种方式创建的 bean 可能无法满足我们的需要。

比如,我们要创建一个购物车的 bean 。如果购物车是以单例模式创建的话,那么所有顾客的商品都会被添加到同一个购物车里。显然,这不是我们想要的结果。

2.Spring 的作用域

Spring 定义了几种作用域,下面是常用的四种:

  1. 单例(Singleton):整个应用中只创建一个 bean 的实例。
  2. 原型(Prototype):每次注入或者通过 Spring 上下文获取的时候,创建一个新的 bean 实例。
  3. 会话(Session):在 Web 应用中,为每个会话创建一个 bean 实例。
  4. 请求(Request):在 Web 应用中,为每个请求创建一个 bean 实例。

关于 Spring 上下文的相关内容,可以参考下面这篇文章:

spring中的web上下文,spring上下文,springmvc上下文区别(超详细)

关于原型 Bean 作用域的相关内容,可以参考这篇文章:

Spring核心技术(五)——Spring中Bean的作用域

3.Spring 作用域的使用

Spring 使用 @Scope 注解来使用作用域,使用示例如下:

1
2
3
4
5
6
7
8
9
// 使用 @Scope 的一种方式
@Component
@Scope(ConfigurableBeanFactory.SCOPR_PROTOTYPE)
public class Notepad {...}
// 使用 @Scope 的另一种方式
@Component
@Scope("prototype")
public class Notepad {...}

4.参考链接

spring中的web上下文,spring上下文,springmvc上下文区别(超详细)

Spring中如何处理自动装配的歧义性

说明:本文为 Spring in action 的读书笔记,其内容部分参考该书。

1.Spring 自动装配的歧义性

我们已经知道,利用自动装配可以减少显式配置的数量,为我们带来很大的方便。但是,如果不仅有一个 bean 能够匹配的话,Spring 就无法进行自动装配了。

举个例子,我们使用 @Autowired 注解标注了 setDessert() 方法:

1
2
3
4
@Autowired
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}

与此同时,我们有两个类实现了这个接口,一个叫做 Cake,另一个叫做 IceCream

1
2
3
4
5
@Component
public class Cake implements Dessert {...}
@Component
public class IceCream implements Dessert {...}

那么此时 Spring 究竟是应当注入 Cake 呢?还是注入 IceCream 呢?Spring 也无法判断,因此它会抛出一个 NoUniqueBeanDedinitionException 的异常。

2.如何解决自动装配的歧义性

Spring 提供了多种方法来解决这个问题,说明如下:

2.1.使用 @primary 注解

@Primary 注解的使用方式如下:

1
2
3
@Component
@Primary
public class IceCream implements Dessert {...}

@Primary 注解使得在有多个满足条件的 bean 同时存在是,提供一种优先的解决方案。

2.2.使用 @Qualifier 注解

@Qualifier 注解的使用方式如下:

1
2
3
4
5
@Autowired
@Quatifier("iceCream")
public void serDessert(Dessert dessert) {
this.dessert = dessert;
}

@Qualifier 注解中的 iceCreamIceCream 类名首字母变为小写的结果。

当然,你也可以像这样使用 @Qualifier 注解。

1
2
3
4
5
6
7
8
9
@Component
@Qualifier("cold")
public class IceCream implements Dessert {...}
@Bean
@Qualifier("cold")
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}

上面的使用方式有利于解耦和代码重构。

2.3.自定义的注解

除此之外,我们还可以创建新的注解(如 @Cold)来代替 @Qualifier(“cold”)

1
2
3
4
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentPolicy.Runtime)
@Qualifier
public @interface Cold { }

一般情况下,这个接口放在 Cold.java 里。这样我们就可以直接使用注解 @Clod

1
2
3
4
5
@Bean
@Cold
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}

当然,我们也可以同时使用多个注解来缩减范围:

1
2
3
4
5
6
@Bean
@Cold
@Creamy
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}

3.参考链接

Spring高级装配之处理自动装配的歧义性

蓝牙协议入门(一)简介

1.蓝牙简史

蓝牙这个名称来自于第十世纪的一位丹麦国王哈拉尔蓝牙王,因为国王喜欢吃蓝莓,吃到牙齿都是蓝色的所以叫蓝牙。后来人们就用它的名字命名一种无线通讯技术,其结果就是我们今日的蓝牙。

蓝牙由阿里新公司研发,目前其发展主要由 Bluetooth SIG 控制,该组织现在有200多家联盟成员公司以及约6000家应用成员企业。

蓝牙共有六个版本V 1.1/1.2/2.0/2.1/3.0/4.0。我们这里列举几个重要的版本的信息:

  • V 1.1版本 传输率约在 748~810 kb/s,因是早期设计,容易受到同频率之产品所干扰下影响通讯质量。

  • V 3.0版本 蓝牙3.0的核心是” GenericAlternate MAC/PHY”(AMP) ,这是一种全新的交替射频技术,允许蓝牙协议栈针对任一任务动态地选择正确射频。最初被期望用于新规范的技术包括 802.11 以及 UMB ,但是新规范中取消了 UMB 的应用。

  • V 4.0 版本 蓝牙 4.0 包括三个子规范,即传统蓝牙技术高速蓝牙和新的蓝牙低功耗技术。蓝牙 4.0 的改进之处主要体现在三个方面,电池续航时间、节能和设备种类上。拥有低成本,跨厂商互操作性,3 毫秒低延迟、100 米以上超长距离、AES-128 加密等诸多特色此外,蓝牙 4.0 的有效传输距离也有所提升。3.0 版本的蓝牙的有效传输距离为10 米,而蓝牙4.0的有效传输距离最高可达到 100 米。

2.蓝牙技术特性

  • 工作频段2.4 GHz 的工科医(ISM)频段,无需申请许可证。大多数国家使用79个频点,载频为(2402+k)MHz(k=0,1, 2…78),载频间隔 1 MHz 。采用 TDD 时分双工方式。
  • 传输速率1 Mb/s
  • 采用跳频技术:跳频速率为1600 跳/秒,在建链时(包括寻呼和查询)提高为 3200 跳/秒。蓝牙通过快跳频和短分组技术减少同频干扰,保证传输的可靠性。
  • 支持电路交换和分组交换业务:蓝牙支持实时的同步定向联接(SCO 链路)和非实时的异步不定向联接( ACL 链路),前者主要传送语音等实时性强的信息,后者以数据包为主。语音和数据可以单独或同时传输。蓝牙支持一个异步数据通道,或三个并发的同步话音通道,或同时传送异步数据和同步话音的通道。每个话音通道支持 64 kbps 的同步话音;异步通道支持 723.2/57.6 kbps 的非对称双工通信或 433.9 kbps 的对称全双工通信。
  • 支持点对点及点对多点通信:蓝牙设备按特定方式可组成两种网络:微微网( Piconet )和分布式网络( Scatternet ),其中微微网的建立由两台设备的连接开始,最多可由八台设备组成。在一个微微网中,只有一台为主设备(Master),其它均为从设备(Slave),不同的主从设备对可以采用不同的链接方式,在一次通信中,链接方式也可以任意改变。几个相互独立的微微网以特定方式链接在一起便构成了分布式网络。所有的蓝牙设备都是对等的,所以在蓝牙中没有基站的概念。
  • 工作距离:蓝牙设备分为三个功率等级,分别是:100 mW20 dBm)、2.5 mW4 dBm)和1 mW0 dBm),相应的有效工作范围为:100 米、10 米和 1 米。

3.蓝牙系统组成

蓝牙系统由以下几部分组成:

  • 传输层(底层硬件模块)
  • 中介层(中间协议层)
  • 应用层

其中,底层硬件模块包括以下部分:

  • 射频层:负责数据和语音的发送和接收,特点是短距离、低功耗。蓝牙天线一般体积小、重量轻,属于微带天线。
  • 基带层:进行射频信号与数字或语音信号的相互转化,实现基带协议和其它的底层连接规程。
  • 链路管理层:负责管理蓝牙设备之间的通信,实现链路的建立、验证、链路配置等操作。

4.蓝牙协议规范

传输层、中介层、应用层拥有着各自的协议。

4.1.传输协议

传输协议负责蓝牙设备间,互相确认对方的位置,以及建立和管理蓝牙设备间的物理链路;

  • 底层传输协议:蓝牙射频(Radio)部分、基带链路管理控制器(Baseband & Link Controller)、链路管理协议(Link ManagerProtocolLMP。负责语言、数据无线传输的物理实现以及蓝牙设备间的联网组网。
  • 高层传输协议:逻辑链路控制与适配器(LogicalLink Control and Adaptation ProtocolL2CAP 、主机控制接口(HostControl InterfaceHCI)。为高层应用屏蔽了跳频序列选择等底层传输操作,为高层程序提供有效、有利于实现数据分组格式。

4.2.中介协议

中介协议为高层应用协议或者程序,在蓝牙逻辑链路上工作提供必要的支持,为应用提供不同标准接口,包括:

  • 串口仿真协议:RFCOMM
  • 服务发现协议:SDP
  • 互操作协议:IrDA
  • 网络访问协议:PPP、IP、TCP、UDP
  • 电话控制协议:TCSAT 指令集

4.3.应用协议

蓝牙协议栈之上的应用软件和所涉及到的协议,如:拨号上网、语言功能的应用程序,包括:

  • 通用应用类框架:查询、建立连接服务等
  • 蓝牙电话应用类框架:电话控制、语言
  • 蓝牙连网应用类框架:网络应用相关
  • 对象交互服务类框架:IrDA、OBEX
  • 蓝牙音视频控制类框架

5.参考链接

蓝牙简介

BlueTooth: 蓝牙技术及其系统原理

蓝牙核心技术概述(一):蓝牙概述