修改图片路径

This commit is contained in:
fxb 2018-10-07 17:53:22 +08:00
parent bb64ddf34b
commit c344f1be83
45 changed files with 1527 additions and 934 deletions

4
.gitignore vendored
View File

@ -1,3 +1,3 @@
pic pic
**.docx **.docx
**.doc **.doc

View File

@ -1,4 +1,4 @@
# technology-note # technology-note
记录技术笔记 记录技术笔记
说明github不支持markdown中内嵌base64图片故需要查看图片请使用其他markdown软件查看文章 说明github不支持markdown中内嵌base64图片故需要查看图片请使用其他markdown软件查看文章

View File

@ -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>
``` ```

View File

@ -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 | 同上 |
&emsp;&emsp;byType在出现多个匹配项时不会自动选择一个然是报错为避免报错有两种办法1.使用\<bean>元素的primary属性设置为首选Bean但所有bean的默认primary都是true因此我们需要将所有非首选Bean设置为false2.将Bean的`autowire-candidate`熟悉设置为**false **,取消 这个Bean的候选资格这个Bean便不会自动注入了。 &emsp;&emsp;byType在出现多个匹配项时不会自动选择一个然是报错为避免报错有两种办法1.使用\<bean>元素的primary属性设置为首选Bean但所有bean的默认primary都是true因此我们需要将所有非首选Bean设置为false2.将Bean的`autowire-candidate`熟悉设置为**false **,取消 这个Bean的候选资格这个Bean便不会自动注入了。
&emsp;&emsp;contructor自动装配和byType有一样的局限性当发现多个Bean匹配某个构造器入参时Spring不会尝试选择其中一个此外如果一个类有多个构造器都满足自动装配的条件Spring也不会猜测哪个更合适使用。 &emsp;&emsp;contructor自动装配和byType有一样的局限性当发现多个Bean匹配某个构造器入参时Spring不会尝试选择其中一个此外如果一个类有多个构造器都满足自动装配的条件Spring也不会猜测哪个更合适使用。
###2、默认自动装配 ###2、默认自动装配
&emsp;&emsp;如果需要为Spring应用上下文中的每个Bean或者其中的大多数配置相同的autowire属性可以在根元素\<beans>上增加一个default-autowire属性默认该属性设置为none。该属性只应用于指定配置文件中的所有Bean并不是Spring上下文中的所有Bean。 &emsp;&emsp;如果需要为Spring应用上下文中的每个Bean或者其中的大多数配置相同的autowire属性可以在根元素\<beans>上增加一个default-autowire属性默认该属性设置为none。该属性只应用于指定配置文件中的所有Bean并不是Spring上下文中的所有Bean。
###3、混合使用自动装配和显式装配 ###3、混合使用自动装配和显式装配
&emsp;&emsp;当我们对某个Bean使用了自动装配策略并不代表我们不能对该Bean的某些属性进行显示装配任然可以为任意一个属性配置\<property>元素,显式装配将会覆盖自动装配。**但是**当使用constructor自动装配策略时我们必须让Spring自动装配构造器所有入参不能使用\<constructor-arg>元素进行混合。 &emsp;&emsp;当我们对某个Bean使用了自动装配策略并不代表我们不能对该Bean的某些属性进行显示装配任然可以为任意一个属性配置\<property>元素,显式装配将会覆盖自动装配。**但是**当使用constructor自动装配策略时我们必须让Spring自动装配构造器所有入参不能使用\<constructor-arg>元素进行混合。
## 二、注解装配 ## 二、注解装配
&emsp;&emsp;从Spring2.5开始可以使用注解自动装配Bean的属性使用注解允许更细粒度的自动装配可选择性的标注某一个属性来对其应用自动装配。Spring容器默认禁用注解装配需要在Spring配置中启用最简单的启用方式是使用Spring的context命令空间配置中的`<context:annotation-config>`,如下所示: &emsp;&emsp;从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>
``` ```
&emsp;&emsp;Spring3支持几种不同的用于自动装配的注解 &emsp;&emsp;Spring3支持几种不同的用于自动装配的注解
- Spring自带的@Autowired注解 - Spring自带的@Autowired注解
- JSR-330的@Inject注解 - JSR-330的@Inject注解
- JSR-250的@Resource注解 - JSR-250的@Resource注解
###1、使用@Autowired ###1、使用@Autowired
&emsp;&emsp;@Autowired用于对被注解对象启动ByType的自动装配,可用于以下对象: &emsp;&emsp;@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**
&emsp;&emsp;创建自定义限定器只需要使用@Qualifier注解作为它的源注解即可如下创建了一个Stringed限定器 &emsp;&emsp;创建自定义限定器只需要使用@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自动注入
&emsp;&emsp;为统一各种依赖注入框架的编程模型JCPJava Community Process发布的Java依赖注入规范被称为JSR-330从Spring3开始Spring已经开始兼容该依赖注入模型。 &emsp;&emsp;为统一各种依赖注入框架的编程模型JCPJava Community Process发布的Java依赖注入规范被称为JSR-330从Spring3开始Spring已经开始兼容该依赖注入模型。
&emsp;&emsp;@Autowired一样@Inject可以用来自动装配属性、方法和构造器。但是@Inject没有required属性,因此依赖关系必须存在,如不存在将抛出异常。 &emsp;&emsp;@Autowired一样@Inject可以用来自动装配属性、方法和构造器。但是@Inject没有required属性,因此依赖关系必须存在,如不存在将抛出异常。
&emsp;&emsp;JSR-330还提供另一种注入技巧注入一个Provider。Provider接口可以实现Bean引用的延迟注入以及注入Bean的多个实例等功能。 &emsp;&emsp;JSR-330还提供另一种注入技巧注入一个Provider。Provider接口可以实现Bean引用的延迟注入以及注入Bean的多个实例等功能。
&emsp;&emsp;例如我们有一个KnifeJuggler类需要注入一个或多个Knife实例假设Knife Bean的作用域声明为prototype下面的KnifeJuggler的构造器将获得多个Knife Bean: &emsp;&emsp;例如我们有一个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());
} }
} }
``` ```
&emsp;&emsp;相对于@Autowired所对应的@Qualifier@Inject对应的是@Named注解。事实上JSR-330中也有@Qualifier注解不过不建议直接使用建议通过该注解来创建自定义的限定注解和Spring的@Qualifier创建过程类似 &emsp;&emsp;相对于@Autowired所对应的@Qualifier@Inject对应的是@Named注解。事实上JSR-330中也有@Qualifier注解不过不建议直接使用建议通过该注解来创建自定义的限定注解和Spring的@Qualifier创建过程类似
### 3、注解中使用表达式 ### 3、注解中使用表达式
&emsp;&emsp;Spring3中引入的`@Value`属性可用来装配String类型的值和基本类型的值。借助SpEL表达式@Value不光可以装配硬编码值还可以在运行期动态计算表达式并装配,例如下面的: &emsp;&emsp;Spring3中引入的`@Value`属性可用来装配String类型的值和基本类型的值。借助SpEL表达式@Value不光可以装配硬编码值还可以在运行期动态计算表达式并装配,例如下面的:
```java ```java
@Value("#{systemProperties.name}") @Value("#{systemProperties.name}")
private String name; private String name;
``` ```
## 三、自动检测Bean ## 三、自动检测Bean
&emsp;&emsp;在Spring中使用上面说到的`<context:annotation-config>`可以做到自动装配但还是要在xml中申明Bean。Spring还有另一个元素`<context:component-scan>`,元素除了完成自动装配的功能还允许Spring自动检测Bean和定义Bean ,用法如下: &emsp;&emsp;在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 | 标识该类定义为服务 |
&emsp;&emsp;使用上述注解是Bean的ID默认为无限定类名。使用`@Component("name")`指定ID。 &emsp;&emsp;使用上述注解是Bean的ID默认为无限定类名。使用`@Component("name")`指定ID。
### 1、过滤组建扫描 ### 1、过滤组建扫描
&emsp;&emsp;通过为<context:component-scan >配置<context:include-filter ><context:exclude-filter >子元素我们可以随意调整扫描行为。下面的配置自动注册所有的TestInterface实现类 &emsp;&emsp;通过为<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属性所指定的正则表达式所匹配的类 |
&emsp;&emsp;exclude-filter使用和include-filter类似只是效果相反。 &emsp;&emsp;exclude-filter使用和include-filter类似只是效果相反。
## 四、使用Spring基于Java的配置 ## 四、使用Spring基于Java的配置
&emsp;&emsp;在Spring3.0中几乎可以不使用XML而使用纯粹的Java代码来配置Spring应用。 &emsp;&emsp;在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());
} }
``` ```
## 五、小结 ## 五、小结
&emsp;&emsp;终于写完了spring 的最小化配置对spring的各种注解也有了一些了解再不是之前看到注解一脸莫名其妙了虽然现在Springboot已经帮我们做了零XML配置但觉得还是有必要了解下XML配置实现这样对Java的配置实现理解也会更加深刻。 &emsp;&emsp;终于写完了spring 的最小化配置对spring的各种注解也有了一些了解再不是之前看到注解一脸莫名其妙了虽然现在Springboot已经帮我们做了零XML配置但觉得还是有必要了解下XML配置实现这样对Java的配置实现理解也会更加深刻。

