#### 每篇一句
> 千古以来要饭的没有要早饭的,知道为什么吗?
#### 相关阅读
[【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor](https://blog.csdn.net/f641385712/article/details/90702928)
[【小家Spring】聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和实现类DirectFieldAccessor的使用](https://blog.csdn.net/f641385712/article/details/95481552)

---
对Spring感兴趣可扫码加入wx群:`Java高工、架构师3群`(文末有二维码)

---
#### 前言
这篇文章需要依赖于对属性访问器`PropertyAccessor`的理解,也就是上篇文章的内容:[【小家Spring】聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和实现类DirectFieldAccessor的使用](https://blog.csdn.net/f641385712/article/details/95481552)

如果说上篇文章所说的`PropertyAccessor`你没有接触过和听过,那么本文即将要说的重点:`BeanWrapper`你应该多少有所耳闻吧~
`BeanWrapper`可以简单的把它理解为:一个方便开发人员**使用字符串**来对`Java Bean`的属性执行get、set操作的工具。关于它的数据转换使用了如下两种机制:

1. `PropertyEditor`:隶属于**Java Bean规范**。`PropertyEditor`只提供了`String Object`的转换。
2. `ConversionService`:Spring自3.0之后提供的替代PropertyEditor的机制(`BeanWrapper`在Spring的第一个版本就存在了~)
> 按照Spring官方文档的说法,当容器内没有注册`ConversionService`的时候,会退回使用`PropertyEditor`机制。言外之意:首选方案是`ConversionService`
> 其实了解的伙伴应该知道,这不是`BeanWrapper`的内容,而是父接口`PropertyAccessor`的内容~
## BeanWrapper
官方解释:Spring低级JavaBeans基础设施的中央接口。通常来说并不直接使用`BeanWrapper`,而是借助`BeanFactory`或者`DataBinder`来一起使用~
```java
//@since 13 April 2001 很清晰的看到,它也是个`PropertyAccessor`属性访问器
public interface BeanWrapper extends ConfigurablePropertyAccessor {

// @since 4.1
void setAutoGrowCollectionLimit(int autoGrowCollectionLimit);
int getAutoGrowCollectionLimit();

Object getWrappedInstance();
Class getWrappedClass();

// 获取属性们的PropertyDescriptor 获取属性们
PropertyDescriptor[] getPropertyDescriptors();
// 获取具体某一个属性~
PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException;
}
```
`BeanWrapper`相当于一个代理器,Spring委托`BeanWrapper`完成Bean属性的填充工作。关于此接口的实现类,简单的说它只有唯一实现类:`BeanWrapperImpl`
### BeanWrapperImpl
它作为`BeanWrapper`接口的默认实现,它足以满足所有的典型应用场景,它会缓存Bean的内省结果而提高效率。
> 在Spring2.5之前,此实现类是非public的,但在2.5之后给public了并且还提供了工厂:`PropertyAccessorFactory`帮助第三方框架能快速获取到一个实例~
```java
public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements BeanWrapper {

// 缓存内省结果~
@Nullable
private CachedIntrospectionResults cachedIntrospectionResults;
// The security context used for invoking the property methods.
@Nullable
private AccessControlContext acc;

// 构造方法都是沿用父类的~
public BeanWrapperImpl() {
this(true);
}
...
private BeanWrapperImpl(Object object, String nestedPath, BeanWrapperImpl parent) {
super(object, nestedPath, parent);
setSecurityContext(parent.acc);
}

// @since 4.3 设置目标对象~~~
public void setBeanInstance(Object object) {
this.wrappedObject = object;
this.rootObject = object;
this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject);
// 设置内省的clazz
setIntrospectionClass(object.getClass());
}

// 复写父类的方法 增加内省逻辑
@Override
public void setWrappedInstance(Object object, @Nullable String nestedPath, @Nullable Object rootObject) {
super.setWrappedInstance(object, nestedPath, rootObject);
setIntrospectionClass(getWrappedClass());
}

// 如果cachedIntrospectionResults它持有的BeanClass并不是传入的clazz 那就清空缓存 重新来~~~
protected void setIntrospectionClass(Class clazz) {
if (this.cachedIntrospectionResults != null && this.cachedIntrospectionResults.getBeanClass() != clazz) {
this.cachedIntrospectionResults = null;
}
}
private CachedIntrospectionResults getCachedIntrospectionResults() {
if (this.cachedIntrospectionResults == null) {
// forClass此方法:生成此clazz的类型结果,并且缓存了起来~~
this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(getWrappedClass());
}
return this.cachedIntrospectionResults;
}
...

// 获取到此属性的处理器。此处是个BeanPropertyHandler 内部类~
@Override
@Nullable
protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) {
PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName);
return (pd != null ? new BeanPropertyHandler(pd) : null);
}
@Override
protected BeanWrapperImpl newNestedPropertyAccessor(Object object, String nestedPath) {
return new BeanWrapperImpl(object, nestedPath, this);
}
@Override
public PropertyDescriptor[] getPropertyDescriptors() {
return getCachedIntrospectionResults().getPropertyDescriptors();
}

// 获取具体某一个属性的PropertyDescriptor
@Override
public PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException {
BeanWrapperImpl nestedBw = (BeanWrapperImpl) getPropertyAccessorForPropertyPath(propertyName);
String finalPath = getFinalPath(nestedBw, propertyName);
PropertyDescriptor pd = nestedBw.getCachedIntrospectionResults().getPropertyDescriptor(finalPath);
if (pd == null) {
throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName, "No property '" + propertyName + "' found");
}
return pd;
}
...

// 此处理器处理的是PropertyDescriptor
private class BeanPropertyHandler extends PropertyHandler {
private final PropertyDescriptor pd;

// 是否可读、可写 都是由PropertyDescriptor 去决定了~
// java.beans.PropertyDescriptor~~
public BeanPropertyHandler(PropertyDescriptor pd) {
super(pd.getPropertyType(), pd.getReadMethod() != null, pd.getWriteMethod() != null);
this.pd = pd;
}
...
@Override
@Nullable
public Object getValue() throws Exception {
...
ReflectionUtils.makeAccessible(readMethod);
return readMethod.invoke(getWrappedInstance(), (Object[]) null);
}
...
}
}
```
从继承体系上,首先我们应该能看出来`BeanWrapperImpl`的三重身份:

1. **Bean包裹器**
2. 属性访问器(PropertyAccessor)
3. 属性编辑器注册表(PropertyEditorRegistry)

从源码中继续分析还能再得出如下两个结论:

1. 它给属性赋值调用的是Method方法,如`readMethod.invoke`和`writeMethod.invoke`
2. 它对Bean的操作,大都委托给`CachedIntrospectionResults`去完成~

因此若想了解它,必然主要是要先了解`java.beans.PropertyDescriptor`和`org.springframework.beans.CachedIntrospectionResults`,首当其冲的自然还有`Java内省`。

---
#### Java内省`Introspector`
首先可以先了解下**JavaBean的概念**:一种特殊的类,主要用于传递数据信息。这种类中的方法主要用于访问私有的字段,且方法名**符合某种命名规则**。如果在两个模块之间传递信息,可以将信息封装进JavaBean中,这种对象称为“值对象”(`Value Object`),或“`VO`”。

**因此JavaBean都有如下几个特征:**
1. 属性都是私有的;
2. 有无参的public构造方法;
3. 对私有属性根据需要提供**公有的**getXxx方法以及setXxx方法;
4. getters必须有返回值没有方法参数;setter值没有返回值,有方法参数;

**符合这些特征的类,被称为JavaBean**;JDK中提供了一套API用来访问某个属性的getter/setter方法,这些API存放在`java.beans`中,这就是`内省(Introspector)`。

###### ==内省和反射的区别==
**反射**:Java反射机制是在运行中,对任意一个类,能够获取得到这个类的所有属性和方法;它针对的是**任意类**
**内省(Introspector)**:是Java语言对JavaBean类属性、事件的处理方法

1. 反射可以操作**各种类的属性**,而内省只是通过反射来操作`JavaBean`的属性
2. 内省设置属性值肯定会调用seter方法,反射可以不用(反射可直接操作属性Field)
3. **反射就像照镜子,然后能看到.class的所有,是客观的事实。内省更像主观的判断:比如看到getName()内省就会认为这个类中有name字段,但事实上并不一定会有name;通过内省可以获取bean的getter/setter**

既然反射比内省比内省强大这么多,**那内省用在什么时候场景呢**?下面给出一个示例来说明它的用武之地:
```java
// 就这样简单几步,就完成了表单到User对象的封装~
public void insertUser(HttpServletRequest request) throws Exception {
User user = new User();

// 遍历:根据字段名去拿值即可(此处省略判空、类型转换等细节,不在本文讨论范围)
PropertyDescriptor[] pds = Introspector.getBeanInfo(User.class).getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
pd.getWriteMethod().invoke(user, request.getParameter(pd.getName()));
}
}
```
通过内省可以很轻松的将form表单的内容填充进对象里面,比反射轻松省力多了。其实像MyBatis这种框架,底层都用到了Java的内省机制。

内省的API主要有Introspector、BeanInfo、PropertyDescriptor等,下面就以他三为例来操作一个JavaBean:
```java
@Getter
@Setter
@ToString
public class Child {

private String name;
private Integer age;

}
```
###### 使用`Introspector` + `BeanInfo`:
```java
public static void main(String[] args) throws IntrospectionException {
BeanInfo beanInfo = Introspector.getBeanInfo(Child.class);

BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
MethodDescriptor[] methodDescriptors = beanInfo.getMethodDescriptors();
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();

// 打印
System.out.println(beanDescriptor);
System.out.println("------------------------------");
Arrays.stream(methodDescriptors).forEach(x -> System.out.println(x));
System.out.println("------------------------------");
Arrays.stream(propertyDescriptors).forEach(x -> System.out.println(x));
System.out.println("------------------------------");
}
```
输入内容如下:
```java
java.beans.BeanDescriptor[name=Child; beanClass=class com.fsx.bean.Child]
------------------------------
java.beans.MethodDescriptor[name=getClass; method=public final native java.lang.Class java.lang.Object.getClass()]
java.beans.MethodDescriptor[name=getName; method=public java.lang.String com.fsx.bean.Child.getName()]
java.beans.MethodDescriptor[name=setAge; method=public void com.fsx.bean.Child.setAge(java.lang.Integer)]
java.beans.MethodDescriptor[name=setName; method=public void com.fsx.bean.Child.setName(java.lang.String)]
java.beans.MethodDescriptor[name=getAge; method=public java.lang.Integer com.fsx.bean.Child.getAge()]
java.beans.MethodDescriptor[name=wait; method=public final void java.lang.Object.wait() throws java.lang.InterruptedException]
java.beans.MethodDescriptor[name=notifyAll; method=public final native void java.lang.Object.notifyAll()]
java.beans.MethodDescriptor[name=notify; method=public final native void java.lang.Object.notify()]
java.beans.MethodDescriptor[name=wait; method=public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException]
java.beans.MethodDescriptor[name=hashCode; method=public native int java.lang.Object.hashCode()]
java.beans.MethodDescriptor[name=wait; method=public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException]
java.beans.MethodDescriptor[name=equals; method=public boolean java.lang.Object.equals(java.lang.Object)]
java.beans.MethodDescriptor[name=toString; method=public java.lang.String com.fsx.bean.Child.toString()]
------------------------------
java.beans.PropertyDescriptor[name=age; propertyType=class java.lang.Integer; readMethod=public java.lang.Integer com.fsx.bean.Child.getAge(); writeMethod=public void com.fsx.bean.Child.setAge(java.lang.Integer)]
java.beans.PropertyDescriptor[name=class; propertyType=class java.lang.Class; readMethod=public final native java.lang.Class java.lang.Object.getClass()]
java.beans.PropertyDescriptor[name=name; propertyType=class java.lang.String; readMethod=public java.lang.String com.fsx.bean.Child.getName(); writeMethod=public void com.fsx.bean.Child.setName(java.lang.String)]
------------------------------
```
可以看到`getMethodDescriptors()`它把父类的`MethodDescriptor`也拿出来了。
而`PropertyDescriptor`中比较特殊的是因为有`getClass()`方法,因此class也算是一个`PropertyDescriptor`,但是它没有`writeMethod`哦~
> 关于`BeanInfo`,Spring在3.1提供了一个类`ExtendedBeanInfo`继承自它实现了功能扩展,并且提供了`BeanInfoFactory`来专门生产它~~~(实现类为:`ExtendedBeanInfoFactory`)

但是如果只想拿某一个属性的话,使用`Introspector`就不是那么方便了,下面介绍更为常用的`PropertyDescriptor`来处理某一个属性~
#### PropertyDescriptor 属性描述器
属性描述符描述了Java bean通过**一对访问器方法导出**的一个属性。上面的示例此处用`PropertyDescriptor`试试:
```java
public static void main(String[] args) throws IntrospectionException {
PropertyDescriptor age = new PropertyDescriptor("age", Child.class);
System.out.println(age.getPropertyType()); //class java.lang.Integer
System.out.println(age.getDisplayName()); //age

// 最重要的两个方法~~~
System.out.println(age.getReadMethod()); //public java.lang.Integer com.fsx.bean.Child.getAge()
System.out.println(age.getWriteMethod()); //public void com.fsx.bean.Child.setAge(java.lang.Integer)
}
```
可以看到它可以实现更加细粒度的控制。将`PropertyDescriptor`类的一些主要方法描述如下:
1. getPropertyType(),获得属性的Class对象;
2. **getReadMethod(),获得用于读取属性值的方法;**
3. **getWriteMethod(),获得用于写入属性值的方法;**
4. setReadMethod(Method readMethod),设置用于读取属性值的方法;
5. setWriteMethod(Method writeMethod),设置用于写入属性值的方法。

---
#### CachedIntrospectionResults
Spring如果需要依赖注入那么就必须依靠Java内省这个特性了,说到**Spring IOC与JDK内省的结合**那么就不得不说一下Spring中的`CachedIntrospectionResults`这个类了。
它是Spring提供的专门用于缓存JavaBean的`PropertyDescriptor`描述信息的类,不能被应用代码直接使用。

它的缓存信息是被静态存储起来的(应用级别),因此对于**同一个类型的**被操作的`JavaBean`并不会都创建一个新的`CachedIntrospectionResults`,因此,这个类使用了工厂模式,使用私有构造器和一个静态的`forClass`工厂方法来获取实例。
```java
public final class CachedIntrospectionResults {

// 它可以通过在spring.properties里设置这个属性,来关闭内省的缓存~~~
public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore";
private static final boolean shouldIntrospectorIgnoreBeaninfoClasses = SpringProperties.getFlag(IGNORE_BEANINFO_PROPERTY_NAME);

// 此处使用了SpringFactoriesLoader这个SPI来加载BeanInfoFactory,唯一实现类是ExtendedBeanInfoFactory
/** Stores the BeanInfoFactory instances. */
private static List beanInfoFactories = SpringFactoriesLoader.loadFactories(
BeanInfoFactory.class, CachedIntrospectionResults.class.getClassLoader());

static final Set acceptedClassLoaders = Collections.newSetFromMap(new ConcurrentHashMap(16));
static final ConcurrentMap, CachedIntrospectionResults> strongClassCache = new ConcurrentHashMap(64);
static final ConcurrentMap, CachedIntrospectionResults> softClassCache = new ConcurrentReferenceHashMap(64);

// 被包裹类的BeanInfo~~~也就是目标类
private final BeanInfo beanInfo;
// 它缓存了被包裹类的所有属性的属性描述器PropertyDescriptor。
private final Map propertyDescriptorCache;

... // 其它的都是静态方法
// 只有它会返回一个实例,此类是单例的设计~ 它保证了每个beanClass都有一个CachedIntrospectionResults 对象,然后被缓存起来~
static CachedIntrospectionResults forClass(Class beanClass) throws BeansException { ... }
}
```
本处理类的核心内容是Java内省`getBeanInfo()`以及`PropertyDescriptor`~注意:为了使此内省缓存生效,有个前提条件请保证了:

- 确保将Spring框架的Jar包和你的应用类使用的是同一个`ClassLoader`加载的,这样在**任何情况下**会允许随着应用的生命周期来清楚缓存。

因此对于web应用来说,Spring建议给web容器注册一个`IntrospectorCleanupListener`监听器来防止多`ClassLoader`布局,这样也可以**有效的利用caching**从而提高效率~

---

监听器的配置形如这样(此处以web.xml里配置为例):
```xml

org.springframework.web.util.IntrospectorCleanupListener

```

> 说明:请保证此监听器配置在第一个位置,比`ContextLoaderListener`还靠前~ 此监听器能有效的防止内存泄漏问题~~~(因为内省的缓存是应用级别的全局缓存,很容易造成泄漏的~)
> 其实流行框架比如`struts, Quartz`等在使用JDK的内省时,存在没有释的内存泄漏问题~
---

##### `DirectFieldAccessFallbackBeanWrapper`
说完了`BeanWrapperImpl`,可以看看它的子类`DirectFieldAccessFallbackBeanWrapper`,他就像`BeanWrapperImpl`和`DirectFieldAccessor`的结合体。它先用`BeanWrapperImpl.getPropertyValue()`,若抛出异常了(毕竟内省不是十分靠谱,哈哈)再用`DirectFieldAccessor`~~~此子类在`JedisClusterConnection`有被使用到过,比较简单没啥太多好说的~

#### PropertyAccessorFactory
`Spring2.5`后提供的快速获取`PropertyAccessor`两个重要实现类的工厂。
```java
public final class PropertyAccessorFactory {
private PropertyAccessorFactory() {
}
// 生产一个BeanWrapperImpl(最为常用)
public static BeanWrapper forBeanPropertyAccess(Object target) {
return new BeanWrapperImpl(target);
}
// 生产一个DirectFieldAccessor
public static ConfigurablePropertyAccessor forDirectFieldAccess(Object target) {
return new DirectFieldAccessor(target);
}

}
```
#### BeanWrapper使用Demo
说了这么多,是时候实战一把了~
```java
// 省略Apple类和Size类,有需要的请参照上篇文章(加上@Getter、@Setter即可

public static void main(String[] args) {
Apple apple = new Apple();

BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(apple);

// ================当作一个普通的PropertyAccessor来使用 默认情况下字段也都必须有初始值才行~===================
// 设置普通属性
beanWrapper.setPropertyValue("color", "红色"); //请保证对应字段有set方法才行,否则抛错:Does the parameter type of the setter match the return type of the getter?
// 设置嵌套属性(注意:此处能够正常work是因为有= new Size(),
// 否则报错:Value of nested property 'size' is null 下同~)
beanWrapper.setPropertyValue("size.height", 10);

// 设置集合/数组属性
beanWrapper.setPropertyValue("arrStr[0]", "arrStr");
beanWrapper.setPropertyValue("arrStr[1]", "arrStr1"); // 注意:虽然初始化时初始化过数组了,但是仍以此处的为准
// =========打印输出
System.out.println(apple); //Apple(color=红色, size=Size(height=10, width=null), arrStr=[arrStr, arrStr1], listStr=[], map={}, listList=[[]], listMap=[{}])

// 当作BeanWrapper使用
PropertyDescriptor[] propertyDescriptors = beanWrapper.getPropertyDescriptors();
PropertyDescriptor color = beanWrapper.getPropertyDescriptor("color");
System.out.println(propertyDescriptors.length); // 8
System.out.println(color); //org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=color]

System.out.println(beanWrapper.getWrappedClass()); //class com.fsx.bean.Apple
System.out.println(beanWrapper.getWrappedInstance()); //Apple(color=红色, size=Size(height=10...
}
```
上面代码能够清晰的表示了通过`BeanWrapper`来操作JavaBean还是非常之简便的。

---

最后,上一张比较丑的结构图,画一画属性编辑器、类型转换器、属性解析器、属性访问器大致的一个关系(此图不喜勿碰):
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190711153603984.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Y2NDEzODU3MTI=,size_16,color_FFFFFF,t_70)
#### 总结
`BeanWrapper`接口,作为Spring内部的一个**核心接口**,正如其名,它是bean的包裹类,即在内部中将会保存该bean的实例,提供其它一些扩展功能。

Spring对Bean的属性存取都是通过`BeanWrapperImpl`实现的,`BeanWrapperImpl`和`Bean`是一对一的关系,`BeanWrapperImpl`通过属性的**读方法**和**写方法**来存取`Bean`属性的。为了更加深刻的了解`BeanWrapper`,下篇文章会深入分析Spring `BeanFactory`对它的应用~
#### 知识交流
==The last:如果觉得本文对你有帮助,不妨点个赞呗。当然分享到你的朋友圈让更多小伙伴看到也是被`作者本人许可的~`==

**若对技术内容感兴趣可以加入wx群交流:`Java高工、架构师3群`。
若群二维码失效,请加wx号:`fsx641385712`(或者扫描下方wx二维码)。并且备注:`"java入群"` 字样,会手动邀请入群**
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190703175020107.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Y2NDEzODU3MTI=,size_16,color_FFFFFF,t_70,pic_center =300x)

【小家Spring】聊聊Spring中的数据绑定 --- BeanWrapper以及内省Introspector和PropertyDescriptor的更多相关文章

  1. 【小家Spring】聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)

    每篇一句 唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~ 相关阅读 [小家Spring]聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和 ...

  2. 【小家Spring】Spring IoC是如何使用BeanWrapper和Java内省结合起来给Bean属性赋值的

    #### 每篇一句 > 具备了技术深度,遇到问题可以快速定位并从根本上解决.有了技术深度之后,学习其它技术可以更快,再深入其它技术也就不会害怕 #### 相关阅读 [[小家Spring]聊聊Sp ...

  3. 【小家Spring】老项目迁移问题:@ImportResource导入的xml配置里的Bean能够使用@PropertySource导入的属性值吗?

    #### 每篇一句 > 大师都是偏执的,偏执才能产生力量,妥协是没有力量的.你对全世界妥协了你就是空气.所以若没有偏见,哪来的大师呢 #### 相关阅读 [[小家Spring]详解Propert ...

  4. Spring官网阅读(十六)Spring中的数据绑定

    文章目录 DataBinder UML类图 使用示例 源码分析 bind方法 doBind方法 applyPropertyValues方法 获取一个属性访问器 通过属性访问器直接set属性值 1.se ...

  5. 从架构演进的角度聊聊Spring Cloud都做了些什么?

    Spring Cloud作为一套微服务治理的框架,几乎考虑到了微服务治理的方方面面,之前也写过一些关于Spring Cloud文章,主要偏重各组件的使用,本次分享主要解答这两个问题:Spring Cl ...

  6. 聊聊 Spring Boot 2.x 那些事儿

    本文目录: 即将的 Spring 2.0 - Spring 2.0 是什么 - 开发环境和 IDE - 使用 Spring Initializr 快速入门 Starter 组件 - Web:REST ...

  7. 聊聊 Spring Boot 2.0 的 WebFlux

    聊聊 Spring Boot 2.0 的 WebFlux## 前言 对照下 Spring Web MVC ,Spring Web MVC 是基于 Servlet API 和 Servlet 容器设计的 ...

  8. [原创]java WEB学习笔记109:Spring学习---spring对JDBC的支持:使用 JdbcTemplate 查询数据库,简化 JDBC 模板查询,在 JDBC 模板中使用具名参数两种实现

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

  9. 在Spring tools suite中使用git 共享项目

    我们都在eclipse 和 myeclipse中使用过cvs 和 svn 版本控制工具进行团队开发,今天我学习了另外一种版本控制工具git,下面我演示如何在Spring tools suite中使用g ...

