修改图片路径
4
.gitignore
vendored
@ -1,3 +1,3 @@
|
|||||||
pic
|
pic
|
||||||
**.docx
|
**.docx
|
||||||
**.doc
|
**.doc
|
@ -1,4 +1,4 @@
|
|||||||
# technology-note
|
# technology-note
|
||||||
记录技术笔记
|
记录技术笔记
|
||||||
|
|
||||||
说明:github不支持markdown中内嵌base64图片,故需要查看图片请使用其他markdown软件查看文章
|
说明:github不支持markdown中内嵌base64图片,故需要查看图片请使用其他markdown软件查看文章
|
@ -1,170 +1,170 @@
|
|||||||
  spring是为了解决企业级应用开发的复杂性而创建的,spring最根本的使命是:简化Java开发。为降低开发复杂性有以下四种关键策略。
|
  spring是为了解决企业级应用开发的复杂性而创建的,spring最根本的使命是:简化Java开发。为降低开发复杂性有以下四种关键策略。
|
||||||
|
|
||||||
- 基于POJO的轻量级和最小侵入性编程
|
- 基于POJO的轻量级和最小侵入性编程
|
||||||
- 通过依赖注入和面向接口实现松耦合
|
- 通过依赖注入和面向接口实现松耦合
|
||||||
- 基于切面和惯例进行声明式编程
|
- 基于切面和惯例进行声明式编程
|
||||||
- 通过切面和模板减少样板式代码
|
- 通过切面和模板减少样板式代码
|
||||||
|
|
||||||
#### 1.依赖注入
|
#### 1.依赖注入
|
||||||
|
|
||||||
  假设类A依赖类B,通常做法是在类A中声明类B,然后使用,这样一方面具有极高的耦合性,将类A与类B绑定在一起;另一方面也让单元测试变得很困难,无法在A外部获得B的执行情况。
|
  假设类A依赖类B,通常做法是在类A中声明类B,然后使用,这样一方面具有极高的耦合性,将类A与类B绑定在一起;另一方面也让单元测试变得很困难,无法在A外部获得B的执行情况。
|
||||||
|
|
||||||
  通过依赖注入,对象的依赖管理将不用对象本身来管理,将由一个第三方组件在创建对象时设定,依赖关系将被自动注入到对应的对象中去。
|
  通过依赖注入,对象的依赖管理将不用对象本身来管理,将由一个第三方组件在创建对象时设定,依赖关系将被自动注入到对应的对象中去。
|
||||||
|
|
||||||
#### 2.创建应用上下文
|
#### 2.创建应用上下文
|
||||||
|
|
||||||
- `ClassPathXmlApplicationContext()`从类路径创建
|
- `ClassPathXmlApplicationContext()`从类路径创建
|
||||||
- `FileSystemXmlApplicationContext()`读取文件系统下的xml配置
|
- `FileSystemXmlApplicationContext()`读取文件系统下的xml配置
|
||||||
- `XmlWebApplicationContext()` 读取web应用下的XML配置文件并装载上下文定义
|
- `XmlWebApplicationContext()` 读取web应用下的XML配置文件并装载上下文定义
|
||||||
|
|
||||||
#### 3.声明Bean
|
#### 3.声明Bean
|
||||||
|
|
||||||
1. 最简单
|
1. 最简单
|
||||||
|
|
||||||
`<bean id="bean1" class="com.example.Class"/>`
|
`<bean id="bean1" class="com.example.Class"/>`
|
||||||
|
|
||||||
2. 带构造器
|
2. 带构造器
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<bean id="bean1" class="com.example.Class">
|
<bean id="bean1" class="com.example.Class">
|
||||||
<contructor-arg value="12"/> //基本数据类型使用value
|
<contructor-arg value="12"/> //基本数据类型使用value
|
||||||
<contructor-arg ref="bean2"/> //对象使用ref
|
<contructor-arg ref="bean2"/> //对象使用ref
|
||||||
</bean>
|
</bean>
|
||||||
```
|
```
|
||||||
|
|
||||||
3. 通过工厂方法创建
|
3. 通过工厂方法创建
|
||||||
|
|
||||||
如果想声明的Bean没有一个公开的构造函数,通过factory-method属性来装配工厂生产的Bean
|
如果想声明的Bean没有一个公开的构造函数,通过factory-method属性来装配工厂生产的Bean
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<bean id="bean1" class="com.example.class" factory-method="getInstance"/>//getInstance为获取实例的静态方法。
|
<bean id="bean1" class="com.example.class" factory-method="getInstance"/>//getInstance为获取实例的静态方法。
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 4.Bean的作用域
|
#### 4.Bean的作用域
|
||||||
|
|
||||||
所有Spring Bean默认都是单例的。通过配置scope属性为prototype可每次请求产生一个新的实例。
|
所有Spring Bean默认都是单例的。通过配置scope属性为prototype可每次请求产生一个新的实例。
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<bean id="bean3" class="com.example.class" scope="prototype">
|
<bean id="bean3" class="com.example.class" scope="prototype">
|
||||||
```
|
```
|
||||||
|
|
||||||
scope可选值:
|
scope可选值:
|
||||||
|
|
||||||
- `singleton`:每个容器中,一个Bean对象只有一个实例。(**默认**)
|
- `singleton`:每个容器中,一个Bean对象只有一个实例。(**默认**)
|
||||||
- `prototype`:允许实例化任意次 ,每次请求都会创建新的
|
- `prototype`:允许实例化任意次 ,每次请求都会创建新的
|
||||||
- `request`:作用域为一次http请求
|
- `request`:作用域为一次http请求
|
||||||
- `session`:作用域为一个http session会话
|
- `session`:作用域为一个http session会话
|
||||||
- `global-session`:作用域为一个全局http session,仅在Protlet上下文中有效
|
- `global-session`:作用域为一个全局http session,仅在Protlet上下文中有效
|
||||||
|
|
||||||
#### 5.初始化和销毁Bean
|
#### 5.初始化和销毁Bean
|
||||||
|
|
||||||
当实例化需要执行初始化操作,或者销毁时需要执行清理工作。两种实现方式:
|
当实例化需要执行初始化操作,或者销毁时需要执行清理工作。两种实现方式:
|
||||||
|
|
||||||
1. xml配置,类中编写初始化方法和销毁方法,在bean中定义。
|
1. xml配置,类中编写初始化方法和销毁方法,在bean中定义。
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<bean id="bean4" class="com.example.Class" init-method="start" destroy-method="destroy"/>
|
<bean id="bean4" class="com.example.Class" init-method="start" destroy-method="destroy"/>
|
||||||
```
|
```
|
||||||
|
|
||||||
也可在Beans中定义默认初始化和销毁方法。
|
也可在Beans中定义默认初始化和销毁方法。
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<beans . . . default-init-method="" default-destroy-method=""/>
|
<beans . . . default-init-method="" default-destroy-method=""/>
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 实现`InitializingBean `和`DisposableBean`接口
|
2. 实现`InitializingBean `和`DisposableBean`接口
|
||||||
|
|
||||||
#### 6.setter注入
|
#### 6.setter注入
|
||||||
|
|
||||||
在bean中使用`<property>`元素配置属性,使用方法类似于`<constructor-arg>`
|
在bean中使用`<property>`元素配置属性,使用方法类似于`<constructor-arg>`
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<property name="name" value="fxg"/> //注入基本数据类型
|
<property name="name" value="fxg"/> //注入基本数据类型
|
||||||
<property name="sex" ref="sex"/> //注入类
|
<property name="sex" ref="sex"/> //注入类
|
||||||
```
|
```
|
||||||
|
|
||||||
可使用p简写,**-ref**后缀说明装配的是一个引用
|
可使用p简写,**-ref**后缀说明装配的是一个引用
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<bean id="bean5" class="com.example.class"
|
<bean id="bean5" class="com.example.class"
|
||||||
p:name="fxb"
|
p:name="fxb"
|
||||||
p:sex-ref="sex"/>
|
p:sex-ref="sex"/>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 7.注入内部Bean
|
#### 7.注入内部Bean
|
||||||
|
|
||||||
既定义其他Bean内部的Bean,避免共享问题,可在属性节点或者构造器参数节点上使用。
|
既定义其他Bean内部的Bean,避免共享问题,可在属性节点或者构造器参数节点上使用。
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<property name="sex">
|
<property name="sex">
|
||||||
<bean class="com.example.sex"/> //没有id属性,因为不会被其他bean使用
|
<bean class="com.example.sex"/> //没有id属性,因为不会被其他bean使用
|
||||||
</property>
|
</property>
|
||||||
<constructor-arg>
|
<constructor-arg>
|
||||||
<bean class="com.example.sex"/>
|
<bean class="com.example.sex"/>
|
||||||
</constructor-arg>
|
</constructor-arg>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 8.装配集合
|
#### 8.装配集合
|
||||||
|
|
||||||
| 集合元素 | 用途 |
|
| 集合元素 | 用途 |
|
||||||
| ---------------- | ------------------------------ |
|
| ---------------- | ------------------------------ |
|
||||||
| \<list\> | 装配list类型,允许重复 |
|
| \<list\> | 装配list类型,允许重复 |
|
||||||
| \<set\> | set,不能重复 |
|
| \<set\> | set,不能重复 |
|
||||||
| \<map\> | map类型 |
|
| \<map\> | map类型 |
|
||||||
| \<props\> | properties类型,键值都为String |
|
| \<props\> | properties类型,键值都为String |
|
||||||
|
|
||||||
- list
|
- list
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<property name="instruments">
|
<property name="instruments">
|
||||||
<list>
|
<list>
|
||||||
<ref bean="guitar"/>
|
<ref bean="guitar"/>
|
||||||
<ref bean="cymbal"/>
|
<ref bean="cymbal"/>
|
||||||
<ref bean="harmonica"/>
|
<ref bean="harmonica"/>
|
||||||
</list>
|
</list>
|
||||||
</property>
|
</property>
|
||||||
<ref>用来定义上下文中的其他引用,还可使用<value>,<bean>,<null/>
|
<ref>用来定义上下文中的其他引用,还可使用<value>,<bean>,<null/>
|
||||||
```
|
```
|
||||||
|
|
||||||
- set
|
- set
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<set>
|
<set>
|
||||||
<ref bean="fasdf"/>
|
<ref bean="fasdf"/>
|
||||||
</set>
|
</set>
|
||||||
```
|
```
|
||||||
|
|
||||||
用法和list相同,只是不能重复
|
用法和list相同,只是不能重复
|
||||||
|
|
||||||
- Map
|
- Map
|
||||||
|
|
||||||
```XML
|
```XML
|
||||||
<map>
|
<map>
|
||||||
<entry key="GUITAR" value-ref="guitar"/>
|
<entry key="GUITAR" value-ref="guitar"/>
|
||||||
</map>
|
</map>
|
||||||
```
|
```
|
||||||
|
|
||||||
entry元素由一个key,一个value组成,分别有两种形式。
|
entry元素由一个key,一个value组成,分别有两种形式。
|
||||||
|
|
||||||
| key | 键为String |
|
| key | 键为String |
|
||||||
| :-------- | -------------- |
|
| :-------- | -------------- |
|
||||||
| key-ref | 键为Bean的引用 |
|
| key-ref | 键为Bean的引用 |
|
||||||
| value | 值为String |
|
| value | 值为String |
|
||||||
| value-ref | 值为Bean的引用 |
|
| value-ref | 值为Bean的引用 |
|
||||||
|
|
||||||
- props
|
- props
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<props>
|
<props>
|
||||||
<prop key="GUITAR">guitar</prop>
|
<prop key="GUITAR">guitar</prop>
|
||||||
</props>
|
</props>
|
||||||
```
|
```
|
||||||
|
|
||||||
键值都是String
|
键值都是String
|
||||||
|
|
||||||
#### 9.装配空值
|
#### 9.装配空值
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<property name="name"><null/></property>
|
<property name="name"><null/></property>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1,223 +1,223 @@
|
|||||||
## 一、自动装配
|
## 一、自动装配
|
||||||
|
|
||||||
### 1、四种类型的自动装配
|
### 1、四种类型的自动装配
|
||||||
|
|
||||||
| 类型 | 解释 | xml配置 |
|
| 类型 | 解释 | xml配置 |
|
||||||
| ---------- | ------------------------------------ | ---------------------------------------------- |
|
| ---------- | ------------------------------------ | ---------------------------------------------- |
|
||||||
| byName | 根据Bean的name或者id | \<bean id="bean" class="…" autowire="byName"/> |
|
| byName | 根据Bean的name或者id | \<bean id="bean" class="…" autowire="byName"/> |
|
||||||
| ByType | 根据Bean类型自动装配 | \<bean id="bean" class="…" autowire="byType"/> |
|
| ByType | 根据Bean类型自动装配 | \<bean id="bean" class="…" autowire="byType"/> |
|
||||||
| contructor | 根据Bean的构造器入参具有相同类型 | 同上 |
|
| contructor | 根据Bean的构造器入参具有相同类型 | 同上 |
|
||||||
| Autodetect | 首先使用contructor,失败再尝试byType | 同上 |
|
| Autodetect | 首先使用contructor,失败再尝试byType | 同上 |
|
||||||
|
|
||||||
  byType在出现多个匹配项时不会自动选择一个然是报错,为避免报错,有两种办法:1.使用\<bean>元素的primary属性,设置为首选Bean,但所有bean的默认primary都是true,因此我们需要将所有非首选Bean设置为false;2.将Bean的`autowire-candidate`熟悉设置为**false **,取消 这个Bean的候选资格,这个Bean便不会自动注入了。
|
  byType在出现多个匹配项时不会自动选择一个然是报错,为避免报错,有两种办法:1.使用\<bean>元素的primary属性,设置为首选Bean,但所有bean的默认primary都是true,因此我们需要将所有非首选Bean设置为false;2.将Bean的`autowire-candidate`熟悉设置为**false **,取消 这个Bean的候选资格,这个Bean便不会自动注入了。
|
||||||
|
|
||||||
  contructor自动装配和byType有一样的局限性,当发现多个Bean匹配某个构造器入参时,Spring不会尝试选择其中一个;此外,如果一个类有多个构造器都满足自动装配的条件,Spring也不会猜测哪个更合适使用。
|
  contructor自动装配和byType有一样的局限性,当发现多个Bean匹配某个构造器入参时,Spring不会尝试选择其中一个;此外,如果一个类有多个构造器都满足自动装配的条件,Spring也不会猜测哪个更合适使用。
|
||||||
|
|
||||||
###2、默认自动装配
|
###2、默认自动装配
|
||||||
|
|
||||||
  如果需要为Spring应用上下文中的每个Bean(或者其中的大多数)配置相同的autowire属性,可以在根元素\<beans>上增加一个default-autowire属性,默认该属性设置为none。该属性只应用于指定配置文件中的所有Bean,并不是Spring上下文中的所有Bean。
|
  如果需要为Spring应用上下文中的每个Bean(或者其中的大多数)配置相同的autowire属性,可以在根元素\<beans>上增加一个default-autowire属性,默认该属性设置为none。该属性只应用于指定配置文件中的所有Bean,并不是Spring上下文中的所有Bean。
