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,然后演示如何使用基于 XML 的 Spring 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. 调用 BeanNameAware 的 setBeanName() 方法。
  4. 调用 BeanFactoryAware 的 setBeanFactory() 方法。
  5. 调用 ApplicationContextAware 的 setApplicationContext() 方法。
  6. 调用 BeanPostProcessor 的预初始化方法。
  7. 调用 InitalizingBean 的 afterPropertiesSet() 方法。
  8. 调用自定义的初始化方法。
  9. 调用 BeanPostProcessor 的初始化后方法。
  10. 使用 bean。
  11. 调用 DisposableBean 的 destory() 方法。
  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级生命周期接口:BeanNameAware、BeanFactoryAware、和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,分别名为 ScopeService,SingletonService,SingletonService 和 SessionService。并修改 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 MHz 和 2 MHz,79 个跳频点中至少 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:设备实际运行的时钟。

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

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

    3.3.蓝牙物理链路

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

  • ACL(Asynchronous Connectionless)异步无连接链路;对时间要求不敏感的数据通信,如文件数据、控制信令等。
  • SCO(Synochronous 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 有效载荷所采用的纠错码方法。1为1/3比例 FEC,设备2个时隙发送一个单时隙分组;2 为 2/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 注解中的 iceCream 为 IceCream 类名首字母变为小写的结果。

当然,你也可以像这样使用 @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 mW(20 dBm)、2.5 mW(4 dBm)和1 mW(0 dBm),相应的有效工作范围为:100 米、10 米和 1 米。

3.蓝牙系统组成

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

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

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

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

4.蓝牙协议规范

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

4.1.传输协议

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

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

4.2.中介协议

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

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

4.3.应用协议

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

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

5.参考链接

蓝牙简介

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

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