随机推荐

  1. C++成员函数指针错误用法警示(成员函数指针与高性能的C++委托,三篇),附好多评论

    今天做一个成绩管理系统的并发引擎,用Qt做的,仿照QtConcurrent搞了个模板基类.这里为了隐藏细节,隔离变化,把并发的东西全部包含在模板基类中.子类只需注册需要并发执行的入口函数即可在单独线程 ...

  2. 从零开始学习音视频编程技术(三) 开发环境搭建(Qt4.86手动设置环境,主要就是设置g++和qmake,比较透彻,附下载链接)

    1.先下载安装Qt 我们使用的版本是4.8. 可以自行百度下载也可以从下面的网盘地址下载: Qt库和编译器下载: 链接:http://pan.baidu.com/s/1hrUxLIG 密码:0181 ...

  3. 微信小程序把玩(五)页面生命周期

    原文:微信小程序把玩(五)页面生命周期 这里只要熟悉页面的基本生命周期即可,业务在指定生命周期函数内书写. 以下是官网给出的生命周期函数方法和状态图 上面的生周期函数图对于做Android 或者IOS ...

  4. 利用NPOI生成DOCX文档

    首先安装NPOI控件: Install-Package NPOI 代码: using NPOI.OpenXmlFormats.Wordprocessing; using NPOI.XWPF.UserM ...

  5. MySQL数据库MHA+keepalive实现

    MHA(Master High Availability)目前在MySQL高可用方面是一个相对成熟的解决方案,它由日本DeNA公司youshimaton(现就职于Facebook公司)开发,是一套优秀 ...

  6. How to setup Assigned Access in Windows 10 (Kiosk Mode) 设置分配的访问权限(Kiosk模式)

    Let’s say you’re building some sort of ingenious mechanical contraption to be displayed in public th ...

  7. 通过HTTP Header控制缓存

    我们经常通过缓存技术来加快网站的访问速度,从而提升用户体验.HTTP协议中也规定了一些和缓存相关的Header,来允许浏览器或共享高速缓存缓存资源.这些Header包括: Last-Modified ...

  8. Docker笔记02-日志平台ELK搭建

    OS: Centos7 准备工作: 虚拟机中安装Centos, 搭建Docker环境 ELK简介: 略 文档地址 https://elk-docker.readthedocs.io/ 需要注意的是在B ...

  9. FMX 动态创建 和 销毁(释放free) 对象

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 3 ...

  10. [转]深入Android内存泄露

    深入内存泄露 Android应用的内存泄露,其实就是java虚拟机的堆内存泄漏. 当然,当应用有ndk,jni时,没有及时free,本地堆也会出现内存泄漏. 本文只是针对JVM内存泄漏应用,进行阐述分 ...