|
||||||
|
|
||||||
###3、混合使用自动装配和显式装配
|
###3、混合使用自动装配和显式装配
|
||||||
|
|
||||||
  当我们对某个Bean使用了自动装配策略,并不代表我们不能对该Bean的某些属性进行显示装配,任然可以为任意一个属性配置\<property>元素,显式装配将会覆盖自动装配。**但是**当使用constructor自动装配策略时,我们必须让Spring自动装配构造器所有入参,不能使用\<constructor-arg>元素进行混合。
|
  当我们对某个Bean使用了自动装配策略,并不代表我们不能对该Bean的某些属性进行显示装配,任然可以为任意一个属性配置\<property>元素,显式装配将会覆盖自动装配。**但是**当使用constructor自动装配策略时,我们必须让Spring自动装配构造器所有入参,不能使用\<constructor-arg>元素进行混合。
|
||||||
|
|
||||||
## 二、注解装配
|
## 二、注解装配
|
||||||
|
|
||||||
  从Spring2.5开始,可以使用注解自动装配Bean的属性,使用注解允许更细粒度的自动装配,可选择性的标注某一个属性来对其应用自动装配。Spring容器默认禁用注解装配,需要在Spring配置中启用,最简单的启用方式是使用Spring的context命令空间配置中的`<context:annotation-config>`,如下所示:
|
  从Spring2.5开始,可以使用注解自动装配Bean的属性,使用注解允许更细粒度的自动装配,可选择性的标注某一个属性来对其应用自动装配。Spring容器默认禁用注解装配,需要在Spring配置中启用,最简单的启用方式是使用Spring的context命令空间配置中的`<context:annotation-config>`,如下所示:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<beans ...>
|
<beans ...>
|
||||||
<context:annotation-config/>
|
<context:annotation-config/>
|
||||||
<!-- bean declarations go here -->
|
<!-- bean declarations go here -->
|
||||||
</beans>
|
</beans>
|
||||||
```
|
```
|
||||||
|
|
||||||
  Spring3支持几种不同的用于自动装配的注解:
|
  Spring3支持几种不同的用于自动装配的注解:
|
||||||
|
|
||||||
- Spring自带的@Autowired注解
|
- Spring自带的@Autowired注解
|
||||||
- JSR-330的@Inject注解
|
- JSR-330的@Inject注解
|
||||||
- JSR-250的@Resource注解
|
- JSR-250的@Resource注解
|
||||||
|
|
||||||
###1、使用@Autowired
|
###1、使用@Autowired
|
||||||
|
|
||||||
  @Autowired用于对被注解对象启动ByType的自动装配,可用于以下对象:
|
  @Autowired用于对被注解对象启动ByType的自动装配,可用于以下对象:
|
||||||
|
|
||||||
- 类属性,即使私有属性也能注入
|
- 类属性,即使私有属性也能注入
|
||||||
- set方法
|
- set方法
|
||||||
- 构造器
|
- 构造器
|
||||||
- 任意需要装配Bean的方法
|
- 任意需要装配Bean的方法
|
||||||
|
|
||||||
在使用@Autowired时有两种情况会出错:没有匹配的Bean和存在多个匹配的Bean,但是都有对应的解决方法。
|
在使用@Autowired时有两种情况会出错:没有匹配的Bean和存在多个匹配的Bean,但是都有对应的解决方法。
|
||||||
|
|
||||||
- 当没有匹配Bean时,自动装配会抛出NoSuchBeanDefinitionException,如果不想抛出可使用required属性,设置为false来配置可选的自动装配,即装配失败就不进行装配,不会报错。
|
- 当没有匹配Bean时,自动装配会抛出NoSuchBeanDefinitionException,如果不想抛出可使用required属性,设置为false来配置可选的自动装配,即装配失败就不进行装配,不会报错。
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Autowired(required=false)
|
@Autowired(required=false)
|
||||||
```
|
```
|
||||||
|
|
||||||
当使用构造器配置时,只有一个构造器可以将required属性设置为true,其他都只能设置为false。此外,当使用注解标注多个构造器时,Spring会从所有满足装配条件的构造器中选择入参最多的那个。
|
当使用构造器配置时,只有一个构造器可以将required属性设置为true,其他都只能设置为false。此外,当使用注解标注多个构造器时,Spring会从所有满足装配条件的构造器中选择入参最多的那个。
|
||||||
|
|
||||||
- 当存在多个Bean满足装配条件时,Spring也会抛出NoSuchBeanDefinitionException错误,为了选择指定的Bean,我们可以使用@Qualifier注解进行筛选:
|
- 当存在多个Bean满足装配条件时,Spring也会抛出NoSuchBeanDefinitionException错误,为了选择指定的Bean,我们可以使用@Qualifier注解进行筛选:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("name1")//筛选名为name1的Bean
|
@Qualifier("name1")//筛选名为name1的Bean
|
||||||
private TestClass testClass;
|
private TestClass testClass;
|
||||||
```
|
```
|
||||||
|
|
||||||
除了通过Bean的ID来缩小选择范围,我们还可以通过直接在Bean上使用qualifier来缩小范围,限制Bean的类型,xml如下:
|
除了通过Bean的ID来缩小选择范围,我们还可以通过直接在Bean上使用qualifier来缩小范围,限制Bean的类型,xml如下:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<bean class="com.test.xxx">
|
<bean class="com.test.xxx">
|
||||||
<qualifier value="stringed"/>
|
<qualifier value="stringed"/>
|
||||||
</bean>
|
</bean>
|
||||||
```
|
```
|
||||||
|
|
||||||
注解如下:
|
注解如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Qualifier("stringed")
|
@Qualifier("stringed")
|
||||||
public class xxx{}
|
public class xxx{}
|
||||||
```
|
```
|
||||||
|
|
||||||
还可以创建**自定义限定器(Qualifier)**
|
还可以创建**自定义限定器(Qualifier)**
|
||||||
|
|
||||||
  创建自定义限定器只需要使用@Qualifier注解作为它的源注解即可,如下创建了一个Stringed限定器:
|
  创建自定义限定器只需要使用@Qualifier注解作为它的源注解即可,如下创建了一个Stringed限定器:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Target({ElementType.FIELD,ElementType.PARAMETER,ElementType.TYPE})
|
@Target({ElementType.FIELD,ElementType.PARAMETER,ElementType.TYPE})
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Qualifier
|
@Qualifier
|
||||||
public @interface Stringed{}
|
public @interface Stringed{}
|
||||||
```
|
```
|
||||||
|
|
||||||
然后使用它注解一个Bean:
|
然后使用它注解一个Bean:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Stringed
|
@Stringed
|
||||||
public class Guitar{}
|
public class Guitar{}
|
||||||
```
|
```
|
||||||
|
|
||||||
然后就可以进行限定了:
|
然后就可以进行限定了:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Autowired
|
@Autowired
|
||||||
@Stringed
|
@Stringed
|
||||||
private Guitar guitar;
|
private Guitar guitar;
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2、使用@Inject自动注入
|
### 2、使用@Inject自动注入
|
||||||
|
|
||||||
  为统一各种依赖注入框架的编程模型,JCP(Java Community Process)发布的Java依赖注入规范,被称为JSR-330,从Spring3开始,Spring已经开始兼容该依赖注入模型。
|
  为统一各种依赖注入框架的编程模型,JCP(Java Community Process)发布的Java依赖注入规范,被称为JSR-330,从Spring3开始,Spring已经开始兼容该依赖注入模型。
|
||||||
|
|
||||||
  和@Autowired一样,@Inject可以用来自动装配属性、方法和构造器。但是@Inject没有required属性,因此依赖关系必须存在,如不存在将抛出异常。
|
  和@Autowired一样,@Inject可以用来自动装配属性、方法和构造器。但是@Inject没有required属性,因此依赖关系必须存在,如不存在将抛出异常。
|
||||||
|
|
||||||
  JSR-330还提供另一种注入技巧,注入一个Provider。Provider接口可以实现Bean引用的延迟注入以及注入Bean的多个实例等功能。
|
  JSR-330还提供另一种注入技巧,注入一个Provider。Provider接口可以实现Bean引用的延迟注入以及注入Bean的多个实例等功能。
|
||||||
|
|
||||||
  例如我们有一个KnifeJuggler类需要注入一个或多个Knife实例,假设Knife Bean的作用域声明为prototype,下面的KnifeJuggler的构造器将获得多个Knife Bean:
|
  例如我们有一个KnifeJuggler类需要注入一个或多个Knife实例,假设Knife Bean的作用域声明为prototype,下面的KnifeJuggler的构造器将获得多个Knife Bean:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