View File

@ -1,239 +1,239 @@
## 一.面向切面编程 ## 一.面向切面编程
&emsp;&emsp;Spring的基础是IOC和AOP前面两节对IOC和DI做了简单总结这里再对AOP进行一个学习总结Spring基础就算有一个初步了解了。 &emsp;&emsp;Spring的基础是IOC和AOP前面两节对IOC和DI做了简单总结这里再对AOP进行一个学习总结Spring基础就算有一个初步了解了。
&emsp;&emsp;在软件开发中,我们可能需要一些跟业务无关但是又必须做的东西,比如日志,事务等,这些分布于应用中多处的功能被称为横切关注点,通常横切关注点从概念上是与应用的业务逻辑相分离的。如何将这些横切关注点与业务逻辑在代码层面进行分离,是面向切面编程(**AOP**)所要解决的。 &emsp;&emsp;在软件开发中,我们可能需要一些跟业务无关但是又必须做的东西,比如日志,事务等,这些分布于应用中多处的功能被称为横切关注点,通常横切关注点从概念上是与应用的业务逻辑相分离的。如何将这些横切关注点与业务逻辑在代码层面进行分离,是面向切面编程(**AOP**)所要解决的。
横切关注点可以被描述为影响应用多处的功能,切面能够帮助我们模块化横切关注点。下图直观呈现了横切关注点的概念: 横切关注点可以被描述为影响应用多处的功能,切面能够帮助我们模块化横切关注点。下图直观呈现了横切关注点的概念:
![横切关注点](./3.面向切面的Spring/切面示例.png) ![横切关注点](./picFolder/切面示例.png)
途中CourseServiceStudentServiceMiscService都需要类似安全、事务这样的辅助功能这些辅助功能就被称为横切关注点。 途中CourseServiceStudentServiceMiscService都需要类似安全、事务这样的辅助功能这些辅助功能就被称为横切关注点。
&emsp;&emsp;**继承**和**委托**是最常见的实现重用通用功能的面向对象技术。但是如果在整个程序中使用相同的基类继承往往会导致一个脆弱的对象体系;而使用委托可能需要对委托对象进行复杂的调用。 &emsp;&emsp;**继承**和**委托**是最常见的实现重用通用功能的面向对象技术。但是如果在整个程序中使用相同的基类继承往往会导致一个脆弱的对象体系;而使用委托可能需要对委托对象进行复杂的调用。
切面提供了取代继承和委托的另一种选择,而且更加清晰简洁。在面向切面编程时,我们任然在一个地方定义通用功能,但是我们可以通过声明的方式定义这个功能以何种方式在何处应用,而无需修改受影响的类,受影响类完全感受不到切面的存在。 切面提供了取代继承和委托的另一种选择,而且更加清晰简洁。在面向切面编程时,我们任然在一个地方定义通用功能,但是我们可以通过声明的方式定义这个功能以何种方式在何处应用,而无需修改受影响的类,受影响类完全感受不到切面的存在。
## 二.AOP常用术语 ## 二.AOP常用术语
&emsp;&emsp;下面是AOP中常用的名词。 &emsp;&emsp;下面是AOP中常用的名词。
### 1. 通知Advice ### 1. 通知Advice
&emsp;&emsp;通知定义了切面是什么以及何时使用。出了描述切面要完成的工作通知还解决了何时执行这个工作的问题。Sping切面可以应用以下5种类型的通知。 &emsp;&emsp;通知定义了切面是什么以及何时使用。出了描述切面要完成的工作通知还解决了何时执行这个工作的问题。Sping切面可以应用以下5种类型的通知。
- **Before** 在方法被调用之前调用通知 - **Before** 在方法被调用之前调用通知
- **After** 在方法完成之后调用通知,无论方法执行是否成功 - **After** 在方法完成之后调用通知,无论方法执行是否成功
- **After-returning** 在方法成功执行后调用通知 - **After-returning** 在方法成功执行后调用通知
- **After-throwing** 在方法抛出异常后调用通知 - **After-throwing** 在方法抛出异常后调用通知
- **Around** 通知包裹了被通知的方法,在被通知的方法调用前和调用后执行 - **Around** 通知包裹了被通知的方法,在被通知的方法调用前和调用后执行
###2.连接点Joinpoint ###2.连接点Joinpoint
&emsp;&emsp;应用可能有很多个时机应用通知,这些时机被称为连接点。连接点是应用在执行过程中能够插入切面的一个点,这个点可以是调用方法时、抛出异常时、甚至是修改字段时。切面代码可以利用这些切入到应用的正常流程中,并添加新的行为。 &emsp;&emsp;应用可能有很多个时机应用通知,这些时机被称为连接点。连接点是应用在执行过程中能够插入切面的一个点,这个点可以是调用方法时、抛出异常时、甚至是修改字段时。切面代码可以利用这些切入到应用的正常流程中,并添加新的行为。
### 3.切点Pointcut ### 3.切点Pointcut
&emsp;&emsp;切点定义了通知所要织入的一个或多个连接点。如果说通知定义了切面的“**什么**”和“**何时**”,那么切点就定义了“**何处**”。通常使用明确的类和方法名称来指定切点或者利用正则表达式定义匹配的类和方法来指定这些切点。有些AOP框架允许我们创建动态的切点可以更具运行时的策略来决定是否应用通知。 &emsp;&emsp;切点定义了通知所要织入的一个或多个连接点。如果说通知定义了切面的“**什么**”和“**何时**”,那么切点就定义了“**何处**”。通常使用明确的类和方法名称来指定切点或者利用正则表达式定义匹配的类和方法来指定这些切点。有些AOP框架允许我们创建动态的切点可以更具运行时的策略来决定是否应用通知。
### 4.切面Aspect ### 4.切面Aspect
&emsp;&emsp;切面是通知和切点的结合。通知和切点定义了关于切面的全部内容,**是什么**,在**何时**、**何处**完成其功能。 &emsp;&emsp;切面是通知和切点的结合。通知和切点定义了关于切面的全部内容,**是什么**,在**何时**、**何处**完成其功能。
### 5.引入 ### 5.引入
&emsp;&emsp;引入允许我们想现有的类添加新方法或属性。即在无需修改现有类的情况下让它们具有新的行为和状态。 &emsp;&emsp;引入允许我们想现有的类添加新方法或属性。即在无需修改现有类的情况下让它们具有新的行为和状态。
### 6.织入 ### 6.织入
&emsp;&emsp;织入是将切面应用到目标对象来创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里有多个点可以进行织入。 &emsp;&emsp;织入是将切面应用到目标对象来创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里有多个点可以进行织入。
- 编译期切面在目标类编译时被织入。这种方式需要特殊的编译期比如AspectJ的织入编译期 - 编译期切面在目标类编译时被织入。这种方式需要特殊的编译期比如AspectJ的织入编译期
- 类加载期切面在目标类加载到JVM时被织入。这种方式需要特殊的加载器它可以在目标类被引入应用之前增强该目标类的字节码例如AspectJ5的**LTW**load-time weaving - 类加载期切面在目标类加载到JVM时被织入。这种方式需要特殊的加载器它可以在目标类被引入应用之前增强该目标类的字节码例如AspectJ5的**LTW**load-time weaving
- 运行期切面在应用运行的某个时刻被织入。一般情况下AOP容器会为目标对象动态创建一个代理对象 - 运行期切面在应用运行的某个时刻被织入。一般情况下AOP容器会为目标对象动态创建一个代理对象
##三.Spring AOP ##三.Spring AOP
&emsp;&emsp;Spring在运行期通知对象通过在代理类中包裹切面Spring在运行期将切面织入到Spring管理的Bean中。代理类封装了目标类并拦截被通知的方法的调用再将调用转发给真正的目标Bean。由于Spring是基于动态代理所有Spring只支持方法连接点如果需要方法拦截之外的连接点拦截我们可以利用Aspect来协助SpringAOP。 &emsp;&emsp;Spring在运行期通知对象通过在代理类中包裹切面Spring在运行期将切面织入到Spring管理的Bean中。代理类封装了目标类并拦截被通知的方法的调用再将调用转发给真正的目标Bean。由于Spring是基于动态代理所有Spring只支持方法连接点如果需要方法拦截之外的连接点拦截我们可以利用Aspect来协助SpringAOP。
&emsp;&emsp;Spring在运行期通知对象通过在代理类中包裹切面Spring在运行期将切面织入到Spring管理的Bean中。代理类封装了目标类并拦截被通知的方法的调用再将调用转发给真正的目标Bean。由于Spring是基于动态代理所有Spring只支持方法连接点如果需要方法拦截之外的连接点拦截我们可以利用Aspect来协助SpringAOP。 &emsp;&emsp;Spring在运行期通知对象通过在代理类中包裹切面Spring在运行期将切面织入到Spring管理的Bean中。代理类封装了目标类并拦截被通知的方法的调用再将调用转发给真正的目标Bean。由于Spring是基于动态代理所有Spring只支持方法连接点如果需要方法拦截之外的连接点拦截我们可以利用Aspect来协助SpringAOP。
### 1、定义切点 ### 1、定义切点
&emsp;&emsp;在SpringAOP中需要使用AspectJ的切点表达式语言来定义切点。Spring只支持AspectJ的部分切点指示器如下表所示 &emsp;&emsp;在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 |
&emsp;其中只有execution指示器是唯一的执行匹配其他都是限制匹配。因此execution指示器是 &emsp;其中只有execution指示器是唯一的执行匹配其他都是限制匹配。因此execution指示器是
其中只有execution指示器是唯一的执行匹配其他都是限制匹配。因此execution指示器是我们在编写切点定义时最主要使用的指示器。 其中只有execution指示器是唯一的执行匹配其他都是限制匹配。因此execution指示器是我们在编写切点定义时最主要使用的指示器。
### 2、编写切点 ### 2、编写切点
&emsp;&emsp;假设我们要使用execution()指示器选择Hello类的sayHello()方法,表达式如下: &emsp;&emsp;假设我们要使用execution()指示器选择Hello类的sayHello()方法,表达式如下:
```java ```java
execution(* com.test.Hello.sayHello(..)) execution(* com.test.Hello.sayHello(..))
``` ```
方法表达式以*** **号开始,说明不管方法返回值的类型。然后指定全限定类名和方法名。对于方法参数列表,我们使用(**标识切点选择任意的sayHello()方法,无论方法入参是什么。 方法表达式以*** **号开始,说明不管方法返回值的类型。然后指定全限定类名和方法名。对于方法参数列表,我们使用(**标识切点选择任意的sayHello()方法,无论方法入参是什么。
&emsp;&emsp;同时我们可以使用&&(and)||(or)(not)来连接指示器,如下所示: &emsp;&emsp;同时我们可以使用&&(and)||(or)(not)来连接指示器,如下所示:
```java ```java
execution(* com.test.Hello.sayHello(..)) and !bean(xiaobu) execution(* com.test.Hello.sayHello(..)) and !bean(xiaobu)
``` ```
### 3、申明切面 ### 3、申明切面
&emsp;&emsp;在经典Spring AOP中使用ProxyFactoryBean非常复杂因此提供了申明式切面的选择在Spring的AOP配置命名空间中有如下配置元素 &emsp;&emsp;在经典Spring AOP中使用ProxyFactoryBean非常复杂因此提供了申明式切面的选择在Spring的AOP配置命名空间中有如下配置元素
| AOP配置元素 | 描述 | | AOP配置元素 | 描述 |
| ------------------------------ | ----------------------------------------------------------- | | ------------------------------ | ----------------------------------------------------------- |
| &lt;aop:advisor &gt; | 定义AOP通知器 | | &lt;aop:advisor &gt; | 定义AOP通知器 |
| &lt;aop:after &gt; | 定义AOP后置通知无论被通知方法是否执行成功 | | &lt;aop:after &gt; | 定义AOP后置通知无论被通知方法是否执行成功 |
| &lt;aop:after-returning &gt; | 定义AOP after-returning通知 | | &lt;aop:after-returning &gt; | 定义AOP after-returning通知 |
| &lt;aop:after-throwing &gt; | 定义after-throwing | | &lt;aop:after-throwing &gt; | 定义after-throwing |
| &lt;aop:around &gt; | 定义AOP环绕通知 | | &lt;aop:around &gt; | 定义AOP环绕通知 |
| &lt;aop:aspect &gt; | 定义切面 | | &lt;aop:aspect &gt; | 定义切面 |
| &lt;aop:aspectj-autoproxy &gt; | 启用@AspectJ注解驱动的切面 | | &lt;aop:aspectj-autoproxy &gt; | 启用@AspectJ注解驱动的切面 |
| &lt;aop:before &gt; | 定义AOP前置通知 | | &lt;aop:before &gt; | 定义AOP前置通知 |
| &lt;aop:config &gt; | 顶层的AOP配置元素。大多数的&lt;aop:* &gt;元素必须包含在其中 | | &lt;aop:config &gt; | 顶层的AOP配置元素。大多数的&lt;aop:* &gt;元素必须包含在其中 |
| &lt;aop:declare-parents &gt; | 为被通知的对象引入额外的接口,并透明的实现 | | &lt;aop:declare-parents &gt; | 为被通知的对象引入额外的接口,并透明的实现 |
| &lt;aop:pointcut &gt; | 定义切点 | | &lt;aop:pointcut &gt; | 定义切点 |
### 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配置实现
&emsp;&emsp;首先将Audience配置到springIOC中: &emsp;&emsp;首先将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、通过切面引入新功能
&emsp;&emsp;既然可以用AOP为对象拥有的方法添加新功能那为什么不能为对象增加新的方法呢利用被称为**引入**的AOP概念切面可以为Spring Bean添加新的方法示例图如下 &emsp;&emsp;既然可以用AOP为对象拥有的方法添加新功能那为什么不能为对象增加新的方法呢利用被称为**引入**的AOP概念切面可以为Spring Bean添加新的方法示例图如下
![引入](.\3.面向切面的Spring\引入新功能.png) ![引入](./picFolder/引入新功能.png)
当引入接口的方法被调用时代理将此调用委托给实现了新接口的某个其他对象。实际上Bean的实现被拆分到了多个类。 当引入接口的方法被调用时代理将此调用委托给实现了新接口的某个其他对象。实际上Bean的实现被拆分到了多个类。
- xml引入需要使用&lt;aop:declare-parents &gt;元素: - xml引入需要使用&lt;aop:declare-parents &gt;元素:
```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>
``` ```
顾名思义\&lt;declare-parents&gt;声明了此切面所通知的Bean在它的对象层次结构中有了新的父类型。其中types-matching指定增强的类implement-interface指定实现新方法的接口default-imple指定实现了implement-interface接口的实现类也可以用delegate-ref来指定一个Bean的引用。 顾名思义\&lt;declare-parents&gt;声明了此切面所通知的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属性指定了将被引入的接口

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -1,48 +0,0 @@
&emsp;&emsp;前面的博客有说到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.选择依赖
&emsp;&emsp;在这里选择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.测试
&emsp;&emsp;现在新建一个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字符串。

View File

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -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
&emsp;&emsp;在这里选择spring boot版本和web依赖忽略sql的依赖如有需要[点击这里](f),单独将mybatis的整合)后面也可手动编辑pom文件修改增加删除依赖 &emsp;&emsp;在这里选择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
&emsp;&emsp;现在新建一个controller包包下新建一个HelloController,创建之后项目目录结构如下: &emsp;&emsp;现在新建一个controller包包下新建一个HelloController,创建之后项目目录结构如下:
![1532969025023](D:\笔记\markdown\springboot系列\springboot搭建.assets\1532969025023.png) ![1532969025023](./picFolder/1532969025023.png)
HelloController代码如下 HelloController代码如下

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because one or more lines are too long

View File

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -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 {
&emsp;&emsp;编写好代码来试试结果如何,下面是运行截图: &emsp;&emsp;编写好代码来试试结果如何,下面是运行截图:
![1536312274474](.\读写分离配置\pic3.png) ![1536312274474](./picFolder/pic3.png)
&emsp;&emsp;断断续续写了好几天终于是写完了如果有帮助到你欢迎star哦这里是完整代码地址[点击跳转](https://github.com/FleyX/demo-project/tree/master/dxfl) &emsp;&emsp;断断续续写了好几天终于是写完了如果有帮助到你欢迎star哦这里是完整代码地址[点击跳转](https://github.com/FleyX/demo-project/tree/master/dxfl)

View File

@ -0,0 +1,17 @@
&emsp;&emsp;项目组最近加了一个新功能到代码中使用flyway记录数据库版本变更该工具会记录每次数据库结构的修改并生成sql文件存在指定目录上当然必须用它来变更数据库外部的变更它是无法感知的然后每次启动时flyway会检查使用的数据库和当前项目代码中的sql变更版本是否一致一致正常启动不一致中如果是数据库落后将会更新数据库这样能够保证代码在任何地方运行数据库都是一致的),否则就报错了。数据库中有一张表记录版本信息,如下图:
![版本记录](./picFolder/版本记录.PNG),同时本地代码中也有一个文件夹保存每次操作的sql语句如下图
![版本sql](./picFolder/版本sql.PNG)
通过对比checksum值来判断当前sql语句和生成数据库的执行语句是否一致checksum值由CRC32计算后处理得出。
&emsp;&emsp;然后问题就来了组中的其他人搭建好flyway后项目文件生成了两个sql文件我用git拉下来后启动报错checkupsum值对不上然后我又不懂这个flyway完全不知道咋回事然后就根据报错的位置一点点找到checkup值生成的代码发现是CRC32计算的就这么搞了一两个小时才发现是文件不一致了但是都是从git拉的怎么就我不一致呢想到可能是文件换行符的问题遂把那几个sql文件的文件换行符全换成了crlfwindows中的换行符然后居然就能够运行。。。关于为啥都从git拉取的文件换行符会不一样原因是他们都用的那个小乌龟的可视化我用的命令行。可视化工具自动配置了文件换行符的自动转换这是git的一个智能功能上传时将文件换行符替换为lf拉取时再替换为crlf这样保证中心仓库使用UNIX风格的换行符本地能够根据运行环境使用相对应的换行符风格但是命令行并没有配置。
&emsp;&emsp;解决办法也很简单开启git 的自动转换。
```
git config --global core.autocrlf true //开启换行符自动转换
git config --global core.safecrlf true //禁止混用换行符
```

View File

@ -0,0 +1,177 @@
## 一、背景
&emsp;&emsp;最近在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"}
}
```
其中的field1field2field3为对象中的属性名参数1参数2参数3为列名实际上这个指定了列的名称和这个列用到数据对象的哪个属性。
## 二、怎么用
&emsp;&emsp;以一个例子来说明怎么用假设有两个类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);
```
当然通常数据是通过数据库查询的,这里为了演示方便没有从数据库查找。
## 三、实现原理
&emsp;&emsp;这里简单说明下实现过程,从调用`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;
}
}
```
将workbooksheet名表头数据行数据传入crateSheet方法中创建sheet。
#### 2、创建表头
&emsp;&emsp;表头也就是一个表格的第一行,通常用来对列进行说明
```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、插入行数据
&emsp;&emsp;这里是最重要的部分首先通过数据的类对象获取它的反射属性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地址[点击跳转]()

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@ -0,0 +1,48 @@
##一、背景
&emsp;&emsp;今天领导让我写几个正则表达式来对密码做强度验证,听到写正则表达式内心是这样的感觉(哈哈,三分钟搞定,今天又可以打鱼了)。需求如下:密码组成只能是**数字****字母****英文可见半角符号**然后需要如下4个表达式:
- 长度6位及以上
- 长度6位及以上包含数字包含字母
- 长度6位及以上包含数字包含字母包含半角符号
- 长度六位及以上,包含数字,包含大写字母,包含小写字母,包含半角符号
&emsp;&emsp;看完需求我就有点懵了,包含数字或者字母我会写,但是同时存在还要在一个表达式中就有点懵了。
##二、解决方法
&emsp;&emsp;以第三种为例,这个可以分解为如下需求:
- 存在数字
- 存在字母
- 存在半角符号
- 长度六位及以上
关键是如何同时满足前三个条件,在我有限的知识里并不知道怎么搞,然后只好求助于万能的百度了,最终在找了几个小时后发现如下几个关键词,来源[菜鸟教程](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
```
&emsp;&emsp;有了上面的知识就能搞定需求啦。
## 三、结果
&emsp;&emsp;对于存在字母我们可以用这样的表达式`(?=.\*?[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
View File

@ -0,0 +1,452 @@
&emsp;&emsp;毕业才刚刚两个多月而已,现在想想大学生活是那么的遥不可及,感觉已经过了好久好久,社会了两个月才明白学校的好啊。。。额,扯远了,自从毕业开始就想找个时间写下毕设的记录总结,结果找了好久好久到今天才开始动笔。
&emsp;&emsp;我的毕业设计题目是:教学辅助系统的设计与实现,,是不是很俗。。。至于为啥是这个题目呢,完全是被导师坑了。。。。。
## 1、需求分析
&emsp;&emsp;拿到这个题目想着这个可能被做了无数次了,就像着哪里能够做出点创新,,最后强行创新出了一个个性化组题(根据学生水平出题)和徽章激励(达到某个要求给予一个徽章)。最后就产生了如下需求,系统有学生端和管理端:
学生端:
- 个人资料设置
- 徽章激励机制
- 查看课程信息,下载课程资料
- 知识点检测及针对性训练
- 在线作业,考试
- 在线答疑,向老师或者学生提问
管理端:
- 课程管理,用户管理(需要管理员权限)
- 课程信息管理
- 课程公告管理
- 题库管理,支持单选,多选,填空,编程题,支持题目编组
- 发布作业,包括个性组题和手动组题
- 发布考试,包括随机出题和手动出题
- 自动判题,支持编程题判重
- 在线答疑,给学生解答
- 统计分析,包含测试统计和课程统计
洋洋洒洒需求列了一大堆,后面才发现是给自己挖坑,,答辩老师一看这类的题目就不感兴趣了,不论你做的咋样(况且我的演讲能力真的很一般),最后累死累活写了一大堆功能也没太高的分,,不过倒是让我的系统设计能力和代码能力有了不少的提高。
## 2、架构选择
&emsp;&emsp;大三的时候了解到Node.js这个比较“奇葩"的异步语言再加上在公司实习了三个月也是用的node开发对node已经比较熟悉了于是就用它做了后台前端用最近比较火的vue.js做单页应用。当时还想着负载均衡啥的就没有用传统的sessioncookie机制转而用jwt做的基于token的身份认证同时后台接口也是类Restful风格的因为纯正的Rest接口太难设计了
总的来说后台用了以下技术和框架:
&emsp;&emsp;总的来说后台用了以下技术和框架:
- 语言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、系统基础框架搭建
&emsp;&emsp;本系统是前后端分离的,下面分别介绍前后端的实现基础。
### 1、后台
&emsp;&emsp;一个web后台最重要的无非那么几个部分路由权限验证数据持久化。
#### a、路由
KOA作为一个web框架其实它本身并没有提供路由功能需要配合使用koa-router来实现路由koa-router以类似下面这样的风格来进行路由
&emsp;&emsp;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、权限验证
&emsp;&emsp;权限管理是一个系统最重要的部分之一,目前主流的方式为**基于角色的权限管理** 一个用户对应多个角色每个角色对应多个权限本系统中每个用户对应一个身份每个身份对应多个角色。我们的系统如何实现的呢先从登录开始说起本系统抛弃了传统的cookiesession模式使用json web tokenJWT来做身份认证用户登录后返回一个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、数据持久化
&emsp;&emsp;本系统中使用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、前端
&emsp;&emsp;前端使用vue-cli构建vue项目主要用到了vue-router,element-ui,axios这三个组件。
#### a、路由组织
&emsp;&emsp;单页应用需要前端自己组织路由。本系统将路由分成了三个部分公共管理端学生端。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
}
]
})
```
其中的AdminClientPublic分别为各部分的路由以子路由的形式一级级组织。如下所示
```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、请求封装
&emsp;&emsp;前端还有一个比较重要的部分是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);
}
})
})
}
```
&emsp;&emsp;到这里就算是简单介绍完了,,想要更加深入了解的可以去github查看源代码地址如下[https://github.com/FleyX/teach_system](https://github.com/FleyX/teach_system)记得star哦

View File

@ -1,75 +1,75 @@
## 前言 ## 前言
&emsp;&emsp;分布式事务是企业集成中的一个技术难点,也是每一个分布式系统架构中都会涉及到的一个东西,特别是在微服务架构中,几乎是无法避免的。 &emsp;&emsp;分布式事务是企业集成中的一个技术难点,也是每一个分布式系统架构中都会涉及到的一个东西,特别是在微服务架构中,几乎是无法避免的。
##一、从单机事务到分布式 ##一、从单机事务到分布式
###1.数据库事务 ###1.数据库事务
我们都知道数据库事务的四个特性原子性、一致性、隔离性和持久性数据库事务由数据库软件自身来完成。假如数据库在提交事务的时候突然断电数据库可以在日志记录中找到上一次事务操作然后根据当前数据库的情况进行undo回滚或者是redo前滚基于某时刻的完整备份然后执行从该时刻到崩溃时间所有增删改查操作使数据库恢复到崩溃前的状态。在分布式环境中可能遇到的问题就更多了例如机器宕机、网络异常、消息乱序、数据错误、存储数据丢失等等数据库自身并没有比较好的解决方案下文会提到一种数据库支持的解决方法 我们都知道数据库事务的四个特性原子性、一致性、隔离性和持久性数据库事务由数据库软件自身来完成。假如数据库在提交事务的时候突然断电数据库可以在日志记录中找到上一次事务操作然后根据当前数据库的情况进行undo回滚或者是redo前滚基于某时刻的完整备份然后执行从该时刻到崩溃时间所有增删改查操作使数据库恢复到崩溃前的状态。在分布式环境中可能遇到的问题就更多了例如机器宕机、网络异常、消息乱序、数据错误、存储数据丢失等等数据库自身并没有比较好的解决方案下文会提到一种数据库支持的解决方法
###2.分布式理论 ###2.分布式理论
&emsp;&emsp;当单个数据库的性能产生瓶颈的时候我们可能会对数据库进行分区这里分区指的是物理分区分区后不同的库可能就在不同的服务器上了这个时候单个数据库的ACID已经不能适应这种情况了在集群情况下想保证集群的ACID是很困难的即使能够达到效率和性能也会大幅下降而且难以拓展。这就需要一个新的理论**CAP原则**。 &emsp;&emsp;当单个数据库的性能产生瓶颈的时候我们可能会对数据库进行分区这里分区指的是物理分区分区后不同的库可能就在不同的服务器上了这个时候单个数据库的ACID已经不能适应这种情况了在集群情况下想保证集群的ACID是很困难的即使能够达到效率和性能也会大幅下降而且难以拓展。这就需要一个新的理论**CAP原则**。
&emsp;&emsp;CAP是加州大学伯克利分校Eric Brewer教授提出的他指出WEB服务无法同时满足以下3个属性 &emsp;&emsp;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理论采用最终一致性实现较为简单性能也不错。
**优点**:一种非常经典的实现,避免了分布式事务,实现了最终一致性。 **优点**:一种非常经典的实现,避免了分布式事务,实现了最终一致性。
**缺点**:消息表耦合到了业务系统中。 **缺点**:消息表耦合到了业务系统中。

View File

@ -1,45 +1,45 @@
## 一、 数据库事务四大特性ACID ## 一、 数据库事务四大特性ACID
### 1、 原子性Atomicity ### 1、 原子性Atomicity
&emsp;&emsp;原子性是指事务包含的所有操作要么全部执行成功,否则失败回滚,回到未执行事务前的状态。 &emsp;&emsp;原子性是指事务包含的所有操作要么全部执行成功,否则失败回滚,回到未执行事务前的状态。
### 2、一致性Consistency ### 2、一致性Consistency
&emsp;&emsp;一致性是指事务必须使数据库从一个一致性状态变成另一个一致性状态,也就是事务执行前后必须处于一致性状态。 &emsp;&emsp;一致性是指事务必须使数据库从一个一致性状态变成另一个一致性状态,也就是事务执行前后必须处于一致性状态。
&emsp;&emsp;以转账为例假设用户A和B两者的钱加起来是5000那么不管A和B之间如何转账转多少次事务结束后两个用户的钱加起来应该还得是5000这就是事务的一致性。 &emsp;&emsp;以转账为例假设用户A和B两者的钱加起来是5000那么不管A和B之间如何转账转多少次事务结束后两个用户的钱加起来应该还得是5000这就是事务的一致性。
### 3、 隔离性Isolation ### 3、 隔离性Isolation
&emsp;&emsp;隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其它的事务操作所干扰,多个并发事务之间要相互隔离。 &emsp;&emsp;隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其它的事务操作所干扰,多个并发事务之间要相互隔离。
&emsp;&emsp;既要达到这么一种效果对于任意的两个并发的事务T1和T2它们相互之间感觉不到对方正在并发的执行关于事务的隔离性数据库提供了多种隔离级别假设我们不考虑隔离性会发生什么问题呢 &emsp;&emsp;既要达到这么一种效果对于任意的两个并发的事务T1和T2它们相互之间感觉不到对方正在并发的执行关于事务的隔离性数据库提供了多种隔离级别假设我们不考虑隔离性会发生什么问题呢
- 脏读 - 脏读
在一个事务中对数据做了修改,但还未提交,这是其他人读取这个数据,得到的是修改后的值,一旦事务回滚了,再次读取时发现值又变了。 在一个事务中对数据做了修改,但还未提交,这是其他人读取这个数据,得到的是修改后的值,一旦事务回滚了,再次读取时发现值又变了。
- 不可重复读 - 不可重复读
不可重复读是在对数据库中的某个数据重复查询时,返回了不同的查询结果,这是由于在查询期间,被另一个事物修改并提交了。和脏读的区别是脏读读取的是另一个事物未提交的脏数据,不可重复读读取的是前一事物已提交的数据。通常情况下不可重复读并不是问题。 不可重复读是在对数据库中的某个数据重复查询时,返回了不同的查询结果,这是由于在查询期间,被另一个事物修改并提交了。和脏读的区别是脏读读取的是另一个事物未提交的脏数据,不可重复读读取的是前一事物已提交的数据。通常情况下不可重复读并不是问题。
- 幻读(虚读) - 幻读(虚读)
幻读是事物非独立执行时发生的一种现象例如事物T1对一个表中所有的行的某个数据设置为了1这时还有一个其他操作向该表中插入了一行数据并且数据不为1如果操作T1的用户查看刚刚修改的数据就会发现还有一行没有修改其实这行是在修改操作完毕后新加入的一条数据。 幻读是事物非独立执行时发生的一种现象例如事物T1对一个表中所有的行的某个数据设置为了1这时还有一个其他操作向该表中插入了一行数据并且数据不为1如果操作T1的用户查看刚刚修改的数据就会发现还有一行没有修改其实这行是在修改操作完毕后新加入的一条数据。
### 4、 持久性 ### 4、 持久性
&emsp;&emsp;持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即使在数据库系统遇到故障的情况下也不会丢失提交事务的操作。 &emsp;&emsp;持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即使在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
&emsp;&emsp;&emsp;例如我们在使用jdbc操作数据库时在提交事务方法成功后就可以认定事务已经被正确提交即使数据发生重大故障操作也会正确完成。 &emsp;&emsp;&emsp;例如我们在使用jdbc操作数据库时在提交事务方法成功后就可以认定事务已经被正确提交即使数据发生重大故障操作也会正确完成。
## 二、 四种隔离级别 ## 二、 四种隔离级别
&emsp;&emsp;Mysql数据库提供了四种数据库的隔离级别 &emsp;&emsp;Mysql数据库提供了四种数据库的隔离级别
1. Serializable(串行化):对表加了读写锁,每次读写都需要获取共享锁,丧失了并发速度,可避免脏读,不可重复读,幻读的发生 1. Serializable(串行化):对表加了读写锁,每次读写都需要获取共享锁,丧失了并发速度,可避免脏读,不可重复读,幻读的发生
2. Repeatable read(可重复读):在同一个事务内的查询都是事务开始时刻一致的,可避免脏读、不可重复读的发生 2. Repeatable read(可重复读):在同一个事务内的查询都是事务开始时刻一致的,可避免脏读、不可重复读的发生
3. Read committed(读已提交):只能读取到已经提交的数据,避免了脏读的发生。 3. Read committed(读已提交):只能读取到已经提交的数据,避免了脏读的发生。
4. Read uncommitted(读未提交):所有都能读取到,啥都无法避免 4. Read uncommitted(读未提交):所有都能读取到,啥都无法避免
&emsp;&emsp;Mysql默认的事务隔离级别是2。 &emsp;&emsp;Mysql默认的事务隔离级别是2。

View File

@ -0,0 +1,57 @@
## 1、wmware设置
&emsp;&emsp;这篇记录下nat网络模式下虚拟机与主机的相互ping通。首先使用wmware建立一个ubuntu虚拟机网络模式选择nat模式。然后点击虚拟网络编辑
![1536153781962](./picFolder/pic1.png)
接下来点击nat设置
![1536153954193](./picFolder/pic2.png)
看到如下:
![pic3](./picFolder/pic3.png)
上面红框是关键记录这个值下面虚拟机设置静态ip要用到。
## 2、window网络设置
&emsp;&emsp;打开网络适配器页面选择VMnet,右键->属性->Internet协议版本 4TCP/IPV4->属性设置ip地址为上面上面网关地址最后一个数改成1比如192.168.128.2就要设置为192.168.128.1同时设置子网掩码为255.255.255.0,默认网关不要填。我的如下:
![pic4](./picFolder/pic4.png)
**如果想让虚拟机能够访问主机需要关闭主机的防火墙**
## 3、ubuntu设置
&emsp;&emsp;编辑/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、验证
&emsp;&emsp;现在虚拟机中`ping 192.168.128.1`可以ping通主机中`ping 192.168.128.129`也可ping通。

BIN
网络/picFolder/pic1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
网络/picFolder/pic2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

BIN
网络/picFolder/pic3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
网络/picFolder/pic4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB