双十一过去了,终于可以把这篇博客补上了。
本文将分析BeanWrapperImpl,出现了大量的源码。如果你对BeanWrapper有相当的了解,请略过;如果想稍微了解一下BeanWrapper,可能需要关注一下源码。
--------------------------------------------
学习 Spring 核心是一件比较耗费精力的过程,代码太多,代码的层次也比较深。每次我都是在上午看代码,把一天中精力比较集中的时间段留给了他。在【 Spring IOC - 依赖注入(概述 getBean )】一文中,我们了解了 Spring 依赖注入的大概过程。本次我们要着重说明一下依赖注入中的 Bean 实例化,以及自动注入的实现方式。
1. 本次分享需要解决的几个问题
不得不说的 BeanWrapper
实例化的经过的各层传递
常用的自动注入如何实现
2. 不得不说的 BeanWrapper
BeanWrapper 是 Spring 框架中十分重要的技术支持组件。他的实现类大概有 1 千多行代码,很长,但是功能也是比较内聚的。 BeanWrapper 的主要作用是把所有的 Bean 的属性都可以集中处理。这种集中处理的机制来自于 Java 的反射。通过 Java 的反射,可以把 Bean 的属性抽取出来,然后在通过反射的方式进行赋值。这种方式使 Spring 可以用同一种方式来处理 Bean 的属性。
2.1 BeanWrapper 的实现类图
上图中可以发现默认实现 BeanWrapper 的实现类 BeanWrapperImpl 通过继承 AbstractPropertyAccessor 来增强其属性的赋值功能。这个过程中你可以定义自己的类型处理器。在 org.springframework.beans.propertyeditors 包下面是 Spring 扩展自己定义的类型处理器,如果需要可以扩展自己处理器注册到容器中。这些不是这次讨论的主题,就不详细说明了。
2.2 构建 BeanWrapper 的实例
构建一个 BeanWrapper 实例有很多种方式,可以指定需要实例化的 Bean.class ,可以指定需要包装的 Bean 实例等。下面就 BeanWrapper 的属性的进行说明。
Object object;
被包装的 Bean 实例,如果传递了对应的 class ,通过 BeanUtils 实例化一个
String nestedPath = "";
嵌套路径。例如如果 BeanA 里面包含属性 BeanB ,那么访问 BeanA 的 BeanB 属性的路径是“ beanA.beanB ”。那 BeanB 的嵌套路径就是“ beanA. ”
Object rootObject;
嵌套上级 Bean 实例,如果没有上级,就是自己本身
TypeConverterDelegate typeConverterDelegate;
类型转换器代理实现
CachedIntrospectionResults cachedIntrospectionResults;
缓存 Bean 实例对应的 class 、属性等信息( beanInfo ),在使用的 Bean 的信息,可以从中获取。( Java 内省机制)
Map nestedBeanWrappers;
保存嵌套路径到 BeanWrapper 实例的映射关系保存。
构造方法重要的有两个:
/**
* Create new BeanWrapperImpl for the given object,
* registering a nested path that the object is in.
* @param object object wrapped by this BeanWrapper
* @param nestedPath the nested path of the object
* @param rootObject the root object at the top of the path
*/
public BeanWrapperImpl(Object object, String nestedPath, Object rootObject) {
registerDefaultEditors();
setWrappedInstance(object, nestedPath, rootObject);
}
/**
* Create new BeanWrapperImpl for the given object,
* registering a nested path that the object is in.
* @param object object wrapped by this BeanWrapper
* @param nestedPath the nested path of the object
* @param superBw the containing BeanWrapper (must not be null
)
*/
private BeanWrapperImpl(Object object, String nestedPath, BeanWrapperImpl superBw) {
setWrappedInstance(object, nestedPath, superBw.getWrappedInstance());
setExtractOldValueForEditor(superBw.isExtractOldValueForEditor());
setAutoGrowNestedPaths(superBw.isAutoGrowNestedPaths());
setAutoGrowCollectionLimit(superBw.getAutoGrowCollectionLimit());
setConversionService(superBw.getConversionService());
setSecurityContext(superBw.acc);
}
/**
* Switch the target object, replacing the cached introspection results only
* if the class of the new object is different to that of the replaced object.
* @param object the new target object
* @param nestedPath the nested path of the object
* @param rootObject the root object at the top of the path
*/
public void setWrappedInstance(Object object, String nestedPath, Object rootObject) {
Assert.notNull(object, "Bean object must not be null");
this.object = object;
this.nestedPath = (nestedPath != null ? nestedPath : "");
this.rootObject = (!"".equals(this.nestedPath) ? rootObject : object);
this.nestedBeanWrappers = null;
this.typeConverterDelegate = new TypeConverterDelegate(this, object);
setIntrospectionClass(object.getClass());
}
2.3 使用 BeanWrapper 设置&获取属性值 (包含嵌套)
在了解 BeanWrapper 设置和获取获取属性值前,我们先了解一下 PropertyTokenHolder 。 PropertyTokenHolder 是 BeanWrapperImpl 的内部类,他很简单,定义了属性命名描述。一个能标识 Bean 的唯一名称(可能包含嵌套的路径), Bean 真实名称(不包含嵌套路径),访问集合属性的索引数组。
private static class PropertyTokenHolder {
// 例如 BeanA 中有个 Set 属性,他们的名称是“xxxSet”,
// 我们可以通过“xxxSet[1]”访问这个属性值,需要获得BeanA.xxxSet的一个值
// 权威唯一名称,例如:xxxSet[1]
public String canonicalName;
// 真实名称,例如:xxxSet
public String actualName;
// 访问集合索引,例如:["1"]
public String[] keys;
}
设置属性值,设置属性值有两个重载的方法可供选择。在设置属性时,需要处理嵌套属性的设置。此种需要了解 PropertyTokenHolder ,他是一个内部简单类,定义了嵌套的名称、真实名称、对应的访问 KEY 三个属性。
@Override
public void setPropertyValue(String propertyName, Object value) throws BeansException {
BeanWrapperImpl nestedBw;
try {
// 此方法在下面介绍
nestedBw = getBeanWrapperForPropertyPath(propertyName);
}
catch (NotReadablePropertyException ex) {
throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
"Nested property in path '" + propertyName + "' does not exist", ex);
}
// getFinalPath 的处理逻辑是如果 nestedBw 是自己情况,直接返回传入的 propertyName
PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName));
// 后面介绍这个方法
nestedBw.setPropertyValue(tokens, new PropertyValue(propertyName, value));
}
@Override
public void setPropertyValue(PropertyValue pv) throws BeansException {
PropertyTokenHolder tokens = (PropertyTokenHolder) pv.resolvedTokens;
// 如果无法获取属性标识,重新计算一下
if (tokens == null) {
String propertyName = pv.getName();
BeanWrapperImpl nestedBw;
try {
// 如果是嵌套的 BeanWrapper,获取的是嵌套的实例,否则是自己本身
nestedBw = getBeanWrapperForPropertyPath(propertyName);
}
catch (NotReadablePropertyException ex) {
throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
"Nested property in path '" + propertyName + "' does not exist", ex);
}
tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName));
if (nestedBw == this) {
pv.getOriginalPropertyValue().resolvedTokens = tokens;
}
nestedBw.setPropertyValue(tokens, pv);
}
else {
setPropertyValue(tokens, pv);
}
}
@SuppressWarnings("unchecked")
private void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
String propertyName = tokens.canonicalName;
String actualName = tokens.actualName;
// 如果标识中的 keys 不为空,说明属性是集合方式的,需要通过集合的方式处理
// 并且应该提供对应属性的 getter 方法,因为需要访问对应的集合
if (tokens.keys != null) {
// Apply indexes and map keys: fetch value for all keys but the last one.
PropertyTokenHolder getterTokens = new PropertyTokenHolder();
getterTokens.canonicalName = tokens.canonicalName;
getterTokens.actualName = tokens.actualName;
getterTokens.keys = new String[tokens.keys.length - 1];
System.arraycopy(tokens.keys, 0, getterTokens.keys, 0, tokens.keys.length - 1);
Object propValue;
try {
// 获取对应属性值
propValue = getPropertyValue(getterTokens);
}
catch (NotReadablePropertyException ex) {
throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
"Cannot access indexed value in property referenced " +
"in indexed property path '" + propertyName + "'", ex);
}
// Set value for last key.
String key = tokens.keys[tokens.keys.length - 1];
if (propValue == null) {
// null map value case
if (this.autoGrowNestedPaths) {
// TODO: cleanup, this is pretty hacky
int lastKeyIndex = tokens.canonicalName.lastIndexOf('[');
getterTokens.canonicalName = tokens.canonicalName.substring(0, lastKeyIndex);
propValue = setDefaultValue(getterTokens);
}
else {
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,
"Cannot access indexed value in property referenced " +
"in indexed property path '" + propertyName + "': returned null");
}
}
// 如果属性值是数组类型
if (propValue.getClass().isArray()) {
PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName);
Class requiredType = propValue.getClass().getComponentType();
int arrayIndex = Integer.parseInt(key);
Object oldValue = null;
try {
if (isExtractOldValueForEditor() && arrayIndex < Array.getLength(propValue)) {
oldValue = Array.get(propValue, arrayIndex);
}
Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(),
requiredType, TypeDescriptor.nested(property(pd), tokens.keys.length));
Array.set(propValue, arrayIndex, convertedValue);
}
catch (IndexOutOfBoundsException ex) {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
"Invalid array index in property path '" + propertyName + "'", ex);
}
}
// 如果属性值是List类型
else if (propValue instanceof List) {
PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName);
Class requiredType = GenericCollectionTypeResolver.getCollectionReturnType(
pd.getReadMethod(), tokens.keys.length);
List list = (List) propValue;
int index = Integer.parseInt(key);
Object oldValue = null;
if (isExtractOldValueForEditor() && index < list.size()) {
oldValue = list.get(index);
}
Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(),
requiredType, TypeDescriptor.nested(property(pd), tokens.keys.length));
int size = list.size();
if (index >= size && index < this.autoGrowCollectionLimit) {
for (int i = size; i < index; i++) {
try {
list.add(null);
}
catch (NullPointerException ex) {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
"Cannot set element with index " + index + " in List of size " +
size + ", accessed using property path '" + propertyName +
"': List does not support filling up gaps with null elements");
}
}
list.add(convertedValue);
}
else {
try {
list.set(index, convertedValue);
}
catch (IndexOutOfBoundsException ex) {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
"Invalid list index in property path '" + propertyName + "'", ex);
}
}
}
// 如果属性是Map类型
else if (propValue instanceof Map) {
PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName);
Class mapKeyType = GenericCollectionTypeResolver.getMapKeyReturnType(
pd.getReadMethod(), tokens.keys.length);
Class mapValueType = GenericCollectionTypeResolver.getMapValueReturnType(
pd.getReadMethod(), tokens.keys.length);
Map map = (Map) propValue;
// IMPORTANT: Do not pass full property name in here - property editors
// must not kick in for map keys but rather only for map values.
TypeDescriptor typeDescriptor = (mapKeyType != null ?
TypeDescriptor.valueOf(mapKeyType) : TypeDescriptor.valueOf(Object.class));
Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor);
Object oldValue = null;
if (isExtractOldValueForEditor()) {
oldValue = map.get(convertedMapKey);
}
// Pass full property name and old value in here, since we want full
// conversion ability for map values.
Object convertedMapValue = convertIfNecessary(propertyName, oldValue, pv.getValue(),
mapValueType, TypeDescriptor.nested(property(pd), tokens.keys.length));
map.put(convertedMapKey, convertedMapValue);
}
// 其他是非集合情况,抛出异常
else {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
"Property referenced in indexed property path '" + propertyName +
"' is neither an array nor a List nor a Map; returned value was [" + pv.getValue() + "]");
}
}
// 当标识中Key为空,此时需要直接设定属性的值
else {
// 获取对应的属性描述
PropertyDescriptor pd = pv.resolvedDescriptor;
// 如果无法获取属性描述,或者获得的属性描述对应的 class 不是被包装实例的class时,重新获取
if (pd == null || !pd.getWriteMethod().getDeclaringClass().isInstance(this.object)) {
pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName);
if (pd == null || pd.getWriteMethod() == null) {
if (pv.isOptional()) {
logger.debug("Ignoring optional value for property '" + actualName +
"' - property not found on bean class [" + getRootClass().getName() + "]");
return;
}
else {
PropertyMatches matches = PropertyMatches.forProperty(propertyName, getRootClass());
throw new NotWritablePropertyException(
getRootClass(), this.nestedPath + propertyName,
matches.buildErrorMessage(), matches.getPossibleMatches());
}
}
pv.getOriginalPropertyValue().resolvedDescriptor = pd;
}
Object oldValue = null;
try {
Object originalValue = pv.getValue();
Object valueToApply = originalValue;
// 如果需要类型转换,需要对输入的属性值进行类型转换
if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
if (pv.isConverted()) {
valueToApply = pv.getConvertedValue();
}
else {
// 如果存在 getter,从 getter 中获取对应的属性值,为了某些个性化的编辑器使用
if (isExtractOldValueForEditor() && pd.getReadMethod() != null) {
final Method readMethod = pd.getReadMethod();
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers()) &&
!readMethod.isAccessible()) {
if (System.getSecurityManager()!= null) {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
readMethod.setAccessible(true);
return null;
}
});
}
else {
readMethod.setAccessible(true);
}
}
try {
if (System.getSecurityManager() != null) {
oldValue = AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
return readMethod.invoke(object);
}
}, acc);
}
else {
oldValue = readMethod.invoke(object);
}
}
catch (Exception ex) {
if (ex instanceof PrivilegedActionException) {
ex = ((PrivilegedActionException) ex).getException();
}
if (logger.isDebugEnabled()) {
logger.debug("Could not read previous value of property '" +
this.nestedPath + propertyName + "'", ex);
}
}
}
valueToApply = convertForProperty(propertyName, oldValue, originalValue, pd);
}
pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
}
// 获取 setter 方法
final Method writeMethod = (pd instanceof GenericTypeAwarePropertyDescriptor ?
((GenericTypeAwarePropertyDescriptor) pd).getWriteMethodForActualAccess() :
pd.getWriteMethod());
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()) && !writeMethod.isAccessible()) {
if (System.getSecurityManager()!= null) {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
writeMethod.setAccessible(true);
return null;
}
});
}
else {
writeMethod.setAccessible(true);
}
}
// 设置处理后的值
final Object value = valueToApply;
if (System.getSecurityManager() != null) {
try {
AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
writeMethod.invoke(object, value);
return null;
}
}, acc);
}
catch (PrivilegedActionException ex) {
throw ex.getException();
}
}
else {
writeMethod.invoke(this.object, value);
}
}
catch (TypeMismatchException ex) {
throw ex;
}
catch (InvocationTargetException ex) {
PropertyChangeEvent propertyChangeEvent =
new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue());
if (ex.getTargetException() instanceof ClassCastException) {
throw new TypeMismatchException(propertyChangeEvent, pd.getPropertyType(), ex.getTargetException());
}
else {
throw new MethodInvocationException(propertyChangeEvent, ex.getTargetException());
}
}
catch (Exception ex) {
PropertyChangeEvent pce =
new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue());
throw new MethodInvocationException(pce, ex);
}
}
}
/**
* 通过嵌套的属性路径递归查询 BeanWrapper 实例。
* Recursively navigate to return a BeanWrapper for the nested property path.
* @param propertyPath property property path, which may be nested
* @return a BeanWrapper for the target bean
*/
protected BeanWrapperImpl getBeanWrapperForPropertyPath(String propertyPath) {
// 获取属性解析的位置,例如属性路径为“beanA.beanB”,此时获取的pos=5
// 如果参数中不包含“.”, pos=-1,这里取得的是第一个“.”的位置
int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath);
// Handle nested properties recursively.
// 如果存在嵌套的情况,就需要递归查询,直到找到对应的 BeanWrapper 实例为止。
if (pos > -1) {
// 得到的值为:beanA
String nestedProperty = propertyPath.substring(0, pos);
// 得到的值为:beanB
String nestedPath = propertyPath.substring(pos + 1);
// 通过 beanA 得到嵌套的 BeanWrapper 实例
BeanWrapperImpl nestedBw = getNestedBeanWrapper(nestedProperty);
// 递归查询通过 beanA 得到嵌套的 BeanWrapper 实例的属性值 beanB
return nestedBw.getBeanWrapperForPropertyPath(nestedPath);
}
// 如果不存在嵌套的情况,返回自己本身的引用
else {
return this;
}
}
/**
* 为指定的嵌套属性恢复一个 BeanWrapper 实例,如果没有缓存中找到,重新创建一个。
* Retrieve a BeanWrapper for the given nested property.
* Create a new one if not found in the cache.
* Note: Caching nested BeanWrappers is necessary now,
* to keep registered custom editors for nested properties.
* @param nestedProperty property to create the BeanWrapper for
* @return the BeanWrapper instance, either cached or newly created
*/
private BeanWrapperImpl getNestedBeanWrapper(String nestedProperty) {
// 如嵌套的 BeanWrapper 缓存为空,重建一个
if (this.nestedBeanWrappers == null) {
this.nestedBeanWrappers = new HashMap();
}
// Get value of bean property.
PropertyTokenHolder tokens = getPropertyNameTokens(nestedProperty);
String canonicalName = tokens.canonicalName;
// 获取对应的属性值(留在后面讲解)
Object propertyValue = getPropertyValue(tokens);
if (propertyValue == null) {
if (this.autoGrowNestedPaths) {
propertyValue = setDefaultValue(tokens);
}
else {
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + canonicalName);
}
}
// Lookup cached sub-BeanWrapper, create new one if not found.
BeanWrapperImpl nestedBw = this.nestedBeanWrappers.get(canonicalName);
if (nestedBw == null || nestedBw.getWrappedInstance() != propertyValue) {
if (logger.isTraceEnabled()) {
logger.trace("Creating new nested BeanWrapper for property '" + canonicalName + "'");
}
// 构建一个新的 BeanWrapper 实例,把自己作为嵌套父类,嵌套路径=当前路径+嵌套Bean标识+“.”
nestedBw = newNestedBeanWrapper(propertyValue, this.nestedPath + canonicalName + NESTED_PROPERTY_SEPARATOR);
// Inherit all type-specific PropertyEditors.
// 复制属性注册器到嵌套的 BeanWrapper 中
copyDefaultEditorsTo(nestedBw);
copyCustomEditorsTo(nestedBw, canonicalName);
// 嵌套 BeanWrapper 缓存添加新建的 BeanWrapper 实例
this.nestedBeanWrappers.put(canonicalName, nestedBw);
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Using cached nested BeanWrapper for property '" + canonicalName + "'");
}
}
return nestedBw;
}
/**
* 解析指定的属性名称,并把它赋值到对应的属性标识中(即保存在 PropertyTokenHolder 中)
* Parse the given property name into the corresponding property name tokens.
* @param propertyName the property name to parse
* @return representation of the parsed property tokens
*/
private PropertyTokenHolder getPropertyNameTokens(String propertyName) {
// 例如输入“xxxSet[1]”
PropertyTokenHolder tokens = new PropertyTokenHolder();
String actualName = null;
List keys = new ArrayList(2);
int searchIndex = 0;
while (searchIndex != -1) {
// 从指定的索引位置查询“[”的位置
int keyStart = propertyName.indexOf(PROPERTY_KEY_PREFIX, searchIndex);
searchIndex = -1;
// 如果能找到,找对应的“]”的位置
if (keyStart != -1) {
int keyEnd = propertyName.indexOf(PROPERTY_KEY_SUFFIX, keyStart + PROPERTY_KEY_PREFIX.length());
if (keyEnd != -1) {
if (actualName == null) {
// actualName = xxxSet
actualName = propertyName.substring(0, keyStart);
}
// key = 1
String key = propertyName.substring(keyStart + PROPERTY_KEY_PREFIX.length(), keyEnd);
if ((key.startsWith("'") && key.endsWith("'")) || (key.startsWith("\"") && key.endsWith("\""))) {
key = key.substring(1, key.length() - 1);
}
keys.add(key);
// 重置查询索取到“]”符号后面
searchIndex = keyEnd + PROPERTY_KEY_SUFFIX.length();
}
}
}
// tokens.actualName = xxxSet
tokens.actualName = (actualName != null ? actualName : propertyName);
// tokens.canonicalName = xxxSet
tokens.canonicalName = tokens.actualName;
if (!keys.isEmpty()) {
// tokens.canonicalName = xxxSet[1]
tokens.canonicalName +=
PROPERTY_KEY_PREFIX +
StringUtils.collectionToDelimitedString(keys, PROPERTY_KEY_SUFFIX + PROPERTY_KEY_PREFIX) +
PROPERTY_KEY_SUFFIX;
// tokens.keys = [1]
tokens.keys = StringUtils.toStringArray(keys);
}
// 输入"xxxSet[1]",得到:
// tokens.actualName="xxxSet",tokens.canonicalName="xxxSet[1]",tokens.keys=["1"]
// 输入"xxxSet",得到:
// tokens.actualName="xxxSet",tokens.canonicalName="xxxSet",tokens.keys=null
return tokens;
}
再来说说获取属性值, BeanWrapperImpl 提供了 getPropertyValue(String propertyName) 来处理。下面看看源码,了解实现方式:
@Override
public Object getPropertyValue(String propertyName) throws BeansException {
// 获取 propertyName 对应的 BeanWrapper 实例。如果包含嵌套,获取最后一层嵌套的 BeanWrapper 实例
BeanWrapperImpl nestedBw = getBeanWrapperForPropertyPath(propertyName);
// 构建访问属性的 property 属性名称的标识
PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName));
// 执行获取属性值
return nestedBw.getPropertyValue(tokens);
}
private Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException {
String propertyName = tokens.canonicalName;
String actualName = tokens.actualName;
// 获取对应的属性描述定义
PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName);
// 如果无法获取getter方法,直接异常
if (pd == null || pd.getReadMethod() == null) {
throw new NotReadablePropertyException(getRootClass(), this.nestedPath + propertyName);
}
final Method readMethod = pd.getReadMethod();
try {
// 如果 getter 访问不是 pulbic 类型,并且不是可以获取的,设置成可以访问
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers()) && !readMethod.isAccessible()) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
readMethod.setAccessible(true);
return null;
}
});
}
else {
readMethod.setAccessible(true);
}
}
// 通过 getter 方法获取对应的属性值
Object value;
if (System.getSecurityManager() != null) {
try {
value = AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
return readMethod.invoke(object, (Object[]) null);
}
}, acc);
}
catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
value = readMethod.invoke(object, (Object[]) null);
}
// 如果需要获取具体集合中某项值,指定了keys
if (tokens.keys != null) {
if (value == null) {
// 如果设置了自动生成嵌套的值属性,设置默认值
if (this.autoGrowNestedPaths) {
value = setDefaultValue(tokens.actualName);
}
else {
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,
"Cannot access indexed value of property referenced in indexed " +
"property path '" + propertyName + "': returned null");
}
}
String indexedPropertyName = tokens.actualName;
// apply indexes and map keys
for (int i = 0; i < tokens.keys.length; i++) {
String key = tokens.keys[i];
// 如果属性值为 null,抛出异常
if (value == null) {
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,
"Cannot access indexed value of property referenced in indexed " +
"property path '" + propertyName + "': returned null");
}
// 如果对应的属性是数组类型
else if (value.getClass().isArray()) {
int index = Integer.parseInt(key);
value = growArrayIfNecessary(value, index, indexedPropertyName);
value = Array.get(value, index);
}
// 如果对应的属性是List类型
else if (value instanceof List) {
int index = Integer.parseInt(key);
List list = (List) value;
growCollectionIfNecessary(list, index, indexedPropertyName, pd, i + 1);
value = list.get(index);
}
// 如果对应的属性是Set类型
else if (value instanceof Set) {
// Apply index to Iterator in case of a Set.
Set set = (Set) value;
int index = Integer.parseInt(key);
if (index < 0 || index >= set.size()) {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
"Cannot get element with index " + index + " from Set of size " +
set.size() + ", accessed using property path '" + propertyName + "'");
}
Iterator it = set.iterator();
for (int j = 0; it.hasNext(); j++) {
Object elem = it.next();
if (j == index) {
value = elem;
break;
}
}
}
// 如果对应的属性是Map类型
else if (value instanceof Map) {
Map map = (Map) value;
Class mapKeyType = GenericCollectionTypeResolver.getMapKeyReturnType(pd.getReadMethod(), i + 1);
// IMPORTANT: Do not pass full property name in here - property editors
// must not kick in for map keys but rather only for map values.
TypeDescriptor typeDescriptor = mapKeyType != null ? TypeDescriptor.valueOf(mapKeyType) : TypeDescriptor.valueOf(Object.class);
Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor);
value = map.get(convertedMapKey);
}
// 其他情况,不是集合类型,不支持,抛出异常
else {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
"Property referenced in indexed property path '" + propertyName +
"' is neither an array nor a List nor a Set nor a Map; returned value was [" + value + "]");
}
indexedPropertyName += PROPERTY_KEY_PREFIX + key + PROPERTY_KEY_SUFFIX;
}
}
return value;
}
catch (IndexOutOfBoundsException ex) {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
"Index of out of bounds in property path '" + propertyName + "'", ex);
}
catch (NumberFormatException ex) {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
"Invalid index in property path '" + propertyName + "'", ex);
}
catch (TypeMismatchException ex) {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
"Invalid index in property path '" + propertyName + "'", ex);
}
catch (InvocationTargetException ex) {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
"Getter for property '" + actualName + "' threw exception", ex);
}
catch (Exception ex) {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
"Illegal attempt to get property '" + actualName + "' threw exception", ex);
}
}
通过上面的代码分析,我们了解了 BeanWrapper 是如何获取属性值以及设置属性值的。同时 BeanWrapperImpl 通过定义嵌套路径来解析我们真正的需要的属性,并且智能的来判断我们意图。值得一提的是,对比设置方法和获取方法,我们会发现在设置方法中其并没有对 Set 进行处理,但是我可以通过 Set[index] 来获取对应的值。这是因为 Set 是要去重的,在设置的时候无法判断他的索引位置,但是在获取的时候可以通过遍历来获取对应 index 的值。
2.5 BeanWrapper 中类型转换
上面在分析设置 Bean 属性值时,使用了 convertIfNecessary 方法,这个方法中会判断是否需要类型转换。下面我们来分析这个类型转换。
BeanWrapperImpl 中的类型转换是通过 TypeConverterDelegate 来实现的。构建这个实例发生在我们构建 BeanWrapper 实例的时候。
public BeanWrapperImpl(boolean registerDefaultEditors) {
if (registerDefaultEditors) {
registerDefaultEditors();
}
this.typeConverterDelegate = new TypeConverterDelegate(this);
}
public void setWrappedInstance(Object object, String nestedPath, Object rootObject) {
Assert.notNull(object, "Bean object must not be null");
this.object = object;
this.nestedPath = (nestedPath != null ? nestedPath : "");
this.rootObject = (!"".equals(this.nestedPath) ? rootObject : object);
this.nestedBeanWrappers = null;
this.typeConverterDelegate = new TypeConverterDelegate(this, object);
setIntrospectionClass(object.getClass());
}
TypeConverterDelegate 重要提供的服务是 convertIfNecessary 。这个方法提供了几个重载方法,如下:
/**
* Convert the value to the specified required type.
* @param newValue the proposed new value
* @param requiredType the type we must convert to
* (or null
if not known, for example in case of a collection element)
* @param methodParam the method parameter that is the target of the conversion
* (may be null
)
* @return the new value, possibly the result of type conversion
* @throws IllegalArgumentException if type conversion failed
*/
public T convertIfNecessary(Object newValue, Class requiredType, MethodParameter methodParam)
throws IllegalArgumentException {
return convertIfNecessary(null, null, newValue, requiredType,
(methodParam != null ? new TypeDescriptor(methodParam) : TypeDescriptor.valueOf(requiredType)));
}
/**
* Convert the value to the required type for the specified property.
* @param propertyName name of the property
* @param oldValue the previous value, if available (may be null
)
* @param newValue the proposed new value
* @param requiredType the type we must convert to
* (or null
if not known, for example in case of a collection element)
* @return the new value, possibly the result of type conversion
* @throws IllegalArgumentException if type conversion failed
*/
public T convertIfNecessary(
String propertyName, Object oldValue, Object newValue, Class requiredType)
throws IllegalArgumentException {
return convertIfNecessary(propertyName, oldValue, newValue, requiredType, TypeDescriptor.valueOf(requiredType));
}
/**
* Convert the value to the required type (if necessary from a String),
* for the specified property.
* @param propertyName name of the property
* @param oldValue the previous value, if available (may be null
)
* @param newValue the proposed new value
* @param requiredType the type we must convert to
* (or null
if not known, for example in case of a collection element)
* @param typeDescriptor the descriptor for the target property or field
* @return the new value, possibly the result of type conversion
* @throws IllegalArgumentException if type conversion failed
*/
@SuppressWarnings("unchecked")
public T convertIfNecessary(String propertyName, Object oldValue, Object newValue,
Class requiredType, TypeDescriptor typeDescriptor) throws IllegalArgumentException {
Object convertedValue = newValue;
// Custom editor for this type?
// 查找匹配的 PropertyEditor
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
ConversionFailedException firstAttemptEx = null;
// No custom editor but custom ConversionService specified?
// 如果没有自定义的 PropertyEditor,但是定义了转换服务(conversionService)
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
if (editor == null && conversionService != null && convertedValue != null && typeDescriptor != null) {
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
TypeDescriptor targetTypeDesc = typeDescriptor;
if (conversionService.canConvert(sourceTypeDesc, targetTypeDesc)) {
try {
return (T) conversionService.convert(convertedValue, sourceTypeDesc, targetTypeDesc);
}
// 如果无法转换成功,走下面的默认处理逻辑
catch (ConversionFailedException ex) {
// fallback to default conversion logic below
firstAttemptEx = ex;
}
}
}
// Value not of required type?
// 如果找到了 PropertyEditor,或者提供转换的新值不是指定的转换类型
if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
// 如果目标类型是集合类型,并且对应的值是 string 类型
if (requiredType != null && Collection.class.isAssignableFrom(requiredType) && convertedValue instanceof String) {
TypeDescriptor elementType = typeDescriptor.getElementTypeDescriptor();
// 处理枚举类型集合
if (elementType != null && Enum.class.isAssignableFrom(elementType.getType())) {
convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
}
}
// 如果对应自定义的 PropertyEditer,使用默认的 PropertyEditor
if (editor == null) {
editor = findDefaultEditor(requiredType, typeDescriptor);
}
// 执行类型转换,获取转换后的值
convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
}
// 如果定义了需要转换的类型
if (requiredType != null) {
// Try to apply some standard type conversion rules if appropriate.
// 如果标准的的转换规则存在,尝试去适配标准的转换器
// 如果通过上面的转换(有自定义的转换或者需要转换的的值不是目标类型)能够得到转换后的值
if (convertedValue != null) {
// 如果是数组,通过数组转换返回
// 如果对应的值是 spring,指定的转换数组的内部类型是枚举,用逗号分隔值
if (requiredType.isArray()) {
// Array required -> apply appropriate conversion of elements.
if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) {
convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
}
return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
}
// 如果对应的转换值是集合类型,通过集合转换
else if (convertedValue instanceof Collection) {
// Convert elements to target type, if determined.
convertedValue = convertToTypedCollection(
(Collection) convertedValue, propertyName, requiredType, typeDescriptor);
}
// 如果对应的转换值是Map类型,通过Map转换器转换
else if (convertedValue instanceof Map) {
// Convert keys and values to respective target type, if determined.
convertedValue = convertToTypedMap(
(Map) convertedValue, propertyName, requiredType, typeDescriptor);
}
// 如果对应的转换类型不是数组类型,对应的值是数组类型,取第一个值作为转换值
if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {
convertedValue = Array.get(convertedValue, 0);
}
// 如果对应转换类型是String类型,转化值是标准内部类型,直接转换为string返回
if (String.class.equals(requiredType) && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {
// We can stringify any primitive value...
return (T) convertedValue.toString();
}
// 处理枚举类型,值是枚举对应的名称
else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {
if (!requiredType.isInterface() && !requiredType.isEnum()) {
try {
Constructor strCtor = requiredType.getConstructor(String.class);
return (T) BeanUtils.instantiateClass(strCtor, convertedValue);
}
catch (NoSuchMethodException ex) {
// proceed with field lookup
if (logger.isTraceEnabled()) {
logger.trace("No String constructor found on type [" + requiredType.getName() + "]", ex);
}
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Construction via String failed for type [" + requiredType.getName() + "]", ex);
}
}
}
String trimmedValue = ((String) convertedValue).trim();
if (requiredType.isEnum() && "".equals(trimmedValue)) {
// It's an empty enum identifier: reset the enum value to null.
return null;
}
convertedValue = attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue);
}
}
// 如果转换后的值不是指定的转换类型,异常
if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) {
if (firstAttemptEx != null) {
throw firstAttemptEx;
}
// Definitely doesn't match: throw IllegalArgumentException/IllegalStateException
StringBuilder msg = new StringBuilder();
msg.append("Cannot convert value of type [").append(ClassUtils.getDescriptiveType(newValue));
msg.append("] to required type [").append(ClassUtils.getQualifiedName(requiredType)).append("]");
if (propertyName != null) {
msg.append(" for property '").append(propertyName).append("'");
}
if (editor != null) {
msg.append(": PropertyEditor [").append(editor.getClass().getName()).append(
"] returned inappropriate value of type [").append(
ClassUtils.getDescriptiveType(convertedValue)).append("]");
throw new IllegalArgumentException(msg.toString());
}
else {
msg.append(": no matching editors or conversion strategy found");
throw new IllegalStateException(msg.toString());
}
}
}
if (firstAttemptEx != null) {
if (editor == null && convertedValue == newValue) {
throw firstAttemptEx;
}
logger.debug("Original ConversionService attempt failed - ignored since " +
"PropertyEditor based conversion eventually succeeded", firstAttemptEx);
}
return (T) convertedValue;
}
3. 实例化的各个流程
再次回顾一下上次分享中的 Bean 依赖注入的大概过程:
BeanFactory.getBean → AbstarctBeanFactory.doGetBean → AbstractAutowireCapableBeanFactory.createBean
中间会递归查询依赖,从而完成 Bean 的依赖注入过程。这个过程也是 Bean 实例化过程。
3.1 创建Bean实例
创建 Bean 实例当调用到 AbstractAutowireCapableBeanFactory.createBean 方法后,会转发到 AbstractAutowireCapableBeanFactory.doCreateBean 方法。
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
// Instantiate the bean.
// 初始化 Bean 实例,创建 Bean 实例的 BeanWrapper。
// 如果是单例,需要从容器实例缓存中获取,并删除缓存
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
// Allow post-processors to modify the merged bean definition.
// 给你一次修改 bean 定义的机会,同时你可以来检查bean定义的相关属性
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
mbd.postProcessed = true;
}
}
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
// 可以做代理,改变Bean的引用的地方(SmartInstantiationAwareBeanPostProcessor)
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, new ObjectFactory() {
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
// 组装 Bean,后面会讲到
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
// 二次验证提前暴露引用(SmartInstantiationAwareBeanPostProcessor)的处理是否正确
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set actualDependentBeans = new LinkedHashSet(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
/**
* 通过指定的实例化策略初始化一个新的的 Bean 实例。初始化策略:工厂方法、自动装载构造方法、默认简单构造方法
* Create a new instance for the specified bean, using an appropriate instantiation strategy:
* factory method, constructor autowiring, or simple instantiation.
* @param beanName the name of the bean
* @param mbd the bean definition for the bean
* @param args arguments to use if creating a prototype using explicit arguments to a
* static factory method. It is invalid to use a non-null args value in any other case.
* @return BeanWrapper for the new instance
* @see #instantiateUsingFactoryMethod
* @see #autowireConstructor
* @see #instantiateBean
*/
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {
// Make sure bean class is actually resolved at this point.
Class beanClass = resolveBeanClass(mbd, beanName);
if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
}
// 如果配置了通过工厂方法构造,查找对应的工厂方法来初始化 Bean 实例
if (mbd.getFactoryMethodName() != null) {
return instantiateUsingFactoryMethod(beanName, mbd, args);
}
// Shortcut when re-creating the same bean…
// 如果是重新创建同一个 bean,可以根据 BeanDefinition 中的标识来判断
boolean resolved = false;
boolean autowireNecessary = false;
if (args == null) {
synchronized (mbd.constructorArgumentLock) {
if (mbd.resolvedConstructorOrFactoryMethod != null) {
resolved = true;
autowireNecessary = mbd.constructorArgumentsResolved;
}
}
}
// 如果已经通过构造方法或者的构造工厂处理过 bean 定义
if (resolved) {
if (autowireNecessary) {
return autowireConstructor(beanName, mbd, null, null);
}
else {
return instantiateBean(beanName, mbd);
}
}
// 剩下的就是需要去查询使用那个构造方法,如果能找到对应的构造方法,使用构造方法来创建实例,
// 否则就是使用默认构造方法。例如经常使用的默认无参数构造方法
// Need to determine the constructor…
// 如果定义了对应的 BeanPostProcessor,定义使用某个构造方法,使用之
Constructor[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null ||
mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args);
}
// No special handling: simply use no-arg constructor.
// 默认使用无参数构造方法
return instantiateBean(beanName, mbd);
}
4. 自动注入实现方式
4.1 组装 Bean
你已经厌倦了上面上面长篇的累述,你还没有了解到 BeanWrapper 和组装 Bean 的用途,那么我可以高兴的告诉你,下面我就分析 Bean 的组装,了解 BeanWrapper 的用途。当了解完这个下面这个方法后,我们这次的分享就基本上结束了。
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean:
/**
* 根据 BeanWrapper,来组装 Bean 实例
* Populate the bean instance in the given BeanWrapper with the property values
* from the bean definition.
* @param beanName the name of the bean
* @param mbd the bean definition for the bean
* @param bw BeanWrapper with bean instance
*/
protected void populateBean(String beanName, AbstractBeanDefinition mbd, BeanWrapper bw) {
// 从 BeanDefinition 中获取所有的的 Bean 属性
PropertyValues pvs = mbd.getPropertyValues();
// 如果没有指定 BeanWrapper,但是有 Bean 属性,异常返回,否则跳过处理
if (bw == null) {
if (!pvs.isEmpty()) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
}
else {
// Skip property population phase for null instance.
return;
}
}
// Give any InstantiationAwareBeanPostProcessors the opportunity to modify the
// state of the bean before properties are set. This can be used, for example,
// to support styles of field injection.
boolean continueWithPropertyPopulation = true;
// 给你一次扩展的 BeanPostProcessor 的机会,来在 Bean 实例化后做点儿自己想干的事情
// 回想一下对应的 InstantiationAwareBeanPostProcessor 什么时间会在初始化前调用?
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
continueWithPropertyPopulation = false;
break;
}
}
}
}
if (!continueWithPropertyPopulation) {
return;
}
// 处理根据名称自动转载和根据类型自动装载的过程
if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||
mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
// Add property values based on autowire by name if applicable.
// 根据名称增加属性的装载
if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) {
autowireByName(beanName, mbd, bw, newPvs);
}
// Add property values based on autowire by type if applicable.
// 根据类型增加属性的装载
if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
autowireByType(beanName, mbd, bw, newPvs);
}
pvs = newPvs;
}
boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
boolean needsDepCheck = (mbd.getDependencyCheck() != RootBeanDefinition.DEPENDENCY_CHECK_NONE);
// 在 Bean 发布可以使用前,检查依赖是否完整等
// 如果定义了InstantiationAwareBeanPostProcessor,你可以自定义自己的检查逻辑
// 也可以强制加载自己的依赖,例如注解 Resource 的实现等。
if (hasInstAwareBpps || needsDepCheck) {
PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw);
if (hasInstAwareBpps) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
if (pvs == null) {
return;
}
}
}
}
if (needsDepCheck) {
checkDependencies(beanName, mbd, filteredPds, pvs);
}
}
// 对Bean相关的属性开始发布赋值,可能递归 getBean。
applyPropertyValues(beanName, mbd, bw, pvs);
}
/**
* Apply the given property values, resolving any runtime references
* to other beans in this bean factory. Must use deep copy, so we
* don't permanently modify this property.
* @param beanName the bean name passed for better exception information
* @param mbd the merged bean definition
* @param bw the BeanWrapper wrapping the target object
* @param pvs the new property values
*/
protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
if (pvs == null || pvs.isEmpty()) {
return;
}
MutablePropertyValues mpvs = null;
List original;
if (System.getSecurityManager()!= null) {
if (bw instanceof BeanWrapperImpl) {
((BeanWrapperImpl) bw).setSecurityContext(getAccessControlContext());
}
}
// 获取属性列表
if (pvs instanceof MutablePropertyValues) {
mpvs = (MutablePropertyValues) pvs;
if (mpvs.isConverted()) {
// Shortcut: use the pre-converted values as-is.
try {
bw.setPropertyValues(mpvs);
return;
}
catch (BeansException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Error setting property values", ex);
}
}
original = mpvs.getPropertyValueList();
}
else {
original = Arrays.asList(pvs.getPropertyValues());
}
// 属性类型转化器注册
TypeConverter converter = getCustomTypeConverter();
if (converter == null) {
converter = bw;
}
// 构建对应的属性值处理器
// 属性值处理器,主要完成对应对依赖的属性的构建
BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);
// Create a deep copy, resolving any references for values.
List deepCopy = new ArrayList(original.size());
boolean resolveNecessary = false;
for (PropertyValue pv : original) {
if (pv.isConverted()) {
deepCopy.add(pv);
}
else {
String propertyName = pv.getName();
Object originalValue = pv.getValue();
// 通过属性转换处理器来完成对应属性值的赋值
Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
Object convertedValue = resolvedValue;
boolean convertible = bw.isWritableProperty(propertyName) &&
!PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
if (convertible) {
convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
}
// Possibly store converted value in merged bean definition,
// in order to avoid re-conversion for every created bean instance.
if (resolvedValue == originalValue) {
if (convertible) {
pv.setConvertedValue(convertedValue);
}
deepCopy.add(pv);
}
else if (convertible && originalValue instanceof TypedStringValue &&
!((TypedStringValue) originalValue).isDynamic() &&
!(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) {
pv.setConvertedValue(convertedValue);
deepCopy.add(pv);
}
else {
resolveNecessary = true;
deepCopy.add(new PropertyValue(pv, convertedValue));
}
}
}
if (mpvs != null && !resolveNecessary) {
mpvs.setConverted();
}
// Set our (possibly massaged) deep copy.
try {
// 通过 BeanWrapper 来把所有的属性值赋值
bw.setPropertyValues(new MutablePropertyValues(deepCopy));
}
catch (BeansException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Error setting property values", ex);
}
}
4.2 详解各个自动注入代码
属性名称自动装载
protected void autowireByName(
String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {
String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
for (String propertyName : propertyNames) {
if (containsBean(propertyName)) {
Object bean = getBean(propertyName);
pvs.add(propertyName, bean);
registerDependentBean(propertyName, beanName);
if (logger.isDebugEnabled()) {
logger.debug("Added autowiring by name from bean name '" + beanName +
"' via property '" + propertyName + "' to bean named '" + propertyName + "'");
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Not autowiring property '" + propertyName + "' of bean '" + beanName +
"' by name: no matching bean found");
}
}
}
}
属性类型自动装载
protected void autowireByType(
String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {
TypeConverter converter = getCustomTypeConverter();
if (converter == null) {
converter = bw;
}
Set autowiredBeanNames = new LinkedHashSet(4);
String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
for (String propertyName : propertyNames) {
try {
PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
// Don't try autowiring by type for type Object: never makes sense,
// even if it technically is a unsatisfied, non-simple property.
if (!Object.class.equals(pd.getPropertyType())) {
MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd);
// Do not allow eager init for type matching in case of a prioritized post-processor.
boolean eager = !PriorityOrdered.class.isAssignableFrom(bw.getWrappedClass());
DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(methodParam, eager);
Object autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter);
if (autowiredArgument != null) {
pvs.add(propertyName, autowiredArgument);
}
for (String autowiredBeanName : autowiredBeanNames) {
registerDependentBean(autowiredBeanName, beanName);
if (logger.isDebugEnabled()) {
logger.debug("Autowiring by type from bean name '" + beanName + "' via property '" +
propertyName + "' to bean named '" + autowiredBeanName + "'");
}
}
autowiredBeanNames.clear();
}
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, propertyName, ex);
}
}
}
5. 小结
通过上面的分享,我们了解了 Bean 创建过程。一般情况下,我们会使用构造函数或者 setter 方法来进行依赖。 BeanWrapper 能很好的处理 getter 和 setter 方式;构造方法、工厂方法的形式构建 bean , Spring 也做了很好的处理。但是我们常用“ Resouce ”方式,即没有构造方法、也没有 setter 方法,又该如何处理呢?这个留在下一次分享,关于 BeanPostProcessor 。
你可能感兴趣的:(Spring)
手把手教你将 DeepSeek 集成到 Java 的 Spring Boot 项目中
Java_young
AI java spring boot 开发语言
前面阳仔给大家介绍了如何将deepseek结合进入办公软件中,今天阳仔将详细介绍如何将DeepSeek集成到Java的SpringBoot项目中,让你的项目具备智能交互能力。一、准备工作1.注册DeepSeek账号并获取APIKey访问DeepSeek官网,点击右上角的“开放平台”,注册或登录账号。在左侧菜单中点击“APIKeys”,点击“创建APIKey”,为APIKey命名(如“test”),
【从0到1构建高并发后端系统:Spring Boot + Redis + RabbitMQ实战指南】
小怪兽9699
spring boot
一、架构演进路径图图1:从单体架构到微服务集群的演进过程二、核心优化策略与落地实践1.数据库优化方案分库分表实践://ShardingSphere分片策略配置spring:shardingsphere:datasource:names:ds0,ds1rules:sharding:tables:order:actual-data-nodes:ds$->{0..1}.order_$->{0..3}ta
【从0到1构建实时聊天系统:Spring Boot + Vue3 + WebSocket全栈实战】
小怪兽9699
spring boot websocket 后端
一、项目架构技术栈清单:后端:SpringBoot3.0+WebSocket+STOMP前端:Vue3+Pinia+WebSocketClient部署:Nginx+DockerCompose二、核心功能实现1.WebSocket双向通信//后端配置类@Configuration@EnableWebSocketMessageBrokerpublicclassWebSocketConfigimplem
Spring Boot整合MinIO对象存储教程
嘵奇
提升自己 spring boot 后端 java
精心整理了最新的面试资料和简历模板,有需要的可以自行获取点击前往百度网盘获取点击前往夸克网盘获取以下是一份详细的SpringBoot整合MinIO的教程,包含基础配置和常用文件操作示例:SpringBoot整合MinIO对象存储教程一、MinIO简介MinIO是一款高性能、云原生的分布式对象存储系统,兼容AmazonS3API。适用于存储图片、文档、视频等非结构化数据。二、环境准备安装MinIO服
SpringBoot+Vue前后端分离项目的搭建及简单开发(这次保证看明白~)
m0_67265464
前端 html javascript 开发语言 ecmascript
文章目录概述一、搭建SpringBoot后端1.sql脚本2.新建SpringBoot项目3.MP代码生成4.编写Controller二、搭建Vue前端1.IDEA安装Vue.js插件2.IDEA启动Vue项目3.编写Vue代码4.接收后端数据三、ElementUI使用1.简单的数据展示2.Element-ui更多…参看:https://www.bilibili.com/video/BV13741
必看!计算机毕设答辩高分技巧,让你脱颖而出
源码姑娘
毕业设计
必看!计算机毕设答辩高分技巧,让你脱颖而出一、答辩前的充分准备:技术与内容的“双向打磨”1.吃透项目,技术实现了然于胸计算机毕设的核心在于技术逻辑与系统设计的合理性。答辩前需反复回顾代码实现、架构设计、数据库模型等关键细节,确保能清晰阐述以下问题:技术选型依据:为何选择SpringBoot而非其他框架?所选算法的优势与局限性是什么?系统功能验证:如何通过测试用例或用户反馈验证系统可行性?若涉及机器
SpringCloud——Ribbon实现客户端负载均衡
袁雾头
SpringCloud
SpringCloud——Ribbon实现客户端负载均衡SpringCloudRibbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于NetflixRibbon实现。通过SpringCloud的封装,可以让我们轻松地将面向服务的REST模板请求自动转换成客户端负载均衡的服务调用。客户端负载均衡硬件负载均衡的设备或是软件负载均衡的软件模块都会维护一个可用的服务端清单,通过心跳检测来剔除故障
苍穹外卖(Springboot3实现) day01
十年不明
苍穹外卖学习 spring spring boot java
黑马给的起步代码基础工程版本是springboot2.x我的电脑用的是JDK21springboot3.x所以第一天整了很长时间需要慢慢修改配置环境目录依赖版本更换springboot版本更换mybatis版本更换lombok版本更换数据库依赖更新(很重要)swagger配置问题代码补全依赖版本更换springboot版本更换父工程sky-take-out的pom文件添加spring-boot-s
大模型系列——Spring AI Advisor 指南
猫猫姐
大模型 人工智能 spring java
大模型系列——SpringAIAdvisor指南1、概览AI驱动的应用已成为我们的新现实。我们正在广泛实现各种RAG应用和提示API,并使用LLM创建令人印象深刻的项目。借助SpringAI,我们可以更快、更稳定地完成这些任务。本文将带你了解SpringAIAdvisor这一功能,它可以为我们处理各种常规任务。2、SpringAIAdvisor是什么?Advisors是在AI应用程序中处理请求和响
【SpringBoot】Spring AOP详解
一只爱打拳的程序猿
Spring spring java 后端 spring boot
目录一、什么是SpringAOP二、SpringAOP实现2.1添加SpringAOP依赖2.2定义切面和切点2.3解释AspectJ2.4什么是通知一、什么是SpringAOPAOP(AspectOrientedProgramming)即面向切面编程,是对某一类事件的集中处理。面向切面编程(AOP)是一种编程范式,旨在将横切关注点(如日志记录、事务管理、权限验证等)从业务逻辑代码中分离出来,从而
SpringBoot统一功能处理——统一异常处理
棕豆兔&
spring boot spring java
目录一、异常简单使用二、@ControllerAdvice源码分析一、异常简单使用统一异常处理使用的是@ControllerAdvice+@ExceptionHandler来实现的,@ControllerAdvice表示控制器通知类,@ExceptionHandler是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件。importcom.example.demo.mod
大模型——Spring Boot 整合 Spring AI 实现项目接入ChatGPT
不二人生
大模型 人工智能 大模型
大模型——SpringBoot整合SpringAI实现项目接入ChatGPT随着人工智能技术的快速发展,越来越多的应用程序开始集成人工智能功能,以提供更智能、更个性化的体验。诸如ChatGPT等开放性大型语言模型的出现,使得自然语言处理和对话系统的开发变得更加容易和普及。这些技术已经在社交媒体、客户服务、教育等领域展现出巨大潜力,对于提升用户体验和提高工作效率至关重要。优势在之前,openai已经
Java 拦截器实战:从入门到精通(精选)
我真的不想做程序员
java java spring 开发语言 后端 算法
目录一、拦截器基础概念二、实战案例1.用户认证与授权2.请求日志与性能监控3.国际化与本地化三、注册拦截器四、总结在JavaWeb开发中,拦截器是一种强大的工具,它允许开发者在请求处理的各个环节进行拦截和处理。与过滤器类似,拦截器也能够对请求和响应进行操作,但它工作在SpringMVC框架内部,能够访问Spring管理的Bean,与Spring的依赖注入无缝集成。本文将通过实战案例,帮助您深入理解
spring核心:IOC和AOP详解
sc重新启程
spring java 后端
引言Spring是众多开源java项⽬中的⼀员,基于分层的javaEE应⽤⼀站式轻量级开源框架,目的是简化企业应用程序的开发,主要核⼼是IOC(控制反转/依赖注⼊)与AOP(⾯向切⾯)两⼤技术,实现项⽬在开发过程中的轻松解耦,提⾼项⽬的开发效率。在项⽬中引⼊Spring可以降低组件之间的耦合度,实现软件各层之间的解耦。SpringIOCIOC,InversionofControl,控制反转,指将对
技术解析:格意互联商城系统(多端适配+开源二次开发
西安漫格科技
开源
一、系统概述格意互联商城系统由西安漫格网络科技有限公司独立研发,专注于商城线上销售场景,支持多端适配(APP、小程序、公众号、H5)及二次开发。系统基于JAVA技术栈构建,采用SpringBoot+JPA作为后端框架,前端用户端使用UniApp实现跨平台兼容,管理端基于Vue+ElementUI开发,具备高扩展性与灵活性111。二、技术架构核心技术栈后端服务:SpringBoot+JPA,支持高并
spring笔记
@卡卡-罗特
spring 笔记 java
01spring简介1.Spring是什么?•定义:Spring是Java生态中轻量级、开源的全栈应用开发框架,核心目标是简化企业级Java应用的开发。•核心思想:•IoC(控制反转):将对象的创建和管理交给框架,开发者无需手动new对象。•AOP(面向切面编程):通过代理机制,将日志、事务等横切关注点与业务代码解耦。•模块化设计:自由组合所需功能(如Web、数据访问、安全等)。2.Spring的
Spring MVC笔记
@卡卡-罗特
spring mvc 笔记
01什么是SpringMVCSpringMVC是Spring框架中的一个核心模块,专门用于构建Web应用程序。它基于经典的MVC设计模式(Model-View-Controller),但通过Spring的特性(如依赖注入、注解驱动)大幅简化了开发流程。SpringMVC是什么?本质:一个基于Java的Web框架,帮助开发者快速、结构化地开发动态网站或RESTfulAPI。核心思想:将应用程序拆分为
Springboot中使用@Async注解7大失效场景及解决方案
江-小北
Java面试题 spring boot 数据库 java
前言在SpringBoot中,@Async注解就像一把瑞士军刀,能帮你轻松处理那些耗时的任务,让主线程可以继续忙别的事儿。不过,跟所有强大的工具一样,用不好它也可能出岔子。有时候,你可能因为线程池没配好、异常没处理好,或者Spring代理没生效等原因,导致@Async没按你期望的那样工作。为了避免这些坑,咱们得深入了解下@Async是怎么工作的,还要知道怎么用才能不出问题。接下来,咱们就来聊聊7种
```markdown
伍辰惟
#超越边界:构建优雅的六边形架构(HexagonalArchitecture)hex-arch-kotlin-spring-bootReferenceJVMmultimoduleprojectforareactivemicroserviceandlambdausingahexagonalarchitecture,DDD,Kotlin,SpringBoot,Quarkus,Lambda,Gradle
Spring Boot笔记
@卡卡-罗特
spring boot 笔记 后端
01概要SpringBoot是Java领域最流行的快速开发框架,专为简化Spring应用的初始搭建和开发而设计。一、SpringBoot解决了什么问题?传统Spring痛点•繁琐的XML配置•需要手动管理依赖版本•部署依赖外部Web服务器(如Tomcat)SpringBoot的答案•约定优于配置:自动配置80%的默认设置•内嵌服务器:直接打包成可执行JAR•起步依赖:一键集成常用技术栈(如数据库、
探索未来架构的钥匙:Hex-Arch-Kotlin-Spring-Boot
孟振优Harvester
探索未来架构的钥匙:Hex-Arch-Kotlin-Spring-Boothex-arch-kotlin-spring-bootReferenceJVMmultimoduleprojectforareactivemicroserviceandlambdausingahexagonalarchitecture,DDD,Kotlin,SpringBoot,Quarkus,Lambda,Gradle.项
Java必知必会系列:Web框架与Spring MVC
AI天才研究院
Java实战 编程实践 大数据 人工智能 语言模型 Java Python 架构设计
作者:禅与计算机程序设计艺术1.背景介绍在企业级应用开发中,采用前后端分离模式和RESTfulAPI风格的Web服务架构逐渐成为主流。基于这些技术,前端可以采用JavaScript、HTML、CSS等多种技术进行开发,而后端则可以通过各种Web框架构建出高性能、可扩展性强的应用系统。在本章节中,我将介绍Java最具代表性的Web框架SpringMVC,并结合实际案例,展示如何利用SpringMVC
Gateway网关分布式微服务认证鉴权
NaughtyBo
# Spring security spring cloud gateway
文章目录学习链接微服务认证方案学习链接【OAuth2系列】SpringCloudGateway作为OAuth2Client接入第三方单点登录代码实践实战干货!SpringCloudGateway整合OAuth2.0实现分布式统一认证授权!spring-cloud-gateway-oauth2的github代码地址-已克隆到gitee微服务权限终极解决方案,SpringCloudGateway+Oa
Spring Cloud Gateway微服务网关鉴权
chengqiuming
java java
一网关鉴权1问题当我们在未登录状态下点击“购买课程”按钮时,会显示“未知错误”,查看trade微服务控制台,发现,JWT为空,无法鉴权。2解决方案微服务网关中添加自定义全局过滤器,统一处理需要鉴权的服务。3鉴权逻辑描述当客户端第一次请求服务时,服务端对用户进行信息认证(登录)认证通过,将用户信息进行加密形成token,返回给客户端作为登录凭证以后每次请求,客户端都携带认证的token服务端对tok
Java直通车系列20【Spring MVC】(拦截器)
浪九天
Java直通车 java spring mvc 开发语言
目录拦截器概述拦截器的工作原理拦截器的使用步骤拦截器接口方法解释场景示例1.创建拦截器类2.配置拦截器3.创建控制器4.测试拦截器其他常见场景拦截器概述在SpringMVC中,拦截器(Interceptor)是一种可以在请求处理的不同阶段进行预处理和后处理的组件。它类似于Servlet中的过滤器(Filter),但拦截器是SpringMVC框架特有的,主要用于对控制器方法的调用进行拦截和处理。拦截
Java直通车系列14【Spring MVC】(深入学习 Controller 编写)
浪九天
Java直通车 java spring 后端
目录基本概念编写Controller的步骤和要点1.定义Controller类2.映射请求3.处理请求参数4.调用业务逻辑5.返回响应场景示例1.简单的HelloWorld示例2.处理路径变量和请求参数3.处理表单提交4.处理JSON数据5.异常处理基本概念Controller是SpringMVC架构中的核心组件之一,它负责接收客户端的请求,调用相应的业务逻辑进行处理,并将处理结果返回给客户端。通
Spring中ImportBeanDefinitionRegistrar的使用与示例
t0_54program
spring java rpc 个人开发
在Spring框架中,@Import注解不仅可以导入普通的配置类,还可以处理ImportSelector和DeferredImportSelector的实现。而在本文中,我们将深入探讨@Import注解的另一种用途——ImportBeanDefinitionRegistrar。ImportBeanDefinitionRegistrar接口ImportBeanDefinitionRegistrar是
聊天服务器分布式改造
jforgame
基于Netty的仿QQ聊天室 分布式 QQ 聊天室 spring cloud
目前的聊天室是单节点的,无论是http接口还是socket接口都在同一个进程,无法承受太多人同时在线,容灾性也非常差。因此,一个成熟的IM产品一定是做成分布式的,根据功能分模块,每个模块也使用多个节点并行部署。1.技术选型SpringCloudAlibaba和Netflix都是用于构建分布式系统的工具集,它们在微服务架构中发挥着重要作用,但在多个方面存在差异:发展与维护Netflix:部分核心组件
Java直通车系列15【Spring MVC】(ModelAndView 使用)
浪九天
Java直通车 java spring 后端
目录1.ModelAndView概述2.ModelAndView的主要属性和方法主要属性主要方法3.场景示例示例1:简单的ModelAndView使用示例2:使用ModelAndView处理列表数据示例3:使用ModelAndView处理异常情况1.ModelAndView概述在SpringMVC中,ModelAndView是一个非常重要的类,它用于封装模型数据和视图信息,是控制器(Control
SpringSecurity中文文档(Project+Modules-Simple)
嗨!陌生人
java spring boot
项目模块和依赖(ProjectModulesandDependencies)即使你不用Maven,我们建议您查阅pom.xml文件以了解第三方依赖关系和版本。另一个好主意是检查示例应用程序中包含的库。本节提供了SpringSecurity中模块的参考,以及它们在运行的应用程序中运行所需的附加依赖项。我们不包括仅在构建或测试SpringSecurity本身时使用的依赖项。我们也不包括外部依赖项所需的
Java实现的简单双向Map,支持重复Value
superlxw1234
java 双向map
关键字:Java双向Map、DualHashBidiMap
有个需求,需要根据即时修改Map结构中的Value值,比如,将Map中所有value=V1的记录改成value=V2,key保持不变。
数据量比较大,遍历Map性能太差,这就需要根据Value先找到Key,然后去修改。
即:既要根据Key找Value,又要根据Value
PL/SQL触发器基础及例子
百合不是茶
oracle数据库 触发器 PL/SQL编程
触发器的简介;
触发器的定义就是说某个条件成立的时候,触发器里面所定义的语句就会被自动的执行。因此触发器不需要人为的去调用,也不能调用。触发器和过程函数类似 过程函数必须要调用,
一个表中最多只能有12个触发器类型的,触发器和过程函数相似 触发器不需要调用直接执行,
触发时间:指明触发器何时执行,该值可取:
before:表示在数据库动作之前触发
[时空与探索]穿越时空的一些问题
comsci
问题
我们还没有进行过任何数学形式上的证明,仅仅是一个猜想.....
这个猜想就是; 任何有质量的物体(哪怕只有一微克)都不可能穿越时空,该物体强行穿越时空的时候,物体的质量会与时空粒子产生反应,物体会变成暗物质,也就是说,任何物体穿越时空会变成暗物质..(暗物质就我的理
easy ui datagrid上移下移一行
商人shang
js 上移下移 easyui datagrid
/**
* 向上移动一行
*
* @param dg
* @param row
*/
function moveupRow(dg, row) {
var datagrid = $(dg);
var index = datagrid.datagrid("getRowIndex", row);
if (isFirstRow(dg, row)) {
Java反射
oloz
反射
本人菜鸟,今天恰好有时间,写写博客,总结复习一下java反射方面的知识,欢迎大家探讨交流学习指教
首先看看java中的Class
package demo;
public class ClassTest {
/*先了解java中的Class*/
public static void main(String[] args) {
//任何一个类都
springMVC 使用JSR-303 Validation验证
杨白白
spring mvc
JSR-303是一个数据验证的规范,但是spring并没有对其进行实现,Hibernate Validator是实现了这一规范的,通过此这个实现来讲SpringMVC对JSR-303的支持。
JSR-303的校验是基于注解的,首先要把这些注解标记在需要验证的实体类的属性上或是其对应的get方法上。
登录需要验证类
public class Login {
@NotEmpty
log4j
香水浓
log4j
log4j.rootCategory=DEBUG, STDOUT, DAILYFILE, HTML, DATABASE
#log4j.rootCategory=DEBUG, STDOUT, DAILYFILE, ROLLINGFILE, HTML
#console
log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender
log4
使用ajax和history.pushState无刷新改变页面URL
agevs
jquery 框架 Ajax html5 chrome
表现
如果你使用chrome或者firefox等浏览器访问本博客、github.com、plus.google.com等网站时,细心的你会发现页面之间的点击是通过ajax异步请求的,同时页面的URL发生了了改变。并且能够很好的支持浏览器前进和后退。
是什么有这么强大的功能呢?
HTML5里引用了新的API,history.pushState和history.replaceState,就是通过
centos中文乱码
AILIKES
centos OS ssh
一、CentOS系统访问 g.cn ,发现中文乱码。
于是用以前的方式:yum -y install fonts-chinese
CentOS系统安装后,还是不能显示中文字体。我使用 gedit 编辑源码,其中文注释也为乱码。
后来,终于找到以下方法可以解决,需要两个中文支持的包:
fonts-chinese-3.02-12.
触发器
baalwolf
触发器
触发器(trigger):监视某种情况,并触发某种操作。
触发器创建语法四要素:1.监视地点(table) 2.监视事件(insert/update/delete) 3.触发时间(after/before) 4.触发事件(insert/update/delete)
语法:
create trigger triggerName
after/before
JS正则表达式的i m g
bijian1013
JavaScript 正则表达式
g:表示全局(global)模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止。 i:表示不区分大小写(case-insensitive)模式,即在确定匹配项时忽略模式与字符串的大小写。 m:表示
HTML5模式和Hashbang模式
bijian1013
JavaScript AngularJS Hashbang模式 HTML5模式
我们可以用$locationProvider来配置$location服务(可以采用注入的方式,就像AngularJS中其他所有东西一样)。这里provider的两个参数很有意思,介绍如下。
html5Mode
一个布尔值,标识$location服务是否运行在HTML5模式下。
ha
[Maven学习笔记六]Maven生命周期
bit1129
maven
从mvn test的输出开始说起
当我们在user-core中执行mvn test时,执行的输出如下:
/software/devsoftware/jdk1.7.0_55/bin/java -Dmaven.home=/software/devsoftware/apache-maven-3.2.1 -Dclassworlds.conf=/software/devs
【Hadoop七】基于Yarn的Hadoop Map Reduce容错
bit1129
hadoop
运行于Yarn的Map Reduce作业,可能发生失败的点包括
Task Failure
Application Master Failure
Node Manager Failure
Resource Manager Failure
1. Task Failure
任务执行过程中产生的异常和JVM的意外终止会汇报给Application Master。僵死的任务也会被A
记一次数据推送的异常解决端口解决
ronin47
记一次数据推送的异常解决
需求:从db获取数据然后推送到B
程序开发完成,上jboss,刚开始报了很多错,逐一解决,可最后显示连接不到数据库。机房的同事说可以ping 通。
自已画了个图,逐一排除,把linux 防火墙 和 setenforce 设置最低。
service iptables stop
巧用视错觉-UI更有趣
brotherlamp
UI ui视频 ui教程 ui自学 ui资料
我们每个人在生活中都曾感受过视错觉(optical illusion)的魅力。
视错觉现象是双眼跟我们开的一个玩笑,而我们往往还心甘情愿地接受我们看到的假象。其实不止如此,视觉错现象的背后还有一个重要的科学原理——格式塔原理。
格式塔原理解释了人们如何以视觉方式感觉物体,以及图像的结构,视角,大小等要素是如何影响我们的视觉的。
在下面这篇文章中,我们首先会简单介绍一下格式塔原理中的基本概念,
线段树-poj1177-N个矩形求边长(离散化+扫描线)
bylijinnan
数据结构 算法 线段树
package com.ljn.base;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
/**
* POJ 1177 (线段树+离散化+扫描线),题目链接为http://poj.org/problem?id=1177
HTTP协议详解
chicony
http协议
引言
Scala设计模式
chenchao051
设计模式 scala
Scala设计模式
我的话: 在国外网站上看到一篇文章,里面详细描述了很多设计模式,并且用Java及Scala两种语言描述,清晰的让我们看到各种常规的设计模式,在Scala中是如何在语言特性层面直接支持的。基于文章很nice,我利用今天的空闲时间将其翻译,希望大家能一起学习,讨论。翻译
安装mysql
daizj
mysql 安装
安装mysql
(1)删除linux上已经安装的mysql相关库信息。rpm -e xxxxxxx --nodeps (强制删除)
执行命令rpm -qa |grep mysql 检查是否删除干净
(2)执行命令 rpm -i MySQL-server-5.5.31-2.el
HTTP状态码大全
dcj3sjt126com
http状态码
完整的 HTTP 1.1规范说明书来自于RFC 2616,你可以在http://www.talentdigger.cn/home/link.php?url=d3d3LnJmYy1lZGl0b3Iub3JnLw%3D%3D在线查阅。HTTP 1.1的状态码被标记为新特性,因为许多浏览器只支持 HTTP 1.0。你应只把状态码发送给支持 HTTP 1.1的客户端,支持协议版本可以通过调用request
asihttprequest上传图片
dcj3sjt126com
ASIHTTPRequest
NSURL *url =@"yourURL";
ASIFormDataRequest*currentRequest =[ASIFormDataRequest requestWithURL:url];
[currentRequest setPostFormat:ASIMultipartFormDataPostFormat];[currentRequest se
C语言中,关键字static的作用
e200702084
C++ c C#
在C语言中,关键字static有三个明显的作用:
1)在函数体,局部的static变量。生存期为程序的整个生命周期,(它存活多长时间);作用域却在函数体内(它在什么地方能被访问(空间))。
一个被声明为静态的变量在这一函数被调用过程中维持其值不变。因为它分配在静态存储区,函数调用结束后并不释放单元,但是在其它的作用域的无法访问。当再次调用这个函数时,这个局部的静态变量还存活,而且用在它的访
win7/8使用curl
geeksun
win7
1. WIN7/8下要使用curl,需要下载curl-7.20.0-win64-ssl-sspi.zip和Win64OpenSSL_Light-1_0_2d.exe。 下载地址:
http://curl.haxx.se/download.html 请选择不带SSL的版本,否则还需要安装SSL的支持包 2. 可以给Windows增加c
Creating a Shared Repository; Users Sharing The Repository
hongtoushizi
git
转载自:
http://www.gitguys.com/topics/creating-a-shared-repository-users-sharing-the-repository/ Commands discussed in this section:
git init –bare
git clone
git remote
git pull
git p
Java实现字符串反转的8种或9种方法
Josh_Persistence
异或反转 递归反转 二分交换反转 java字符串反转 栈反转
注:对于第7种使用异或的方式来实现字符串的反转,如果不太看得明白的,可以参照另一篇博客:
http://josh-persistence.iteye.com/blog/2205768
/**
*
*/
package com.wsheng.aggregator.algorithm.string;
import java.util.Stack;
/**
代码实现任意容量倒水问题
home198979
PHP 算法 倒水
形象化设计模式实战 HELLO!架构 redis命令源码解析
倒水问题:有两个杯子,一个A升,一个B升,水有无限多,现要求利用这两杯子装C
Druid datasource
zhb8015
druid
推荐大家使用数据库连接池 DruidDataSource. http://code.alibabatech.com/wiki/display/Druid/DruidDataSource DruidDataSource经过阿里巴巴数百个应用一年多生产环境运行验证,稳定可靠。 它最重要的特点是:监控、扩展和性能。 下载和Maven配置看这里: http
两种启动监听器ApplicationListener和ServletContextListener
spjich
java spring 框架
引言:有时候需要在项目初始化的时候进行一系列工作,比如初始化一个线程池,初始化配置文件,初始化缓存等等,这时候就需要用到启动监听器,下面分别介绍一下两种常用的项目启动监听器
ServletContextListener
特点: 依赖于sevlet容器,需要配置web.xml
使用方法:
public class StartListener implements
JavaScript Rounding Methods of the Math object
何不笑
JavaScript Math
The next group of methods has to do with rounding decimal values into integers. Three methods — Math.ceil(), Math.floor(), and Math.round() — handle rounding in differen