private Set<Knife> knifes;
|
private Set<Knife> knifes;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public KnifeJuggler(Provider<Knife> knifeProvider){
|
public KnifeJuggler(Provider<Knife> knifeProvider){
|
||||||
knives = new HashSet<Knife>();
|
knives = new HashSet<Knife>();
|
||||||
for(int i=0;i<5;i++){
|
for(int i=0;i<5;i++){
|
||||||
knives.add(knifeProvider.get());
|
knives.add(knifeProvider.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
  相对于@Autowired所对应的@Qualifier,@Inject对应的是@Named注解。事实上JSR-330中也有@Qualifier注解,不过不建议直接使用,建议通过该注解来创建自定义的限定注解,和Spring的@Qualifier创建过程类似。
|
  相对于@Autowired所对应的@Qualifier,@Inject对应的是@Named注解。事实上JSR-330中也有@Qualifier注解,不过不建议直接使用,建议通过该注解来创建自定义的限定注解,和Spring的@Qualifier创建过程类似。
|
||||||
|
|
||||||
### 3、注解中使用表达式
|
### 3、注解中使用表达式
|
||||||
|
|
||||||
  Spring3中引入的`@Value`属性可用来装配String类型的值和基本类型的值。借助SpEL表达式,@Value不光可以装配硬编码值还可以在运行期动态计算表达式并装配,例如下面的:
|
  Spring3中引入的`@Value`属性可用来装配String类型的值和基本类型的值。借助SpEL表达式,@Value不光可以装配硬编码值还可以在运行期动态计算表达式并装配,例如下面的:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Value("#{systemProperties.name}")
|
@Value("#{systemProperties.name}")
|
||||||
private String name;
|
private String name;
|
||||||
```
|
```
|
||||||
|
|
||||||
## 三、自动检测Bean
|
## 三、自动检测Bean
|
||||||
|
|
||||||
  在Spring中使用上面说到的`<context:annotation-config>`,可以做到自动装配,但还是要在xml中申明Bean。Spring还有另一个元素`<context:component-scan>`,元素除了完成自动装配的功能,还允许Spring自动检测Bean和定义Bean ,用法如下:
|
  在Spring中使用上面说到的`<context:annotation-config>`,可以做到自动装配,但还是要在xml中申明Bean。Spring还有另一个元素`<context:component-scan>`,元素除了完成自动装配的功能,还允许Spring自动检测Bean和定义Bean ,用法如下:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<beans ...>
|
<beans ...>
|
||||||
<context:component-scan base-package="com.springtest">
|
<context:component-scan base-package="com.springtest">
|
||||||
</context:component-scan>
|
</context:component-scan>
|
||||||
</beans>
|
</beans>
|
||||||
```
|
```
|
||||||
|
|
||||||
开启后支持如下注解:
|
开启后支持如下注解:
|
||||||
|
|
||||||
| 注解 | 解释 |
|
| 注解 | 解释 |
|
||||||
| ----------- | ------------------------------------ |
|
| ----------- | ------------------------------------ |
|
||||||
| @Component | 通用的构造型注解,标识类为Spring组件 |
|
| @Component | 通用的构造型注解,标识类为Spring组件 |
|
||||||
| @Controller | 标识该类定义为Spring MVC controller |
|
| @Controller | 标识该类定义为Spring MVC controller |
|
||||||
| @Repository | 标识该类定义为数据仓库 |
|
| @Repository | 标识该类定义为数据仓库 |
|
||||||
| @Service | 标识该类定义为服务 |
|
| @Service | 标识该类定义为服务 |
|
||||||
|
|
||||||
  使用上述注解是Bean的ID默认为无限定类名。使用`@Component("name")`指定ID。
|
  使用上述注解是Bean的ID默认为无限定类名。使用`@Component("name")`指定ID。
|
||||||
|
|
||||||
### 1、过滤组建扫描
|
### 1、过滤组建扫描
|
||||||
|
|
||||||
  通过为<context:component-scan >配置<context:include-filter >和<context:exclude-filter >子元素,我们可以随意调整扫描行为。下面的配置自动注册所有的TestInterface实现类:
|
  通过为<context:component-scan >配置<context:include-filter >和<context:exclude-filter >子元素,我们可以随意调整扫描行为。下面的配置自动注册所有的TestInterface实现类:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<context:component-scan base-package="com.fxb.springtest">
|
<context:component-scan base-package="com.fxb.springtest">
|
||||||
<context:include-filter type="assignable"
|
<context:include-filter type="assignable"
|
||||||
expression="com.fxb.springTest.TestInterface"/>
|
expression="com.fxb.springTest.TestInterface"/>
|
||||||
</context:component-scan>
|
</context:component-scan>
|
||||||
```
|
```
|
||||||
|
|
||||||
其中的type和expression属性一起协作来定义组件扫描策略。type有以下值可选择:
|
其中的type和expression属性一起协作来定义组件扫描策略。type有以下值可选择:
|
||||||
|
|
||||||
| 过滤器类型 | 描述 |
|
| 过滤器类型 | 描述 |
|
||||||
| ---------- | ------------------------------------------------------------ |
|
| ---------- | ------------------------------------------------------------ |
|
||||||
| annotation | 过滤器扫描使用指定注解所标注的类。通过expression属性指定要扫描的注解 |
|
| annotation | 过滤器扫描使用指定注解所标注的类。通过expression属性指定要扫描的注解 |
|
||||||
| assignable | 过滤器扫描派生于expression属性所指定类型的那些类 |
|
| assignable | 过滤器扫描派生于expression属性所指定类型的那些类 |
|
||||||
| aspectj | 过滤器扫描于expression属性所指定的AspectJ表达式所匹配的那些类 |
|
| aspectj | 过滤器扫描于expression属性所指定的AspectJ表达式所匹配的那些类 |
|
||||||
| custom | 使用自定义的org.springframework.core.type.TypeFilter实现类,该类由expression属性指定 |
|
| custom | 使用自定义的org.springframework.core.type.TypeFilter实现类,该类由expression属性指定 |
|
||||||
| regex | 过滤器扫描类的名称与expression属性所指定的正则表达式所匹配的类 |
|
| regex | 过滤器扫描类的名称与expression属性所指定的正则表达式所匹配的类 |
|
||||||
|
|
||||||
  exclude-filter使用和include-filter类似,只是效果相反。
|
  exclude-filter使用和include-filter类似,只是效果相反。
|
||||||
|
|
||||||
## 四、使用Spring基于Java的配置
|
## 四、使用Spring基于Java的配置
|
||||||
|
|
||||||
  在Spring3.0中几乎可以不使用XML而使用纯粹的Java代码来配置Spring应用。
|
  在Spring3.0中几乎可以不使用XML而使用纯粹的Java代码来配置Spring应用。
|
||||||
|
|
||||||
- 首先还是需要极少量的XML来启用Java配置,就是上面说到的`<context:component-scan>`,该标签还会自动加载使用`@Configuration`注解所标识的类
|
- 首先还是需要极少量的XML来启用Java配置,就是上面说到的`<context:component-scan>`,该标签还会自动加载使用`@Configuration`注解所标识的类
|
||||||
|
|
||||||
- @Configuration注解相当于XML配置中的\<beans>元素,这个注解将会告知Spring:这个类包含一个或多个Spring Bean的定义,这些定义是使用@Bean注解所标注的方法
|
- @Configuration注解相当于XML配置中的\<beans>元素,这个注解将会告知Spring:这个类包含一个或多个Spring Bean的定义,这些定义是使用@Bean注解所标注的方法
|
||||||
|
|
||||||
- 申明一个简单的Bean代码如下:
|
- 申明一个简单的Bean代码如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Configuration
|
@Configuration
|
||||||
public class TestConfig{
|
public class TestConfig{
|
||||||
@Bean
|
@Bean
|
||||||
public Animal duck(){
|
public Animal duck(){
|
||||||
return new Ducker();
|
return new Ducker();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@Bean告知Spring这个方法将返回一个对象,该对象应该被注册为Spring应用上下文中的一个Bean,方法名作为该Bean的ID 。想要使用另一个Bean的引用也很简单,如下:
|
@Bean告知Spring这个方法将返回一个对象,该对象应该被注册为Spring应用上下文中的一个Bean,方法名作为该Bean的ID 。想要使用另一个Bean的引用也很简单,如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Bean
|
@Bean
|
||||||
public Food duckFood(){
|
public Food duckFood(){
|
||||||
return new DuckFood();
|
return new DuckFood();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean //通过方法名引用一个Bean,并不会创建一个新的实例
|
@Bean //通过方法名引用一个Bean,并不会创建一个新的实例
|
||||||
public Animal duck(){
|
public Animal duck(){
|
||||||
return new Ducker(DuckFood());
|
return new Ducker(DuckFood());
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 五、小结
|
## 五、小结
|
||||||
|
|
||||||
  终于写完了spring 的最小化配置,对spring的各种注解也有了一些了解,再不是之前看到注解一脸莫名其妙了,虽然现在Springboot已经帮我们做了零XML配置,但觉得还是有必要了解下XML配置实现,这样对Java的配置实现理解也会更加深刻。
|
  终于写完了spring 的最小化配置,对spring的各种注解也有了一些了解,再不是之前看到注解一脸莫名其妙了,虽然现在Springboot已经帮我们做了零XML配置,但觉得还是有必要了解下XML配置实现,这样对Java的配置实现理解也会更加深刻。
|
@ -1,239 +1,239 @@
|
|||||||
## 一.面向切面编程
|
## 一.面向切面编程
|
||||||
|
|
||||||
  Spring的基础是IOC和AOP,前面两节对IOC和DI做了简单总结,这里再对AOP进行一个学习总结,Spring基础就算有一个初步了解了。
|
  Spring的基础是IOC和AOP,前面两节对IOC和DI做了简单总结,这里再对AOP进行一个学习总结,Spring基础就算有一个初步了解了。
|
||||||
|
|
||||||
  在软件开发中,我们可能需要一些跟业务无关但是又必须做的东西,比如日志,事务等,这些分布于应用中多处的功能被称为横切关注点,通常横切关注点从概念上是与应用的业务逻辑相分离的。如何将这些横切关注点与业务逻辑在代码层面进行分离,是面向切面编程(**AOP**)所要解决的。
|
  在软件开发中,我们可能需要一些跟业务无关但是又必须做的东西,比如日志,事务等,这些分布于应用中多处的功能被称为横切关注点,通常横切关注点从概念上是与应用的业务逻辑相分离的。如何将这些横切关注点与业务逻辑在代码层面进行分离,是面向切面编程(**AOP**)所要解决的。
|
||||||
|
|
||||||
横切关注点可以被描述为影响应用多处的功能,切面能够帮助我们模块化横切关注点。下图直观呈现了横切关注点的概念:
|
横切关注点可以被描述为影响应用多处的功能,切面能够帮助我们模块化横切关注点。下图直观呈现了横切关注点的概念:
|
||||||
|
|
||||||
![横切关注点](./3.面向切面的Spring/切面示例.png)
|
![横切关注点](./picFolder/切面示例.png)
|
||||||
|
|
||||||
途中CourseService,StudentService,MiscService都需要类似安全、事务这样的辅助功能,这些辅助功能就被称为横切关注点。
|
途中CourseService,StudentService,MiscService都需要类似安全、事务这样的辅助功能,这些辅助功能就被称为横切关注点。
|
||||||
|
|
||||||
  **继承**和**委托**是最常见的实现重用通用功能的面向对象技术。但是如果在整个程序中使用相同的基类继承往往会导致一个脆弱的对象体系;而使用委托可能需要对委托对象进行复杂的调用。
|
  **继承**和**委托**是最常见的实现重用通用功能的面向对象技术。但是如果在整个程序中使用相同的基类继承往往会导致一个脆弱的对象体系;而使用委托可能需要对委托对象进行复杂的调用。
|
||||||
|
|
||||||
切面提供了取代继承和委托的另一种选择,而且更加清晰简洁。在面向切面编程时,我们任然在一个地方定义通用功能,但是我们可以通过声明的方式定义这个功能以何种方式在何处应用,而无需修改受影响的类,受影响类完全感受不到切面的存在。
|
切面提供了取代继承和委托的另一种选择,而且更加清晰简洁。在面向切面编程时,我们任然在一个地方定义通用功能,但是我们可以通过声明的方式定义这个功能以何种方式在何处应用,而无需修改受影响的类,受影响类完全感受不到切面的存在。
|
||||||
|
|
||||||
## 二.AOP常用术语
|
## 二.AOP常用术语
|
||||||
|
|
||||||
  下面是AOP中常用的名词。
|
  下面是AOP中常用的名词。
|
||||||
|
|
||||||
### 1. 通知(Advice)
|
### 1. 通知(Advice)
|
||||||
|
|
||||||
  通知定义了切面是什么以及何时使用。出了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。Sping切面可以应用以下5种类型的通知。
|
  通知定义了切面是什么以及何时使用。出了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。Sping切面可以应用以下5种类型的通知。
|
||||||
|
|
||||||
- **Before** 在方法被调用之前调用通知
|
- **Before** 在方法被调用之前调用通知
|
||||||
- **After** 在方法完成之后调用通知,无论方法执行是否成功
|
- **After** 在方法完成之后调用通知,无论方法执行是否成功
|
||||||
- **After-returning** 在方法成功执行后调用通知
|
- **After-returning** 在方法成功执行后调用通知
|
||||||
- **After-throwing** 在方法抛出异常后调用通知
|
- **After-throwing** 在方法抛出异常后调用通知
|
||||||
- **Around** 通知包裹了被通知的方法,在被通知的方法调用前和调用后执行
|
- **Around** 通知包裹了被通知的方法,在被通知的方法调用前和调用后执行
|
||||||
|
|
||||||
###2.连接点(Joinpoint)
|
###2.连接点(Joinpoint)
|
||||||
|
|
||||||
  应用可能有很多个时机应用通知,这些时机被称为连接点。连接点是应用在执行过程中能够插入切面的一个点,这个点可以是调用方法时、抛出异常时、甚至是修改字段时。切面代码可以利用这些切入到应用的正常流程中,并添加新的行为。
|
  应用可能有很多个时机应用通知,这些时机被称为连接点。连接点是应用在执行过程中能够插入切面的一个点,这个点可以是调用方法时、抛出异常时、甚至是修改字段时。切面代码可以利用这些切入到应用的正常流程中,并添加新的行为。
|
||||||
|
|
||||||
### 3.切点(Pointcut)
|
### 3.切点(Pointcut)
|
||||||
|
|
||||||
  切点定义了通知所要织入的一个或多个连接点。如果说通知定义了切面的“**什么**”和“**何时**”,那么切点就定义了“**何处**”。通常使用明确的类和方法名称来指定切点,或者利用正则表达式定义匹配的类和方法来指定这些切点。有些AOP框架允许我们创建动态的切点,可以更具运行时的策略来决定是否应用通知。
|
  切点定义了通知所要织入的一个或多个连接点。如果说通知定义了切面的“**什么**”和“**何时**”,那么切点就定义了“**何处**”。通常使用明确的类和方法名称来指定切点,或者利用正则表达式定义匹配的类和方法来指定这些切点。有些AOP框架允许我们创建动态的切点,可以更具运行时的策略来决定是否应用通知。
|
||||||
|
|
||||||
### 4.切面(Aspect)
|
### 4.切面(Aspect)
|
||||||
|
|
||||||
  切面是通知和切点的结合。通知和切点定义了关于切面的全部内容,**是什么**,在**何时**、**何处**完成其功能。
|
  切面是通知和切点的结合。通知和切点定义了关于切面的全部内容,**是什么**,在**何时**、**何处**完成其功能。
|
||||||
|
|
||||||
### 5.引入
|
### 5.引入
|
||||||
|
|
||||||
  引入允许我们想现有的类添加新方法或属性。即在无需修改现有类的情况下让它们具有新的行为和状态。
|
  引入允许我们想现有的类添加新方法或属性。即在无需修改现有类的情况下让它们具有新的行为和状态。
|
||||||
|
|
||||||
### 6.织入
|
### 6.织入
|
||||||
|
|
||||||
  织入是将切面应用到目标对象来创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里有多个点可以进行织入。
|
  织入是将切面应用到目标对象来创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里有多个点可以进行织入。
|
||||||
|
|
||||||
- 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译期,比如AspectJ的织入编译期
|
- 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译期,比如AspectJ的织入编译期
|
||||||
- 类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的加载器,它可以在目标类被引入应用之前增强该目标类的字节码,例如AspectJ5的**LTW**(load-time weaving)
|
- 类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的加载器,它可以在目标类被引入应用之前增强该目标类的字节码,例如AspectJ5的**LTW**(load-time weaving)
|
||||||
- 运行期:切面在应用运行的某个时刻被织入。一般情况下AOP容器会为目标对象动态创建一个代理对象
|
- 运行期:切面在应用运行的某个时刻被织入。一般情况下AOP容器会为目标对象动态创建一个代理对象
|
||||||
|
|
||||||
##三.Spring AOP
|
##三.Spring AOP
|
||||||
|
|
||||||
  Spring在运行期通知对象,通过在代理类中包裹切面,Spring在运行期将切面织入到Spring管理的Bean中。代理类封装了目标类,并拦截被通知的方法的调用,再将调用转发给真正的目标Bean。由于Spring是基于动态代理,所有Spring只支持方法连接点,如果需要方法拦截之外的连接点拦截,我们可以利用Aspect来协助SpringAOP。
|
  Spring在运行期通知对象,通过在代理类中包裹切面,Spring在运行期将切面织入到Spring管理的Bean中。代理类封装了目标类,并拦截被通知的方法的调用,再将调用转发给真正的目标Bean。由于Spring是基于动态代理,所有Spring只支持方法连接点,如果需要方法拦截之外的连接点拦截,我们可以利用Aspect来协助SpringAOP。
|
||||||
|
|
||||||
  Spring在运行期通知对象,通过在代理类中包裹切面,Spring在运行期将切面织入到Spring管理的Bean中。代理类封装了目标类,并拦截被通知的方法的调用,再将调用转发给真正的目标Bean。由于Spring是基于动态代理,所有Spring只支持方法连接点,如果需要方法拦截之外的连接点拦截,我们可以利用Aspect来协助SpringAOP。
|
  Spring在运行期通知对象,通过在代理类中包裹切面,Spring在运行期将切面织入到Spring管理的Bean中。代理类封装了目标类,并拦截被通知的方法的调用,再将调用转发给真正的目标Bean。由于Spring是基于动态代理,所有Spring只支持方法连接点,如果需要方法拦截之外的连接点拦截,我们可以利用Aspect来协助SpringAOP。
|
||||||
|
|
||||||
### 1、定义切点
|
### 1、定义切点
|
||||||
|
|
||||||
  在SpringAOP中,需要使用AspectJ的切点表达式语言来定义切点。Spring只支持AspectJ的部分切点指示器,如下表所示:
|
  在SpringAOP中,需要使用AspectJ的切点表达式语言来定义切点。Spring只支持AspectJ的部分切点指示器,如下表所示:
|
||||||
|
|
||||||
| AspectJ指示器 | 描述 |
|
| AspectJ指示器 | 描述 |
|
||||||
| ------------- | ------------------------------------------------------------ |
|
| ------------- | ------------------------------------------------------------ |
|
||||||
| arg() | 限制连接点匹配参数为指定类型的执行方法 |
|
| arg() | 限制连接点匹配参数为指定类型的执行方法 |
|
||||||
| @args() | 限制连接点匹配参数由指定注解标注的执行方法 |
|
| @args() | 限制连接点匹配参数由指定注解标注的执行方法 |
|
||||||
| execution() | 用于匹配是连接点的执行方法 |
|
| execution() | 用于匹配是连接点的执行方法 |
|
||||||
| this() | 限制连接点匹配AOP代理的Bean引用为指导类型的类 |
|
| this() | 限制连接点匹配AOP代理的Bean引用为指导类型的类 |
|
||||||
| target() | 限制连接点匹配目标对象为指定类型的类 |
|
| target() | 限制连接点匹配目标对象为指定类型的类 |
|
||||||
| @target() | 限制连接点匹配特定的执行对象,这些对象对应的类要具备指定类型的注解 |
|
| @target() | 限制连接点匹配特定的执行对象,这些对象对应的类要具备指定类型的注解 |
|
||||||
| within() | 限制连接点匹配指定的类型 |
|
| within() | 限制连接点匹配指定的类型 |
|
||||||
| @within() | 限制连接点匹配指定注解所标注的类型(当使用SpringAOP时,方法定义在由指定的注解所标注的类里) |
|
| @within() | 限制连接点匹配指定注解所标注的类型(当使用SpringAOP时,方法定义在由指定的注解所标注的类里) |
|
||||||
| @annotation | 限制匹配带有指定注解连接点 |
|
| @annotation | 限制匹配带有指定注解连接点 |
|
||||||
| bean() | 使用Bean ID或Bean名称作为参数来限制切点只匹配特定的Bean |
|
| bean() | 使用Bean ID或Bean名称作为参数来限制切点只匹配特定的Bean |
|
||||||
|
|
||||||
 其中只有execution指示器是唯一的执行匹配,其他都是限制匹配。因此execution指示器是
|
 其中只有execution指示器是唯一的执行匹配,其他都是限制匹配。因此execution指示器是
|
||||||
|
|
||||||
其中只有execution指示器是唯一的执行匹配,其他都是限制匹配。因此execution指示器是我们在编写切点定义时最主要使用的指示器。
|
其中只有execution指示器是唯一的执行匹配,其他都是限制匹配。因此execution指示器是我们在编写切点定义时最主要使用的指示器。
|
||||||
|
|
||||||
### 2、编写切点
|
### 2、编写切点
|
||||||
|
|
||||||
  假设我们要使用execution()指示器选择Hello类的sayHello()方法,表达式如下:
|
  假设我们要使用execution()指示器选择Hello类的sayHello()方法,表达式如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
execution(* com.test.Hello.sayHello(..))
|
execution(* com.test.Hello.sayHello(..))
|
||||||
```
|
```
|
||||||
|
|
||||||
方法表达式以*** **号开始,说明不管方法返回值的类型。然后指定全限定类名和方法名。对于方法参数列表,我们使用(**)标识切点选择任意的sayHello()方法,无论方法入参是什么。
|
方法表达式以*** **号开始,说明不管方法返回值的类型。然后指定全限定类名和方法名。对于方法参数列表,我们使用(**)标识切点选择任意的sayHello()方法,无论方法入参是什么。
|
||||||
|
|
||||||
  同时我们可以使用&&(and),||(or),!(not)来连接指示器,如下所示:
|
  同时我们可以使用&&(and),||(or),!(not)来连接指示器,如下所示:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
execution(* com.test.Hello.sayHello(..)) and !bean(xiaobu)
|
execution(* com.test.Hello.sayHello(..)) and !bean(xiaobu)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3、申明切面
|
### 3、申明切面
|
||||||
|
|
||||||
  在经典Spring AOP中使用ProxyFactoryBean非常复杂,因此提供了申明式切面的选择,在Spring的AOP配置命名空间中有如下配置元素:
|
  在经典Spring AOP中使用ProxyFactoryBean非常复杂,因此提供了申明式切面的选择,在Spring的AOP配置命名空间中有如下配置元素:
|
||||||
|
|
||||||
| AOP配置元素 | 描述 |
|
| AOP配置元素 | 描述 |
|
||||||
| ------------------------------ | ----------------------------------------------------------- |
|
| ------------------------------ | ----------------------------------------------------------- |
|
||||||
| <aop:advisor > | 定义AOP通知器 |
|
| <aop:advisor > | 定义AOP通知器 |
|
||||||
| <aop:after > | 定义AOP后置通知(无论被通知方法是否执行成功) |
|
| <aop:after > | 定义AOP后置通知(无论被通知方法是否执行成功) |
|
||||||
| <aop:after-returning > | 定义AOP after-returning通知 |
|
| <aop:after-returning > | 定义AOP after-returning通知 |
|
||||||
| <aop:after-throwing > | 定义after-throwing |
|
| <aop:after-throwing > | 定义after-throwing |
|
||||||
| <aop:around > | 定义AOP环绕通知 |
|
| <aop:around > | 定义AOP环绕通知 |
|
||||||
| <aop:aspect > | 定义切面 |
|
| <aop:aspect > | 定义切面 |
|
||||||
| <aop:aspectj-autoproxy > | 启用@AspectJ注解驱动的切面 |
|
| <aop:aspectj-autoproxy > | 启用@AspectJ注解驱动的切面 |
|
||||||
| <aop:before > | 定义AOP前置通知 |
|
| <aop:before > | 定义AOP前置通知 |
|
||||||
| <aop:config > | 顶层的AOP配置元素。大多数的<aop:* >元素必须包含在其中 |
|
| <aop:config > | 顶层的AOP配置元素。大多数的<aop:* >元素必须包含在其中 |
|
||||||
| <aop:declare-parents > | 为被通知的对象引入额外的接口,并透明的实现 |
|
| <aop:declare-parents > | 为被通知的对象引入额外的接口,并透明的实现 |
|
||||||
| <aop:pointcut > | 定义切点 |
|
| <aop:pointcut > | 定义切点 |
|
||||||
|
|
||||||
### 4、实现
|
### 4、实现
|
||||||
|
|
||||||
假设有一个演员类`Actor`,演员类中有一个表演方法`perform()`,然后还有一个观众类`Audience`,这两个类都在包`com.example.springtest`下,Audience类主要方法如下:
|
假设有一个演员类`Actor`,演员类中有一个表演方法`perform()`,然后还有一个观众类`Audience`,这两个类都在包`com.example.springtest`下,Audience类主要方法如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class Audience{
|
public class Audience{
|
||||||
//搬凳子
|
//搬凳子
|
||||||
public void takeSeats(){}
|
public void takeSeats(){}
|
||||||
//欢呼
|
//欢呼
|
||||||
public void applaud(){}
|
public void applaud(){}
|
||||||
//计时,环绕通知需要一个ProceedingJoinPoint参数
|
//计时,环绕通知需要一个ProceedingJoinPoint参数
|
||||||
public void timing(ProceedingJoinPoint joinPoint){
|
public void timing(ProceedingJoinPoint joinPoint){
|
||||||
joinPoint.proceed();
|
joinPoint.proceed();
|
||||||
}
|
}
|
||||||
//演砸了
|
//演砸了
|
||||||
public void demandRefund(){}
|
public void demandRefund(){}
|
||||||
//测试带参数
|
//测试带参数
|
||||||
public void dealString(String word){}
|
public void dealString(String word){}
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### a、xml配置实现
|
#### a、xml配置实现
|
||||||
|
|
||||||
  首先将Audience配置到springIOC中:
|
  首先将Audience配置到springIOC中:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<bean id="audience" class="com.example.springtest.Audience"/>
|
<bean id="audience" class="com.example.springtest.Audience"/>
|
||||||
```
|
```
|
||||||
|
|
||||||
然后申明通知:
|
然后申明通知:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<aop:config>
|
<aop:config>
|
||||||
<aop:aspect ref="audience">
|
<aop:aspect ref="audience">
|
||||||
<!-- 申明切点 -->
|
<!-- 申明切点 -->
|
||||||
<aop:pointcut id="performance" expression="execution(* com.example.springtest.Performer.perform(..))"/>
|
<aop:pointcut id="performance" expression="execution(* com.example.springtest.Performer.perform(..))"/>
|
||||||
<!-- 声明传递参数切点 -->
|
<!-- 声明传递参数切点 -->
|
||||||
<aop:pointcut id="performanceStr" expression="execution(* com.example.springtest.Performer.performArg(String) and args(word))"/>
|
<aop:pointcut id="performanceStr" expression="execution(* com.example.springtest.Performer.performArg(String) and args(word))"/>
|
||||||
<!-- 前置通知 -->
|
<!-- 前置通知 -->
|
||||||
<aop:before pointcut-ref="performance" method="takeSeats"/>
|
<aop:before pointcut-ref="performance" method="takeSeats"/>
|
||||||
<!-- 执行成功通知 -->
|
<!-- 执行成功通知 -->
|
||||||
<aop:after-returning pointcout-ref="performance" method="applaud"/>
|
<aop:after-returning pointcout-ref="performance" method="applaud"/>
|
||||||
<!-- 执行异常通知 -->
|
<!-- 执行异常通知 -->
|
||||||
<aop:after-throwing pointcut-ref="performance" method="demandRefund"/>
|
<aop:after-throwing pointcut-ref="performance" method="demandRefund"/>
|
||||||
<!-- 环绕通知 -->
|
<!-- 环绕通知 -->
|
||||||
<aop:around pointcut-ref="performance" method="timing"/>
|
<aop:around pointcut-ref="performance" method="timing"/>
|
||||||
<!-- 传递参数 -->
|
<!-- 传递参数 -->
|
||||||
<aop:before pointcut-ref="performanceStr" arg-names="word" method="dealString"/>
|
<aop:before pointcut-ref="performanceStr" arg-names="word" method="dealString"/>
|
||||||
</aop:aspect>
|
</aop:aspect>
|
||||||
</aop:config>
|
</aop:config>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### b、注解实现
|
#### b、注解实现
|
||||||
|
|
||||||
直接在Audience类上加注解(Aspect注解并不能被spring自动发现并注册,要么写到xml中,要么使用@Aspectj注解或者加一个@Component注解),如下所示:
|
直接在Audience类上加注解(Aspect注解并不能被spring自动发现并注册,要么写到xml中,要么使用@Aspectj注解或者加一个@Component注解),如下所示:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Aspect
|
@Aspect
|
||||||
public class Audience{
|
public class Audience{
|
||||||
//定义切点
|
//定义切点
|
||||||
@Pointcut(execution(* com.example.springtest.Performer.perform(..)))
|
@Pointcut(execution(* com.example.springtest.Performer.perform(..)))
|
||||||
public void perform(){}
|
public void perform(){}
|
||||||
|
|
||||||
//定义带参数切点
|
//定义带参数切点
|
||||||
@Pointcut(execution(* com.example.springtest.Performer.performArg(String) and args(word)))
|
@Pointcut(execution(* com.example.springtest.Performer.performArg(String) and args(word)))
|
||||||
public void performStr(String word){}
|
public void performStr(String word){}
|
||||||
|
|
||||||
//搬凳子
|
//搬凳子
|
||||||
@Before("perform()")
|
@Before("perform()")
|
||||||
public void takeSeats(){}
|
public void takeSeats(){}
|
||||||
|
|
||||||
//欢呼
|
//欢呼
|
||||||
@AfterReturning("perform()")
|
@AfterReturning("perform()")
|
||||||
public void applaud(){}
|
public void applaud(){}
|
||||||
|
|
||||||
//计时,环绕通知需要一个ProceedingJoinPoint参数
|
//计时,环绕通知需要一个ProceedingJoinPoint参数
|
||||||
@Around("perform()")
|
@Around("perform()")
|
||||||
public void timing(ProceedingJoinPoint joinPoint){
|
public void timing(ProceedingJoinPoint joinPoint){
|
||||||
joinPoint.proceed();
|
joinPoint.proceed();
|
||||||
}
|
}
|
||||||
|
|
||||||
//演砸了
|
//演砸了
|
||||||
@AfterThrowing("perform()")
|
@AfterThrowing("perform()")
|
||||||
public void demandRefund(){}
|
public void demandRefund(){}
|
||||||
|
|
||||||
//带参数
|
//带参数
|
||||||
@Before("performStr(word)")
|
@Before("performStr(word)")
|
||||||
public void dealString(String word){}
|
public void dealString(String word){}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### c、通过切面引入新功能
|
#### c、通过切面引入新功能
|
||||||
|
|
||||||
  既然可以用AOP为对象拥有的方法添加新功能,那为什么不能为对象增加新的方法呢?利用被称为**引入**的AOP概念,切面可以为Spring Bean添加新的方法,示例图如下:
|
  既然可以用AOP为对象拥有的方法添加新功能,那为什么不能为对象增加新的方法呢?利用被称为**引入**的AOP概念,切面可以为Spring Bean添加新的方法,示例图如下:
|
||||||
|
|
||||||
![引入](.\3.面向切面的Spring\引入新功能.png)
|
![引入](./picFolder/引入新功能.png)
|
||||||
|
|
||||||
当引入接口的方法被调用时,代理将此调用委托给实现了新接口的某个其他对象。实际上,Bean的实现被拆分到了多个类。
|
当引入接口的方法被调用时,代理将此调用委托给实现了新接口的某个其他对象。实际上,Bean的实现被拆分到了多个类。
|
||||||
|
|
||||||
- xml引入需要使用<aop:declare-parents >元素:
|
- xml引入需要使用<aop:declare-parents >元素:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<aop:aspect>
|
<aop:aspect>
|
||||||
<aop:declare-parents types-matching="com.fxb.springtest.Performer+"
|
<aop:declare-parents types-matching="com.fxb.springtest.Performer+"
|
||||||
implement-interface="com.fxb.springtest.AddTestInterface"
|
implement-interface="com.fxb.springtest.AddTestInterface"
|
||||||
default-impl="com.fxb.springtest.AddTestImpl"/>
|
default-impl="com.fxb.springtest.AddTestImpl"/>
|
||||||
</aop:aspect>
|
</aop:aspect>
|
||||||
```
|
```
|
||||||
|
|
||||||
顾名思义\<declare-parents>声明了此切面所通知的Bean在它的对象层次结构中有了新的父类型。其中types-matching指定增强的类;implement-interface指定实现新方法的接口;default-imple指定实现了implement-interface接口的实现类,也可以用delegate-ref来指定一个Bean的引用。
|
顾名思义\<declare-parents>声明了此切面所通知的Bean在它的对象层次结构中有了新的父类型。其中types-matching指定增强的类;implement-interface指定实现新方法的接口;default-imple指定实现了implement-interface接口的实现类,也可以用delegate-ref来指定一个Bean的引用。
|
||||||
|
|
||||||
- 注解引入,通过`@DeclareParents`注解
|
- 注解引入,通过`@DeclareParents`注解
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
@DeclareParents(value="com.fxb.springtest.Performer+",
|
@DeclareParents(value="com.fxb.springtest.Performer+",
|
||||||
defaultImpl=AddTestImpl.class)
|
defaultImpl=AddTestImpl.class)
|
||||||
public static AddTestInterface addTestInterface;
|
public static AddTestInterface addTestInterface;
|
||||||
```
|
```
|
||||||
|
|
||||||
同xml实现一样,注解也由三部分组成:1、value属性相当于tpes-matching属性,标识被增强的类;2、defaultImpl等同于default-imple,指定接口的实现类;3、有@DeclareParents注解所标注的static属性指定了将被引入的接口。
|
同xml实现一样,注解也由三部分组成:1、value属性相当于tpes-matching属性,标识被增强的类;2、defaultImpl等同于default-imple,指定接口的实现类;3、有@DeclareParents注解所标注的static属性指定了将被引入的接口。
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
@ -1,48 +0,0 @@
|
|||||||
  前面的博客有说到spring boot搭建见另一篇博文,其实那篇博文还没写,现在来填个坑。我们使用spring initializr来构建,idea和eclipse都支持这种方式,构建过程类似,这里以idea为例,详细记录构建过程。
|
|
||||||
|
|
||||||
###1.选择spring initializr
|
|
||||||
|
|
||||||
![1532967570728](D:\笔记\markdown\springboot系列\springboot搭建.assets\1532967570728.png)
|
|
||||||
|
|
||||||
next
|
|
||||||
|
|
||||||
#### 2.设置参数
|
|
||||||
|
|
||||||
![1532967772110](D:\笔记\markdown\springboot系列\springboot搭建.assets\1532967772110.png)
|
|
||||||
|
|
||||||
next
|
|
||||||
|
|
||||||
#### 3.选择依赖
|
|
||||||
|
|
||||||
  在这里选择spring boot版本和web依赖(忽略sql的依赖,如有需要[点击这里](f),单独将mybatis的整合),后面也可手动编辑pom文件修改增加删除依赖
|
|
||||||
|
|
||||||
![1532967938985](D:\笔记\markdown\springboot系列\springboot搭建.assets\1532967938985.png)
|
|
||||||
|
|
||||||
这里我们选择web搭建一个简单的REST风格demo。然后next。
|
|
||||||
|
|
||||||
####4.设置项目存放地址
|
|
||||||
|
|
||||||
![1532968024509](D:\笔记\markdown\springboot系列\springboot搭建.assets\1532968024509.png)
|
|
||||||
|
|
||||||
这样就成功构建了一个springboot项目。
|
|
||||||
|
|
||||||
#### 5.测试
|
|
||||||
|
|
||||||
  现在新建一个controller包,包下新建一个HelloController,创建之后项目目录结构如下:
|
|
||||||
|
|
||||||
![1532969025023](D:\笔记\markdown\springboot系列\springboot搭建.assets\1532969025023.png)
|
|
||||||
|
|
||||||
HelloController代码如下:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/home")
|
|
||||||
public class HelloController{
|
|
||||||
@GetMapping("/hello")
|
|
||||||
public String sayHello(){
|
|
||||||
return "hello";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
然后运行项目,访问localhost:8080/home/hello即可看到hello字符串。
|
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
###1.选择spring initializr
|
###1.选择spring initializr
|
||||||
|
|
||||||
![1532967570728](.\springboot搭建.assets\1532967570728.png)
|
![1532967570728](./picFolder/1532967570728.png)
|
||||||
|
|
||||||
next
|
next
|
||||||
|
|
||||||
#### 2.设置参数
|
#### 2.设置参数
|
||||||
|
|
||||||
![1532967772110](D:\笔记\markdown\springboot系列\springboot搭建.assets\1532967772110.png)
|
![1532967772110](./picFolder/1532967772110.png)
|
||||||
|
|
||||||
next
|
next
|
||||||
|
|
||||||
@ -16,13 +16,13 @@ next
|
|||||||
|
|
||||||
  在这里选择spring boot版本和web依赖(忽略sql的依赖,如有需要[点击这里](f),单独将mybatis的整合),后面也可手动编辑pom文件修改增加删除依赖
|
  在这里选择spring boot版本和web依赖(忽略sql的依赖,如有需要[点击这里](f),单独将mybatis的整合),后面也可手动编辑pom文件修改增加删除依赖
|
||||||
|
|
||||||
![1532967938985](D:\笔记\markdown\springboot系列\springboot搭建.assets\1532967938985.png)
|
![1532967938985](./picFolder/1532967938985.png)
|
||||||
|
|
||||||
这里我们选择web搭建一个简单的REST风格demo。然后next。
|
这里我们选择web搭建一个简单的REST风格demo。然后next。
|
||||||
|
|
||||||
####4.设置项目存放地址
|
####4.设置项目存放地址
|
||||||
|
|
||||||
![1532968024509](D:\笔记\markdown\springboot系列\springboot搭建.assets\1532968024509.png)
|
![1532968024509](./picFolder/1532968024509.png)
|
||||||
|
|
||||||
这样就成功构建了一个springboot项目。
|
这样就成功构建了一个springboot项目。
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ next
|
|||||||
|
|
||||||
  现在新建一个controller包,包下新建一个HelloController,创建之后项目目录结构如下:
|
  现在新建一个controller包,包下新建一个HelloController,创建之后项目目录结构如下:
|
||||||
|
|
||||||
![1532969025023](D:\笔记\markdown\springboot系列\springboot搭建.assets\1532969025023.png)
|
![1532969025023](./picFolder/1532969025023.png)
|
||||||
|
|
||||||
HelloController代码如下:
|
HelloController代码如下:
|
||||||
|
|
||||||
|
BIN
springboot系列/springsecurity/picFolder/pic1.png
Normal file
After Width: | Height: | Size: 81 KiB |
BIN
springboot系列/springsecurity/picFolder/pic2.png
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
springboot系列/数据库/picFolder/pic1.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
springboot系列/数据库/picFolder/pic2.png
Normal file
After Width: | Height: | Size: 87 KiB |
BIN
springboot系列/数据库/picFolder/pic3.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
springboot系列/数据库/picFolder/pic4.png
Normal file
After Width: | Height: | Size: 117 KiB |
BIN
springboot系列/消息队列/picFolder/pic1.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
springboot系列/消息队列/picFolder/pic2.png
Normal file
After Width: | Height: | Size: 82 KiB |
BIN
springboot系列/消息队列/picFolder/pic3.png
Normal file
After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
然后重启数据库,使用`show master status;`语句查看主库状态,如下所示:
|
然后重启数据库,使用`show master status;`语句查看主库状态,如下所示:
|
||||||
|
|
||||||
![主库状态](.\读写分离配置\pic1.png)
|
![主库状态](./picFolder/pic1.png)
|
||||||
|
|
||||||
- 从库配置
|
- 从库配置
|
||||||
|
|
||||||
@ -59,7 +59,7 @@
|
|||||||
|
|
||||||
接着运行`start slave;`开启备份,正常情况如下图所示:Slave_IO_Running和Slave_SQL_Running都为yes。
|
接着运行`start slave;`开启备份,正常情况如下图所示:Slave_IO_Running和Slave_SQL_Running都为yes。
|
||||||
|
|
||||||
![1536223020742](.\读写分离配置\pic2.png)
|
![1536223020742](./picFolder/pic2.png)
|
||||||
|
|
||||||
可以用这个步骤开启多个从库。
|
可以用这个步骤开启多个从库。
|
||||||
|
|
||||||
@ -306,6 +306,6 @@ public class ReadOnlyInterceptor implements Ordered {
|
|||||||
|
|
||||||
  编写好代码来试试结果如何,下面是运行截图:
|
  编写好代码来试试结果如何,下面是运行截图:
|
||||||
|
|
||||||
![1536312274474](.\读写分离配置\pic3.png)
|
![1536312274474](./picFolder/pic3.png)
|
||||||
|
|
||||||
  断断续续写了好几天终于是写完了,,,如果有帮助到你,,欢迎star哦,,这里是完整代码地址:[点击跳转](https://github.com/FleyX/demo-project/tree/master/dxfl)
|
  断断续续写了好几天终于是写完了,,,如果有帮助到你,,欢迎star哦,,这里是完整代码地址:[点击跳转](https://github.com/FleyX/demo-project/tree/master/dxfl)
|
17
其他各种/git crlf、lf自动转换引起的问题.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
  项目组最近加了一个新功能到代码中,使用flyway记录数据库版本变更,,,该工具会记录每次数据库结构的修改并生成sql文件存在指定目录上(当然必须用它来变更数据库,外部的变更它是无法感知的),然后每次启动时flyway会检查使用的数据库和当前项目代码中的sql变更版本是否一致,一致正常启动,不一致中如果是数据库落后将会更新数据库(这样能够保证代码在任何地方运行数据库都是一致的),否则就报错了。数据库中有一张表记录版本信息,如下图:
|
||||||
|
|
||||||
|
![版本记录](./picFolder/版本记录.PNG),同时本地代码中也有一个文件夹保存每次操作的sql语句,如下图:
|
||||||
|
|
||||||
|
![版本sql](./picFolder/版本sql.PNG)
|
||||||
|
|
||||||
|
通过对比checksum值来判断当前sql语句和生成数据库的执行语句是否一致,checksum值由CRC32计算后处理得出。
|
||||||
|
|
||||||
|
  然后问题就来了,组中的其他人搭建好flyway后,项目文件生成了两个sql文件,我用git拉下来后启动报错,checkupsum值对不上,,然后我又不懂这个flyway完全不知道咋回事,然后就根据报错的位置一点点找到checkup值生成的代码,发现是CRC32计算的,,(就这么搞了一两个小时才发现是文件不一致了),但是都是从git拉的怎么就我不一致呢???想到可能是文件换行符的问题,遂把那几个sql文件的文件换行符全换成了crlf(windows中的换行符),然后居然就能够运行。。。关于为啥都从git拉取的文件换行符会不一样原因是:他们都用的那个小乌龟的可视化,我用的命令行。可视化工具自动配置了文件换行符的自动转换(这是git的一个智能功能,上传时将文件换行符替换为lf,,拉取时再替换为crlf,,这样保证中心仓库使用UNIX风格的换行符,,本地能够根据运行环境使用相对应的换行符风格),但是命令行并没有配置。
|
||||||
|
|
||||||
|
  解决办法也很简单,开启git 的自动转换。
|
||||||
|
|
||||||
|
```
|
||||||
|
git config --global core.autocrlf true //开启换行符自动转换
|
||||||
|
git config --global core.safecrlf true //禁止混用换行符
|
||||||
|
```
|
||||||
|
|
177
其他各种/java导出EXCEL文件.md
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
## 一、背景
|
||||||
|
|
||||||
|
  最近在java上做了一个EXCEL的导出功能,写了一个通用类,在这里分享分享,该类支持多sheet,且无需手动进行复杂的类型转换,只需提供三个参数即可:
|
||||||
|
|
||||||
|
- `fileName`
|
||||||
|
|
||||||
|
excel文件名
|
||||||
|
|
||||||
|
- `HasMap<String,List<?>> data`
|
||||||
|
|
||||||
|
具体的数据,每个List代表一张表的数据,?表示可为任意的自定义对象
|
||||||
|
|
||||||
|
- `LinkedHashMap<String,String[][]> headers`
|
||||||
|
|
||||||
|
`Stirng`代表sheet名。每个`String[][] `代表一个sheet的定义,举个例子如下:
|
||||||
|
|
||||||
|
```java
|
||||||
|
String[][] header = {
|
||||||
|
{"field1","参数1"}
|
||||||
|
,{"field2","参数2"}
|
||||||
|
,{"field3","参数3"}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
其中的field1,field2,field3为对象中的属性名,参数1,参数2,参数3为列名,实际上这个指定了列的名称和这个列用到数据对象的哪个属性。
|
||||||
|
|
||||||
|
## 二、怎么用
|
||||||
|
|
||||||
|
  以一个例子来说明怎么用,假设有两个类A和B定义如下:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class A{
|
||||||
|
private String name;
|
||||||
|
private String address;
|
||||||
|
}
|
||||||
|
public class B{
|
||||||
|
private int id;
|
||||||
|
private double sum;
|
||||||
|
private String cat;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
现在我们通过查询数据库获得了A和B的两个列表:
|
||||||
|
|
||||||
|
```java
|
||||||
|
List<A> dataA = .....;
|
||||||
|
List<B> dataB = .....;
|
||||||
|
```
|
||||||
|
|
||||||
|
我们将这两个导出到excel中,首先需要定义sheet:
|
||||||
|
|
||||||
|
```java
|
||||||
|
String[][] sheetA = {
|
||||||
|
{"name","姓名"}
|
||||||
|
,{"address","住址"}
|
||||||
|
}
|
||||||
|
String[][] sheetB = {
|
||||||
|
{"id","ID"}
|
||||||
|
,{"sum","余额"}
|
||||||
|
,{"cat","猫的名字"}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
然后将数据汇总构造一个ExcelUtil:
|
||||||
|
|
||||||
|
```java
|
||||||
|
String fileName = "测试Excel";
|
||||||
|
HashMap<String,List<?>> data = new HashMap<>();
|
||||||
|
//ASheet为表名,后面headers里的key要跟这里一致
|
||||||
|
data.put("ASheet",dataA);
|
||||||
|
data.put("BSheet",dataB);
|
||||||
|
LinkedHashMap<String,String[][]> headers = new LinkedHashMap<>();
|
||||||
|
headers.put("ASheet",sheetA);
|
||||||
|
headers.put("BSheet",sheetB);
|
||||||
|
ExcelUtil excelUtil = new ExcelUtil(fileName,data,headers);
|
||||||
|
//获取表格对象
|
||||||
|
HSSFWorkbook workbook = excelUtil.createExcel();
|
||||||
|
//这里内置了一个写到response的方法(判断浏览器类型设置合适的参数),如果想写到文件也是类似的
|
||||||
|
workbook.writeToResponse(workbook,request,response);
|
||||||
|
```
|
||||||
|
|
||||||
|
当然通常数据是通过数据库查询的,这里为了演示方便没有从数据库查找。
|
||||||
|
|
||||||
|
## 三、实现原理
|
||||||
|
|
||||||
|
  这里简单说明下实现过程,从调用`createExcel()`这里开始
|
||||||
|
|
||||||
|
####1、遍历headers创建sheet
|
||||||
|
|
||||||
|
```java
|
||||||
|
public HSSFWorkbook createExcel() throws Exception {
|
||||||
|
try {
|
||||||
|
HSSFWorkbook workbook = new HSSFWorkbook();
|
||||||
|
//遍历headers创建表格
|
||||||
|
for (String key : headers.keySet()) {
|
||||||
|
this.createSheet(workbook, key, headers.get(key), this.data.get(key));
|
||||||
|
}
|
||||||
|
return workbook;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("创建表格失败:{}", e.getMessage());
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
将workbook,sheet名,表头数据,行数据传入crateSheet方法中创建sheet。
|
||||||
|
|
||||||
|
#### 2、创建表头
|
||||||
|
|
||||||
|
  表头也就是一个表格的第一行,通常用来对列进行说明
|
||||||
|
|
||||||
|
```java
|
||||||
|
HSSFSheet sheet = workbook.createSheet(sheetName);
|
||||||
|
// 列数
|
||||||
|
int cellNum = header.length;
|
||||||
|
// 单元行,单元格
|
||||||
|
HSSFRow row;
|
||||||
|
HSSFCell cell;
|
||||||
|
// 表头单元格样式
|
||||||
|
HSSFCellStyle columnTopStyle = this.getColumnTopStyle(workbook);
|
||||||
|
// 设置表头
|
||||||
|
row = sheet.createRow(0);
|
||||||
|
for (int i = 0; i < cellNum; i++) {
|
||||||
|
cell = row.createCell(i);
|
||||||
|
cell.setCellStyle(columnTopStyle);
|
||||||
|
String str = header[i][1];
|
||||||
|
cell.setCellValue(str);
|
||||||
|
// 设置列宽为表头的文字宽度+6个半角符号宽度
|
||||||
|
sheet.setColumnWidth(i, (str.getBytes("utf-8").length + 6) * 256);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3、插入行数据
|
||||||
|
|
||||||
|
  这里是最重要的部分,首先通过数据的类对象获取它的反射属性Field类,然后将属性名和Field做一个hash映射,避免循环查找,提高插入速度,接着通过一个switch语句,根据属性类别设值,主要代码如下:
|
||||||
|
|
||||||
|
```java
|
||||||
|
/**
|
||||||
|
* 设置单元格,根据fieldName获取对应的Field类,使用反射得到值
|
||||||
|
*
|
||||||
|
* @param cell 单元格实例
|
||||||
|
* @param obj 存有属性的对象实例
|
||||||
|
* @param fieldMap 属性名与Field的映射
|
||||||
|
* @param fieldName 属性名
|
||||||
|
*/
|
||||||
|
private void setCell(HSSFCell cell, Object obj, Map<String, Field> fieldMap, String fieldName) throws Exception {
|
||||||
|
//获取该属性的Field对象
|
||||||
|
Field field = fieldMap.get(fieldName);
|
||||||
|
//通过反射获取属性的值,由于不能确定该值的类型,用下面的判断语句进行合适的转型
|
||||||
|
Object value = field.get(obj);
|
||||||
|
if (value == null) {
|
||||||
|
cell.setCellValue("");
|
||||||
|
} else {
|
||||||
|
switch (field.getGenericType().getTypeName()) {
|
||||||
|
case "java.lang.String":
|
||||||
|
cell.setCellValue((String) value);
|
||||||
|
break;
|
||||||
|
case "java.lang.Integer":
|
||||||
|
case "int":
|
||||||
|
cell.setCellValue((int) value);
|
||||||
|
break;
|
||||||
|
case "java.lang.Double":
|
||||||
|
case "double":
|
||||||
|
cell.setCellValue((double) value);
|
||||||
|
break;
|
||||||
|
case "java.util.Date":
|
||||||
|
cell.setCellValue(this.dateFormat.format((Date) value));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
cell.setCellValue(obj.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
完整代码可以到github上查看下载,这里就不列出来了。
|
||||||
|
|
||||||
|
github地址:[点击跳转]()
|
BIN
其他各种/picFolder/版本sql.PNG
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
其他各种/picFolder/版本记录.PNG
Normal file
After Width: | Height: | Size: 8.8 KiB |
48
其他各种/正则表达式在密码匹配中的使用.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
##一、背景
|
||||||
|
|
||||||
|
  今天领导让我写几个正则表达式来对密码做强度验证,听到写正则表达式内心是这样的感觉(哈哈,三分钟搞定,今天又可以打鱼了)。需求如下:密码组成只能是**数字**,**字母**,**英文可见半角符号**,然后需要如下4个表达式:
|
||||||
|
|
||||||
|
- 长度6位及以上
|
||||||
|
- 长度6位及以上,包含数字,包含字母
|
||||||
|
- 长度6位及以上,包含数字,包含字母,包含半角符号
|
||||||
|
- 长度六位及以上,包含数字,包含大写字母,包含小写字母,包含半角符号
|
||||||
|
|
||||||
|
  看完需求我就有点懵了,包含数字或者字母我会写,但是同时存在还要在一个表达式中就有点懵了。
|
||||||
|
|
||||||
|
##二、解决方法
|
||||||
|
|
||||||
|
  以第三种为例,这个可以分解为如下需求:
|
||||||
|
|
||||||
|
- 存在数字
|
||||||
|
- 存在字母
|
||||||
|
- 存在半角符号
|
||||||
|
- 长度六位及以上
|
||||||
|
|
||||||
|
关键是如何同时满足前三个条件,在我有限的知识里并不知道怎么搞,然后只好求助于万能的百度了,最终在找了几个小时后发现如下几个关键词,来源[菜鸟教程](http://www.runoob.com/java/java-regular-expressions.html) :
|
||||||
|
|
||||||
|
- (?=*pattern*) :正向预测先行搜索
|
||||||
|
|
||||||
|
名字看着高大上,不明所以,看完示例大概明白什么意思,这个表达式匹配从这个表达式起始的字符串(我也不知道咋解释),就是假设这样一个表达式abc(?=[abc]) ,用它来匹配abc123字符串,(?=[abc])只会对作用于后面的123,这个显然是不匹配的后整个就不匹配了,然后关键来了名字里有**预测**两个字,这两个字表名了这个表达式的特性:不占用字符,匹配后如果匹配成功就继续匹配了好像从来不存在这个东西一样,匹配失败就立即返回失败了。利用这个特性我们就可以给正则加限制条件了。
|
||||||
|
|
||||||
|
- (?!*pattern*) :反向预测先行搜索
|
||||||
|
|
||||||
|
概念和上面一样,但是效果是相反的,abc(?![abc]),对于abc123是匹配成功的,对于abca匹配失败,如下所示:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
reg = /abc(?![abc])/;
|
||||||
|
reg.test("abc123")
|
||||||
|
//返回true
|
||||||
|
reg.test("abca")
|
||||||
|
//返回false
|
||||||
|
```
|
||||||
|
|
||||||
|
  有了上面的知识就能搞定需求啦。
|
||||||
|
|
||||||
|
## 三、结果
|
||||||
|
|
||||||
|
  对于存在字母我们可以用这样的表达式`(?=.\*?[a-zA-Z]+.\*?),来检查是否存在至少一个字母,最后对于需求3的表达式如下:(半角字符我用的ASCII码里的16进制表示的)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
^(?=.*?\d+.*?)(?=.*?[a-zA-Z]+.*?)(?=.*?[\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]+.*?)[\da-zA-Z\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]{6,}$
|
||||||
|
```
|
||||||
|
|
452
其他各种/毕设.md
Normal file
@ -0,0 +1,452 @@
|
|||||||
|
  毕业才刚刚两个多月而已,现在想想大学生活是那么的遥不可及,感觉已经过了好久好久,社会了两个月才明白学校的好啊。。。额,扯远了,自从毕业开始就想找个时间写下毕设的记录总结,结果找了好久好久到今天才开始动笔。
|
||||||
|
|
||||||
|
  我的毕业设计题目是:教学辅助系统的设计与实现,,是不是很俗。。。至于为啥是这个题目呢,完全是被导师坑了。。。。。
|
||||||
|
|
||||||
|
## 1、需求分析
|
||||||
|
|
||||||
|
  拿到这个题目想着这个可能被做了无数次了,就像着哪里能够做出点创新,,最后强行创新出了一个个性化组题(根据学生水平出题)和徽章激励(达到某个要求给予一个徽章)。最后就产生了如下需求,系统有学生端和管理端:
|
||||||
|
|
||||||
|
学生端:
|
||||||
|
|
||||||
|
- 个人资料设置
|
||||||
|
- 徽章激励机制
|
||||||
|
- 查看课程信息,下载课程资料
|
||||||
|
- 知识点检测及针对性训练
|
||||||
|
- 在线作业,考试
|
||||||
|
- 在线答疑,向老师或者学生提问
|
||||||
|
|
||||||
|
管理端:
|
||||||
|
|
||||||
|
- 课程管理,用户管理(需要管理员权限)
|
||||||
|
- 课程信息管理
|
||||||
|
- 课程公告管理
|
||||||
|
- 题库管理,支持单选,多选,填空,编程题,支持题目编组
|
||||||
|
- 发布作业,包括个性组题和手动组题
|
||||||
|
- 发布考试,包括随机出题和手动出题
|
||||||
|
- 自动判题,支持编程题判重
|
||||||
|
- 在线答疑,给学生解答
|
||||||
|
- 统计分析,包含测试统计和课程统计
|
||||||
|
|
||||||
|
洋洋洒洒需求列了一大堆,后面才发现是给自己挖坑,,答辩老师一看这类的题目就不感兴趣了,不论你做的咋样(况且我的演讲能力真的很一般),最后累死累活写了一大堆功能也没太高的分,,不过倒是让我的系统设计能力和代码能力有了不少的提高。
|
||||||
|
|
||||||
|
## 2、架构选择
|
||||||
|
|
||||||
|
  大三的时候了解到Node.js这个比较“奇葩"的异步语言,再加上在公司实习了三个月也是用的node开发,对node已经比较熟悉了,于是就用它做了后台,前端用最近比较火的vue.js做单页应用。当时还想着负载均衡啥的,就没有用传统的session,cookie机制,转而用jwt做的基于token的身份认证,同时后台接口也是类Restful风格的(因为纯正的Rest接口太难设计了)。
|
||||||
|
|
||||||
|
总的来说后台用了以下技术和框架:
|
||||||
|
|
||||||
|
  总的来说后台用了以下技术和框架:
|
||||||
|
|
||||||
|
- 语言:Node.js
|
||||||
|
- web框架:kOA
|
||||||
|
- 前后台传输协议:jwt
|
||||||
|
- 缓存:redis
|
||||||
|
- 数据库:mysql
|
||||||
|
- 编程题判题核心:[青岛大学OJ判题核心](https://github.com/QingdaoU/JudgeServer)
|
||||||
|
- 代码判重:[SIM](https://dickgrune.com/Programs/similarity_tester/)
|
||||||
|
|
||||||
|
前台技术如下:
|
||||||
|
|
||||||
|
- 框架:Vue.js
|
||||||
|
- UI框架:Element-UI
|
||||||
|
- 图表组件:G2
|
||||||
|
|
||||||
|
## 3、系统基础框架搭建
|
||||||
|
|
||||||
|
  本系统是前后端分离的,下面分别介绍前后端的实现基础。
|
||||||
|
|
||||||
|
### 1、后台
|
||||||
|
|
||||||
|
  一个web后台最重要的无非那么几个部分:路由;权限验证;数据持久化。
|
||||||
|
|
||||||
|
#### a、路由
|
||||||
|
|
||||||
|
KOA作为一个web框架其实它本身并没有提供路由功能,需要配合使用koa-router来实现路由,koa-router以类似下面这样的风格来进行路由:
|
||||||
|
|
||||||
|
  KOA作为一个web框架其实它本身并没有提供路由功能,需要配合使用koa-router来实现路由,koa-router以类似下面这样的风格来进行路由:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const app = require("koa");
|
||||||
|
const router = require("koa-router");
|
||||||
|
router.get("/hello",koa=>{
|
||||||
|
koa.response="hello";
|
||||||
|
});
|
||||||
|
app.use(router.routes())
|
||||||
|
```
|
||||||
|
|
||||||
|
显然这样在项目中是很不方便的,如果每个路由都要手动进行挂载,很难将每个文件中的路由都挂载到一个router中。因此在参考网上的实现后,我写了一个方法在启动时自动扫描某个文件夹下所有的路由文件并挂载到router中,代码如下:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const koaBody = require('koa-body');
|
||||||
|
const config = require('../config/config.js');
|
||||||
|
|
||||||
|
function addMapping(router, filePath) {
|
||||||
|
let mapping = require(filePath);
|
||||||
|
for (let url in mapping) {
|
||||||
|
if (url.startsWith('GET ')) {
|
||||||
|
let temp = url.substring(4);
|
||||||
|
router.get(temp, mapping[url]);
|
||||||
|
console.log(`----GET:${temp}`);
|
||||||
|
} else if (url.startsWith('POST ')) {
|
||||||
|
let temp = url.substring(5);
|
||||||
|
router.post(temp, mapping[url]);
|
||||||
|
console.log(`----POST:${temp}`);
|
||||||
|
} else if (url.startsWith('PUT ')) {
|
||||||
|
let temp = url.substring(4);
|
||||||
|
router.put(temp, mapping[url]);
|
||||||
|
console.log(`----PUT:${temp}`)
|
||||||
|
} else if (url.startsWith('DELETE ')) {
|
||||||
|
let temp = url.substring(7);
|
||||||
|
router.delete(temp, mapping[url]);
|
||||||
|
console.log(`----DELETE: ${temp}`);
|
||||||
|
} else {
|
||||||
|
console.log(`xxxxx无效路径:${url}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addControllers(router, filePath) {
|
||||||
|
let files = fs.readdirSync(filePath);
|
||||||
|
files.forEach(element => {
|
||||||
|
let temp = path.join(filePath, element);
|
||||||
|
let state = fs.statSync(temp);
|
||||||
|
if (state.isDirectory()) {
|
||||||
|
addControllers(router, temp);
|
||||||
|
} else {
|
||||||
|
if (!temp.endsWith('Helper.js')) {
|
||||||
|
console.log('\n--开始处理: ' + element + "路由");
|
||||||
|
addMapping(router, temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function engine(router, folder) {
|
||||||
|
addControllers(router, folder);
|
||||||
|
return router.routes();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = engine;
|
||||||
|
```
|
||||||
|
|
||||||
|
然后在index.js中use此方法:
|
||||||
|
|
||||||
|
```
|
||||||
|
const RouterMW = require("./middleware/controllerEngine.js");
|
||||||
|
app.use(RouterMW(router,path.join(config.rootPath, 'api')));
|
||||||
|
```
|
||||||
|
|
||||||
|
然后路由文件以下面的形式编写:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const knowledgePointDao = require('../dao/knowledgePointDao.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回某门课的全部知识点,按章节分类
|
||||||
|
*/
|
||||||
|
exports["GET /course/:c_id/knowledge_point"] = async (ctx, next) => {
|
||||||
|
let res = await knowledgePointDao.getPontsOrderBySection(ctx.params.c_id);
|
||||||
|
ctx.onSuccess(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
//返回某位学生知识点答题情况
|
||||||
|
exports["GET /user/:u_id/course/:c_id/knowledge_point/condition"]=async(ctx,next)=>{
|
||||||
|
let {u_id,c_id}=ctx.params;
|
||||||
|
let res = await knowledgePointDao.getStudentCondition(u_id,c_id);
|
||||||
|
ctx.onSuccess(res);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### b、权限验证
|
||||||
|
|
||||||
|
  权限管理是一个系统最重要的部分之一,目前主流的方式为**基于角色的权限管理**, 一个用户对应多个角色,每个角色对应多个权限(本系统中每个用户对应一个身份,每个身份对应多个角色)。我们的系统如何实现的呢?先从登录开始说起,本系统抛弃了传统的cookie,session模式,使用json web token(JWT)来做身份认证,用户登录后返回一个token给客户端,代码如下所示:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
//生成随机盐值
|
||||||
|
let str = StringHelper.getRandomString(0, 10);
|
||||||
|
//使用该盐值生成token
|
||||||
|
let token = jwt.sign({
|
||||||
|
u_id: userInfo.u_id,
|
||||||
|
isRememberMe
|
||||||
|
}, str, {
|
||||||
|
expiresIn: isRememberMe ? config.longTokenExpiration:config.shortTokenExpiration
|
||||||
|
});
|
||||||
|
//token-盐值存入redis,如想让该token过期,redis中清楚该token键值对即可
|
||||||
|
await RedisHelper.setString(token, str, 30 * 24 * 60 * 60);
|
||||||
|
res.code = 1;
|
||||||
|
res.info = '登录成功';
|
||||||
|
res.data = {
|
||||||
|
u_type: userInfo.u_type,
|
||||||
|
u_id: userInfo.u_id,
|
||||||
|
token
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
以后每次客户端请求都要在header中设置该token,然后每次服务端收到请求都先验证是否拥有权限,验证代码使用`router.use(auth)`,挂载到koa-router中,这样每次在进入具体的路由前都要先执行auth方法进行权限验证,主要验证代码逻辑如下:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
/**
|
||||||
|
* 1 验证成功
|
||||||
|
* 2 登录信息无效 401
|
||||||
|
* 3 已登录,无操作权限 403
|
||||||
|
* 4 token已过期
|
||||||
|
*/
|
||||||
|
let verify = async (ctx) => {
|
||||||
|
let token = ctx.headers.authorization;
|
||||||
|
if (typeof (token) != 'string') {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
let yan = await redisHelper.getString(token);
|
||||||
|
if (yan == null) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = jwt.verify(token, yan);
|
||||||
|
} catch (e) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if (data.exp * 1000 < Date.now()) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
//判断是否需要刷新token,如需要刷新将新token写入响应头
|
||||||
|
if (!data.isRememberMe && (data.exp * 1000 - Date.now()) < 30 * 60 * 1000) {
|
||||||
|
//token有效期不足半小时,重新签发新token给客户端
|
||||||
|
let newYan = StringHelper.getRandomString(0, 10);
|
||||||
|
let newToken = jwt.sign({
|
||||||
|
u_id: data.u_id,
|
||||||
|
isRememberMe:false
|
||||||
|
}, newYan, {
|
||||||
|
expiresIn: config.shortTokenExpiration
|
||||||
|
});
|
||||||
|
// await redisHelper.deleteKey(token);
|
||||||
|
await redisHelper.setString(newToken, newYan,config.shortTokenExpiration);
|
||||||
|
ctx.response.set('new-token', newToken);
|
||||||
|
ctx.response.set('Access-Control-Expose-Headers','new-token');
|
||||||
|
}
|
||||||
|
//获取用户信息
|
||||||
|
let userInfoKey = data.u_id + '_userInfo';
|
||||||
|
let userInfo = await redisHelper.getString(userInfoKey);
|
||||||
|
if (userInfo == null || Object.keys(userInfo).length != 3) {
|
||||||
|
userInfo = await mysqlHelper.first(`select u_id,u_type,j_id from user where u_id=?`, data.u_id);
|
||||||
|
await redisHelper.setString(userInfoKey, JSON.stringify(userInfo), 24 * 60 * 60);
|
||||||
|
}else{
|
||||||
|
userInfo = JSON.parse(userInfo);
|
||||||
|
}
|
||||||
|
ctx.userInfo = userInfo;
|
||||||
|
//更新用户上次访问时间
|
||||||
|
mysqlHelper.execute(`update user set last_login_time=? where u_id=?`,Date.now(),userInfo.u_id);
|
||||||
|
//管理员拥有全部权限
|
||||||
|
if (userInfo.u_type == 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
//获取该用户类型权限
|
||||||
|
let authKey = userInfo.j_id + '_authority';
|
||||||
|
let urls = await redisHelper.getObject(authKey);
|
||||||
|
// let urls = null;
|
||||||
|
if (urls == null) {
|
||||||
|
urls = await mysqlHelper.row(`
|
||||||
|
select b.r_id,b.url,b.method from jurisdiction_resource a inner join resource b on a.r_id = b.r_id where a.j_id=?
|
||||||
|
`, userInfo.j_id);
|
||||||
|
let temp = {};
|
||||||
|
urls.forEach(item => {
|
||||||
|
temp[item.url + item.method] = true;
|
||||||
|
})
|
||||||
|
await redisHelper.setObject(authKey, temp);
|
||||||
|
urls = temp;
|
||||||
|
}
|
||||||
|
//判断是否拥有权限
|
||||||
|
if (urls.hasOwnProperty(ctx._matchedRoute.replace(config.url_prefix, '') + ctx.method)) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
根据用户id获取用户身份id,根据用户身份id从redis中获取拥有的权限,如为null,从mysql数据库中拉取,并存入redis中,然后判断是否拥有要访问的url权限。
|
||||||
|
|
||||||
|
#### c、数据持久化
|
||||||
|
|
||||||
|
  本系统中使用mysql存储数据,redis做缓存,由于当时操作库不支持promise,故对它两做了个promise封装,方便代码中调用,参见:[MysqlHelper](https://github.com/FleyX/teach_system/tree/master/teachSystem/util/MysqlHelper.js),[RedisHelper.js](https://github.com/FleyX/teach_system/tree/master/teachSystem/util/RedisHelper.js)。
|
||||||
|
|
||||||
|
### 2、前端
|
||||||
|
|
||||||
|
  前端使用vue-cli构建vue项目,主要用到了vue-router,element-ui,axios这三个组件。
|
||||||
|
|
||||||
|
#### a、路由组织
|
||||||
|
|
||||||
|
  单页应用需要前端自己组织路由。本系统将路由分成了三个部分:公共,管理端,学生端。index.js如下:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
export default new Router({
|
||||||
|
mode: 'history',
|
||||||
|
base: '/app/',
|
||||||
|
routes: [{
|
||||||
|
path: '',
|
||||||
|
name: 'indexPage',
|
||||||
|
component: IndexPage
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/about',
|
||||||
|
name: 'about',
|
||||||
|
component: About
|
||||||
|
},
|
||||||
|
Admin,
|
||||||
|
Client,
|
||||||
|
Public,
|
||||||
|
{
|
||||||
|
path: '*',
|
||||||
|
name: "NotFound",
|
||||||
|
component: NotFound
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
其中的Admin,Client,Public分别为各部分的路由,以子路由的形式一级级组织。如下所示:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
export default {
|
||||||
|
path: "/client",
|
||||||
|
component: Client,
|
||||||
|
beforeEnter: (to, from, next) => {
|
||||||
|
if (getClientUserInfo() == null) {
|
||||||
|
next({
|
||||||
|
path: '/public/client_login',
|
||||||
|
replace: true,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
children: [{
|
||||||
|
//学生端主页
|
||||||
|
path: '',
|
||||||
|
name: "ClientMain",
|
||||||
|
component: ClientHome
|
||||||
|
}, {
|
||||||
|
//学生个人资料页面
|
||||||
|
path: 'person/student_info',
|
||||||
|
name: "StudentInfo",
|
||||||
|
component: StudentInfo
|
||||||
|
}, {
|
||||||
|
//公告页面
|
||||||
|
path: 'course/:c_id/announcement',
|
||||||
|
name: 'Main',
|
||||||
|
component: Announcement
|
||||||
|
}, {
|
||||||
|
//课程基本信息
|
||||||
|
path: 'course/:c_id/base',
|
||||||
|
component: ClientMain,
|
||||||
|
children: [{
|
||||||
|
path: 'course_intro',
|
||||||
|
name: "ClientCourseIntro",
|
||||||
|
component: CourseIntro
|
||||||
|
}, {
|
||||||
|
path: 'exam_type',
|
||||||
|
name: "ClientExamType",
|
||||||
|
component: ExamType
|
||||||
|
}
|
||||||
|
......
|
||||||
|
```
|
||||||
|
|
||||||
|
其中的beforEnter为钩子函数,每次进入路由时执行该函数,用于判断用户是否登录。这里涉及到了一个前端鉴权的概念,由于前后端分离了,前端也必须做鉴权以免用户进入到了无权限的页面,这里我只是简单的做了登录判断,更详细的url鉴权也可实现,只需在对应的钩子函数中进行鉴权操作,更多关于钩子函数信息[点击这里](https://router.vuejs.org/zh/guide/advanced/navigation-guards.html)。
|
||||||
|
|
||||||
|
#### b、请求封装
|
||||||
|
|
||||||
|
  前端还有一个比较重要的部分是ajax请求的处理,请求处理还保护错误处理,有些错误只需要统一处理,而有些又需要独立的处理,这样一来就需要根据业务需求进行一下请求封装了,对结果进行处理后再返回给调用者。我的实现思路是发起请求,收到响应后先对错误进行一个同意弹窗提示,然后再将错误继续向后传递,调用者可选择性的捕获错误进行针对性处理,主要代码如下:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
request = (url, method, params, form, isFormData, type) => {
|
||||||
|
let token;
|
||||||
|
if (type == 'admin')
|
||||||
|
token = getToken();
|
||||||
|
else
|
||||||
|
token = getClientToken();
|
||||||
|
let headers = {
|
||||||
|
'Authorization': token
|
||||||
|
};
|
||||||
|
if (isFormData) {
|
||||||
|
headers['Content-Type'] = "multipart/form-data";
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
axios({
|
||||||
|
url,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
data: form,
|
||||||
|
headers,
|
||||||
|
// timeout:2000
|
||||||
|
}).then(res => {
|
||||||
|
resolve(res.data);
|
||||||
|
//检查是否有更新token
|
||||||
|
// console.log(res);
|
||||||
|
if (res.headers['new-token'] != undefined) {
|
||||||
|
console.log('set new token');
|
||||||
|
if (vm.$route.path.startsWith('/admin')){
|
||||||
|
localStorage.setItem("token",res.headers['new-token']);
|
||||||
|
window.token = undefined;
|
||||||
|
}else if(vm.$route.path.startsWith('/client')){
|
||||||
|
localStorage.setItem("clientToken",res.headers['new-token']);
|
||||||
|
window.clientToken = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
reject(err);
|
||||||
|
if (err.code == 'ECONNABORTED') {
|
||||||
|
alertNotify("错误", "请求超时", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (err.message == 'Network Error') {
|
||||||
|
alertNotify("错误", "无法连接服务器", 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (err.response != undefined) {
|
||||||
|
switch (err.response.status) {
|
||||||
|
case 401:
|
||||||
|
if (window.isGoToLogin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//使用该变量表示是否已经弹窗提示了,避免大量未登录弹窗堆积。
|
||||||
|
window.isGoToLogin = true;
|
||||||
|
vm.$alert(err.response.data, "警告", {
|
||||||
|
type: "warning",
|
||||||
|
showClose: false
|
||||||
|
}).then(res => {
|
||||||
|
window.isGoToLogin = false;
|
||||||
|
if (vm.$route.path.startsWith('/admin/')) {
|
||||||
|
clearInfo();
|
||||||
|
vm.$router.replace("/public/admin_login");
|
||||||
|
} else {
|
||||||
|
clearClientInfo();
|
||||||
|
vm.$router.replace("/public/client_login");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 403:
|
||||||
|
alertNotify("Error:403", '拒绝执行:' + err.response.data, "error");
|
||||||
|
break;
|
||||||
|
case 404:
|
||||||
|
alertNotify("Error:404", "找不到资源:" + url.substr(0, url.indexOf('?')), 'error');
|
||||||
|
break;
|
||||||
|
case 400:
|
||||||
|
alertNotify("Error:400", "请求参数错误:" + err.response.data, 'error');
|
||||||
|
break;
|
||||||
|
case 500:
|
||||||
|
alertNotify("Error:500", "服务器内部错误:" + err.response.data, 'error');
|
||||||
|
default:
|
||||||
|
console.log('存在错误未处理:' + err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
  到这里就算是简单介绍完了,,想要更加深入了解的可以去github查看源代码,地址如下:[https://github.com/FleyX/teach_system,](https://github.com/FleyX/teach_system)记得star哦!
|
150
数据库/分布式事务1.md
@ -1,75 +1,75 @@
|
|||||||
## 前言
|
## 前言
|
||||||
|
|
||||||
  分布式事务是企业集成中的一个技术难点,也是每一个分布式系统架构中都会涉及到的一个东西,特别是在微服务架构中,几乎是无法避免的。
|
  分布式事务是企业集成中的一个技术难点,也是每一个分布式系统架构中都会涉及到的一个东西,特别是在微服务架构中,几乎是无法避免的。
|
||||||
|
|
||||||
##一、从单机事务到分布式
|
##一、从单机事务到分布式
|
||||||
|
|
||||||
###1.数据库事务
|
###1.数据库事务
|
||||||
|
|
||||||
我们都知道数据库事务的四个特性:原子性、一致性、隔离性和持久性,数据库事务由数据库软件自身来完成。假如数据库在提交事务的时候突然断电,数据库可以在日志记录中找到上一次事务操作然后根据当前数据库的情况进行undo回滚或者是redo前滚(基于某时刻的完整备份然后执行从该时刻到崩溃时间所有增删改查操作,使数据库恢复到崩溃前的状态)。在分布式环境中可能遇到的问题就更多了,例如机器宕机、网络异常、消息乱序、数据错误、存储数据丢失等等,数据库自身并没有比较好的解决方案(下文会提到一种数据库支持的解决方法)。
|
我们都知道数据库事务的四个特性:原子性、一致性、隔离性和持久性,数据库事务由数据库软件自身来完成。假如数据库在提交事务的时候突然断电,数据库可以在日志记录中找到上一次事务操作然后根据当前数据库的情况进行undo回滚或者是redo前滚(基于某时刻的完整备份然后执行从该时刻到崩溃时间所有增删改查操作,使数据库恢复到崩溃前的状态)。在分布式环境中可能遇到的问题就更多了,例如机器宕机、网络异常、消息乱序、数据错误、存储数据丢失等等,数据库自身并没有比较好的解决方案(下文会提到一种数据库支持的解决方法)。
|
||||||
|
|
||||||
###2.分布式理论
|
###2.分布式理论
|
||||||
|
|
||||||
  当单个数据库的性能产生瓶颈的时候,我们可能会对数据库进行分区,这里分区指的是物理分区,分区后不同的库可能就在不同的服务器上了,这个时候单个数据库的ACID已经不能适应这种情况了,在集群情况下想保证集群的ACID是很困难的,即使能够达到,效率和性能也会大幅下降,而且难以拓展。这就需要一个新的理论**CAP原则**。
|
  当单个数据库的性能产生瓶颈的时候,我们可能会对数据库进行分区,这里分区指的是物理分区,分区后不同的库可能就在不同的服务器上了,这个时候单个数据库的ACID已经不能适应这种情况了,在集群情况下想保证集群的ACID是很困难的,即使能够达到,效率和性能也会大幅下降,而且难以拓展。这就需要一个新的理论**CAP原则**。
|
||||||
|
|
||||||
  CAP是加州大学伯克利分校Eric Brewer教授提出的,他指出WEB服务无法同时满足以下3个属性:
|
  CAP是加州大学伯克利分校Eric Brewer教授提出的,他指出WEB服务无法同时满足以下3个属性:
|
||||||
|
|
||||||
- 一致性(Consistency):客户端发起的一系列操作都会同时生效
|
- 一致性(Consistency):客户端发起的一系列操作都会同时生效
|
||||||
- 可用性(Availability):每个操作必须以可预期的响应结束
|
- 可用性(Availability):每个操作必须以可预期的响应结束
|
||||||
- 分区容错性(Partition Tolerance):即使单个组件无法使用,操作任然可以完成
|
- 分区容错性(Partition Tolerance):即使单个组件无法使用,操作任然可以完成
|
||||||
|
|
||||||
具体的说在分布式系统中,任何数据库至多只能同时支持上面的两个属性,但是任何横向拓展策略都要依赖数据分区。因此,设计人员必须在一致性与可用性中作出选择。在此基础上后面又提出了另外一个理论,**BASE**理论,用于对CAP原则进一步扩充。BASE理论指的是:
|
具体的说在分布式系统中,任何数据库至多只能同时支持上面的两个属性,但是任何横向拓展策略都要依赖数据分区。因此,设计人员必须在一致性与可用性中作出选择。在此基础上后面又提出了另外一个理论,**BASE**理论,用于对CAP原则进一步扩充。BASE理论指的是:
|
||||||
|
|
||||||
- 基本可用(Basically Available)
|
- 基本可用(Basically Available)
|
||||||
- 软状态(Soft State)
|
- 软状态(Soft State)
|
||||||
- 最终一致性(Eventually Consistent)
|
- 最终一致性(Eventually Consistent)
|
||||||
|
|
||||||
BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:**我们没法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性**。
|
BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:**我们没法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性**。
|
||||||
|
|
||||||
## 二、解决方案
|
## 二、解决方案
|
||||||
|
|
||||||
### 1.两阶段提交(2PC)
|
### 1.两阶段提交(2PC)
|
||||||
|
|
||||||
主流关系型数据库大都支持,又叫**XA Transactions(XAT)**。其中XA是一个两阶段提交协议,该协议分为以下两个阶段:
|
主流关系型数据库大都支持,又叫**XA Transactions(XAT)**。其中XA是一个两阶段提交协议,该协议分为以下两个阶段:
|
||||||
|
|
||||||
- 第一阶段:事务协调器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交
|
- 第一阶段:事务协调器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交
|
||||||
- 第二阶段:事务协调器要求每个数据库提交数据
|
- 第二阶段:事务协调器要求每个数据库提交数据
|
||||||
|
|
||||||
其中,在第一阶段如果有任何一个数据库否决此次提交,那么所有数据库都会被要求回滚它们在此次事务中的那部分信息。通俗的说在进行一次事务操作时,事务管理器会先询问每个用到的数据库能不能操作成功(数据库进行一次预操作看能否成功),如果任意一个说操作失败,所有的数据库都回滚预操作。
|
其中,在第一阶段如果有任何一个数据库否决此次提交,那么所有数据库都会被要求回滚它们在此次事务中的那部分信息。通俗的说在进行一次事务操作时,事务管理器会先询问每个用到的数据库能不能操作成功(数据库进行一次预操作看能否成功),如果任意一个说操作失败,所有的数据库都回滚预操作。
|
||||||
|
|
||||||
**优点**:尽量保证了数据的强一致
|
**优点**:尽量保证了数据的强一致
|
||||||
|
|
||||||
**缺点**:实现复杂,可用性降低,同时对性能影响很大
|
**缺点**:实现复杂,可用性降低,同时对性能影响很大
|
||||||
|
|
||||||
### 2.补偿事务(TCC)
|
### 2.补偿事务(TCC)
|
||||||
|
|
||||||
核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个阶段:
|
核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个阶段:
|
||||||
|
|
||||||
- Try阶段主要对业务系统做检测及资源预留
|
- Try阶段主要对业务系统做检测及资源预留
|
||||||
- Confirm阶段主要对业务系统做确认提交,Try阶段执行成功然后开始执行Confirm阶段时,默认Confirm阶段是不会出错的。也就是只要Try成功,Confirm一定成功。
|
- Confirm阶段主要对业务系统做确认提交,Try阶段执行成功然后开始执行Confirm阶段时,默认Confirm阶段是不会出错的。也就是只要Try成功,Confirm一定成功。
|
||||||
- Cancel阶段主要在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
|
- Cancel阶段主要在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
|
||||||
|
|
||||||
**优点**:跟2PC比起来,实现以及流程相对简单了点,但是数据的一致性也差一点
|
**优点**:跟2PC比起来,实现以及流程相对简单了点,但是数据的一致性也差一点
|
||||||
|
|
||||||
**缺点**:在第二阶段、第三阶段都由可能失败。对业务代码入侵比较严重,需要在实现的时候多写很多补偿代码。
|
**缺点**:在第二阶段、第三阶段都由可能失败。对业务代码入侵比较严重,需要在实现的时候多写很多补偿代码。
|
||||||
|
|
||||||
### 3.本地消息表(异步补偿)
|
### 3.本地消息表(异步补偿)
|
||||||
|
|
||||||
本地消息应该是业界使用最多的,其核心思想是将分布式事务拆分成本地事务进行处理,这种思路来源于ebay。
|
本地消息应该是业界使用最多的,其核心思想是将分布式事务拆分成本地事务进行处理,这种思路来源于ebay。
|
||||||
|
|
||||||
基本思路如下:
|
基本思路如下:
|
||||||
|
|
||||||
每个分布式节点都需要额外建一个消息表。
|
每个分布式节点都需要额外建一个消息表。
|
||||||
|
|
||||||
消息生产方,记录消息发送状态。消息表和业务数据放在一个事物里提交,然后消息经过MQ发送到消息的消费方,如果消息发送失败,进行重新发送。
|
消息生产方,记录消息发送状态。消息表和业务数据放在一个事物里提交,然后消息经过MQ发送到消息的消费方,如果消息发送失败,进行重新发送。
|
||||||
|
|
||||||
消息消费方,需要处理这个消息,并完成自己的业务逻辑,同上也要将消息记录到消息表中,此时如果本地事务处理成功,将消息通过MQ发送给下一个消费方,知道所有事物执行完毕,如果事物处理失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作,生产方再向上传递,直到回到初始状态。
|
消息消费方,需要处理这个消息,并完成自己的业务逻辑,同上也要将消息记录到消息表中,此时如果本地事务处理成功,将消息通过MQ发送给下一个消费方,知道所有事物执行完毕,如果事物处理失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作,生产方再向上传递,直到回到初始状态。
|
||||||
|
|
||||||
生产方和消费方定时扫描本地消息表,把还没处理玩的消息或失败消息再次发送一遍,这需要一个靠谱的自动对账补账逻辑。
|
生产方和消费方定时扫描本地消息表,把还没处理玩的消息或失败消息再次发送一遍,这需要一个靠谱的自动对账补账逻辑。
|
||||||
|
|
||||||
这种方案遵循BASE理论,采用最终一致性,实现较为简单,性能也不错。
|
这种方案遵循BASE理论,采用最终一致性,实现较为简单,性能也不错。
|
||||||
|
|
||||||
**优点**:一种非常经典的实现,避免了分布式事务,实现了最终一致性。
|
**优点**:一种非常经典的实现,避免了分布式事务,实现了最终一致性。
|
||||||
|
|
||||||
**缺点**:消息表耦合到了业务系统中。
|
**缺点**:消息表耦合到了业务系统中。
|
||||||
|
|
||||||
|
@ -1,45 +1,45 @@
|
|||||||
## 一、 数据库事务四大特性ACID
|
## 一、 数据库事务四大特性ACID
|
||||||
|
|
||||||
### 1、 原子性(Atomicity)
|
### 1、 原子性(Atomicity)
|
||||||
|
|
||||||
  原子性是指事务包含的所有操作要么全部执行成功,否则失败回滚,回到未执行事务前的状态。
|
  原子性是指事务包含的所有操作要么全部执行成功,否则失败回滚,回到未执行事务前的状态。
|
||||||
|
|
||||||
### 2、一致性(Consistency)
|
### 2、一致性(Consistency)
|
||||||
|
|
||||||
  一致性是指事务必须使数据库从一个一致性状态变成另一个一致性状态,也就是事务执行前后必须处于一致性状态。
|
  一致性是指事务必须使数据库从一个一致性状态变成另一个一致性状态,也就是事务执行前后必须处于一致性状态。
|
||||||
  以转账为例,假设用户A和B两者的钱加起来是5000,那么不管A和B之间如何转账,转多少次,事务结束后两个用户的钱加起来应该还得是5000,这就是事务的一致性。
|
  以转账为例,假设用户A和B两者的钱加起来是5000,那么不管A和B之间如何转账,转多少次,事务结束后两个用户的钱加起来应该还得是5000,这就是事务的一致性。
|
||||||
|
|
||||||
### 3、 隔离性(Isolation)
|
### 3、 隔离性(Isolation)
|
||||||
|
|
||||||
  隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其它的事务操作所干扰,多个并发事务之间要相互隔离。
|
  隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其它的事务操作所干扰,多个并发事务之间要相互隔离。
|
||||||
|
|
||||||
  既要达到这么一种效果:对于任意的两个并发的事务T1和T2,它们相互之间感觉不到对方正在并发的执行,关于事务的隔离性数据库提供了多种隔离级别,假设我们不考虑隔离性会发生什么问题呢?
|
  既要达到这么一种效果:对于任意的两个并发的事务T1和T2,它们相互之间感觉不到对方正在并发的执行,关于事务的隔离性数据库提供了多种隔离级别,假设我们不考虑隔离性会发生什么问题呢?
|
||||||
|
|
||||||
- 脏读
|
- 脏读
|
||||||
|
|
||||||
在一个事务中对数据做了修改,但还未提交,这是其他人读取这个数据,得到的是修改后的值,一旦事务回滚了,再次读取时发现值又变了。
|
在一个事务中对数据做了修改,但还未提交,这是其他人读取这个数据,得到的是修改后的值,一旦事务回滚了,再次读取时发现值又变了。
|
||||||
|
|
||||||
- 不可重复读
|
- 不可重复读
|
||||||
|
|
||||||
不可重复读是在对数据库中的某个数据重复查询时,返回了不同的查询结果,这是由于在查询期间,被另一个事物修改并提交了。和脏读的区别是脏读读取的是另一个事物未提交的脏数据,不可重复读读取的是前一事物已提交的数据。通常情况下不可重复读并不是问题。
|
不可重复读是在对数据库中的某个数据重复查询时,返回了不同的查询结果,这是由于在查询期间,被另一个事物修改并提交了。和脏读的区别是脏读读取的是另一个事物未提交的脏数据,不可重复读读取的是前一事物已提交的数据。通常情况下不可重复读并不是问题。
|
||||||
|
|
||||||
- 幻读(虚读)
|
- 幻读(虚读)
|
||||||
|
|
||||||
幻读是事物非独立执行时发生的一种现象,例如事物T1对一个表中所有的行的某个数据设置为了1,这时还有一个其他操作向该表中插入了一行数据,并且数据不为1,如果操作T1的用户查看刚刚修改的数据就会发现还有一行没有修改,其实这行是在修改操作完毕后新加入的一条数据。
|
幻读是事物非独立执行时发生的一种现象,例如事物T1对一个表中所有的行的某个数据设置为了1,这时还有一个其他操作向该表中插入了一行数据,并且数据不为1,如果操作T1的用户查看刚刚修改的数据就会发现还有一行没有修改,其实这行是在修改操作完毕后新加入的一条数据。
|
||||||
|
|
||||||
### 4、 持久性
|
### 4、 持久性
|
||||||
|
|
||||||
  持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即使在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
|
  持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即使在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
|
||||||
|
|
||||||
   例如我们在使用jdbc操作数据库时,在提交事务方法成功后,就可以认定事务已经被正确提交,即使数据发生重大故障,操作也会正确完成。
|
   例如我们在使用jdbc操作数据库时,在提交事务方法成功后,就可以认定事务已经被正确提交,即使数据发生重大故障,操作也会正确完成。
|
||||||
|
|
||||||
## 二、 四种隔离级别
|
## 二、 四种隔离级别
|
||||||
|
|
||||||
  Mysql数据库提供了四种数据库的隔离级别:
|
  Mysql数据库提供了四种数据库的隔离级别:
|
||||||
|
|
||||||
1. Serializable(串行化):对表加了读写锁,每次读写都需要获取共享锁,丧失了并发速度,可避免脏读,不可重复读,幻读的发生
|
1. Serializable(串行化):对表加了读写锁,每次读写都需要获取共享锁,丧失了并发速度,可避免脏读,不可重复读,幻读的发生
|
||||||
2. Repeatable read(可重复读):在同一个事务内的查询都是事务开始时刻一致的,可避免脏读、不可重复读的发生
|
2. Repeatable read(可重复读):在同一个事务内的查询都是事务开始时刻一致的,可避免脏读、不可重复读的发生
|
||||||
3. Read committed(读已提交):只能读取到已经提交的数据,避免了脏读的发生。
|
3. Read committed(读已提交):只能读取到已经提交的数据,避免了脏读的发生。
|
||||||
4. Read uncommitted(读未提交):所有都能读取到,啥都无法避免
|
4. Read uncommitted(读未提交):所有都能读取到,啥都无法避免
|
||||||
|
|
||||||
  Mysql默认的事务隔离级别是2。
|
  Mysql默认的事务隔离级别是2。
|
57
网络/nat模式虚拟机主机相互ping通.md
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
## 1、wmware设置
|
||||||
|
|
||||||
|
  这篇记录下nat网络模式下虚拟机与主机的相互ping通。首先使用wmware建立一个ubuntu虚拟机,网络模式选择nat模式。然后点击虚拟网络编辑:
|
||||||
|
|
||||||
|
![1536153781962](./picFolder/pic1.png)
|
||||||
|
|
||||||
|
接下来点击nat设置:
|
||||||
|
|
||||||
|
![1536153954193](./picFolder/pic2.png)
|
||||||
|
|
||||||
|
看到如下:
|
||||||
|
|
||||||
|
![pic3](./picFolder/pic3.png)
|
||||||
|
|
||||||
|
上面红框是关键,记录这个值,下面虚拟机设置静态ip要用到。
|
||||||
|
|
||||||
|
## 2、window网络设置
|
||||||
|
|
||||||
|
  打开网络适配器页面,选择VMnet,右键->属性->Internet协议版本 4(TCP/IPV4)->属性,设置ip地址为上面上面网关地址最后一个数改成1,比如192.168.128.2就要设置为192.168.128.1,同时设置子网掩码为255.255.255.0,默认网关不要填。我的如下:
|
||||||
|
|
||||||
|
![pic4](./picFolder/pic4.png)
|
||||||
|
|
||||||
|
**如果想让虚拟机能够访问主机需要关闭主机的防火墙**
|
||||||
|
|
||||||
|
## 3、ubuntu设置
|
||||||
|
|
||||||
|
  编辑/etc/network/interfaces
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vim /etc/network/interfaces
|
||||||
|
# This file describes the network interfaces available on your system
|
||||||
|
# and how to activate them. For more information, see interfaces(5).
|
||||||
|
|
||||||
|
source /etc/network/interfaces.d/*
|
||||||
|
|
||||||
|
# The loopback network interface
|
||||||
|
auto lo
|
||||||
|
iface lo inet loopback
|
||||||
|
|
||||||
|
# The primary network interface
|
||||||
|
auto ens33
|
||||||
|
# dhcp 改成static,然后设置下面的address,netmask,gateway
|
||||||
|
iface ens33 inet static
|
||||||
|
address 192.168.128.129
|
||||||
|
netmask 255.255.255.0
|
||||||
|
gateway 192.168.128.2
|
||||||
|
# 设置dns
|
||||||
|
dns-nameservers 192.168.128.2
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
然后执行`/etc/init.d/networking restart`,或者重启虚拟机以启用网络设置。
|
||||||
|
|
||||||
|
## 3、验证
|
||||||
|
|
||||||
|
  现在虚拟机中`ping 192.168.128.1`可以ping通,主机中`ping 192.168.128.129`也可ping通。
|
BIN
网络/picFolder/pic1.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
网络/picFolder/pic2.png
Normal file
After Width: | Height: | Size: 83 KiB |
BIN
网络/picFolder/pic3.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
网络/picFolder/pic4.png
Normal file
After Width: | Height: | Size: 67 KiB |