java冷知識:java bean內省模式以及spring bean的關聯
最近一直整理著搜尋引擎相關的主題,後續也準備多分享一些spring相關的技術。正所謂溫故而知新,java bean內省模式對spring bean的影響非常深,真可謂是青出於藍而勝於藍。
目錄
1. JavaBean Introspector
Java的內省機制其實是基於JavaBean的,那麼,什麼是JavaBean哪?我們可以這樣說,我們在專案中經常用到的Model可以充當JavaBean,其實我對於JavaBean的理解是它要滿足以下幾個特徵:
- 有屬性可以儲存的成員變數
- 有空參構造方法
- 屬性由對應get/set方法。
而內省(Introspector) 是Java語言對JavaBean類屬性
1.1 BeanDescriptor
BeanDescriptor就是bean描述符,原始碼
package java.beans; public class BeanDescriptor extends FeatureDescriptor { private Reference<? extends Class<?>> beanClassRef; private Reference<? extends Class<?>> customizerClassRef; public BeanDescriptor(Class<?> var1) { this(var1, (Class)null); } public BeanDescriptor(Class<?> var1, Class<?> var2) { this.beanClassRef = getWeakReference(var1); this.customizerClassRef = getWeakReference(var2); String var3; for(var3 = var1.getName(); var3.indexOf(46) >= 0; var3 = var3.substring(var3.indexOf(46) + 1)) { ; } this.setName(var3); } public Class<?> getBeanClass() { return this.beanClassRef != null?(Class)this.beanClassRef.get():null; } public Class<?> getCustomizerClass() { return this.customizerClassRef != null?(Class)this.customizerClassRef.get():null; } BeanDescriptor(BeanDescriptor var1) { super(var1); this.beanClassRef = var1.beanClassRef; this.customizerClassRef = var1.customizerClassRef; } void appendTo(StringBuilder var1) { appendTo(var1, "beanClass", this.beanClassRef); appendTo(var1, "customizerClass", this.customizerClassRef); } }
1.2 MethodDescriptor
MethodDescriptor就是方法描述符,
package java.beans;
public class MethodDescriptor extends FeatureDescriptor {
private final MethodRef methodRef = new MethodRef();
private String[] paramNames;
private List<WeakReference<Class<?>>> params;
private ParameterDescriptor parameterDescriptors[];
/**
* Constructs a <code>MethodDescriptor</code> from a
* <code>Method</code>.
*
* @param method The low-level method information.
*/
public MethodDescriptor(Method method) {
this(method, null);
}
/**
* Constructs a <code>MethodDescriptor</code> from a
* <code>Method</code> providing descriptive information for each
* of the method's parameters.
*
* @param method The low-level method information.
* @param parameterDescriptors Descriptive information for each of the
* method's parameters.
*/
public MethodDescriptor(Method method,
ParameterDescriptor parameterDescriptors[]) {
setName(method.getName());
setMethod(method);
this.parameterDescriptors = (parameterDescriptors != null)
? parameterDescriptors.clone()
: null;
}
}
1.3 PropertyDescriptor
PropertyDescriptor就是欄位描述符,
package java.beans;
public class PropertyDescriptor extends FeatureDescriptor {
private Reference<? extends Class<?>> propertyTypeRef;
private final MethodRef readMethodRef = new MethodRef();
private final MethodRef writeMethodRef = new MethodRef();
private Reference<? extends Class<?>> propertyEditorClassRef;
private boolean bound;
private boolean constrained;
// The base name of the method name which will be prefixed with the
// read and write method. If name == "foo" then the baseName is "Foo"
private String baseName;
private String writeMethodName;
private String readMethodName;
/**
* Constructs a PropertyDescriptor for a property that follows
* the standard Java convention by having getFoo and setFoo
* accessor methods. Thus if the argument name is "fred", it will
* assume that the writer method is "setFred" and the reader method
* is "getFred" (or "isFred" for a boolean property). Note that the
* property name should start with a lower case character, which will
* be capitalized in the method names.
*
* @param propertyName The programmatic name of the property.
* @param beanClass The Class object for the target bean. For
* example sun.beans.OurButton.class.
* @exception IntrospectionException if an exception occurs during
* introspection.
*/
public PropertyDescriptor(String propertyName, Class<?> beanClass)
throws IntrospectionException {
this(propertyName, beanClass,
Introspector.IS_PREFIX + NameGenerator.capitalize(propertyName),
Introspector.SET_PREFIX + NameGenerator.capitalize(propertyName));
}
}
1.4 ParameterDescriptor
ParameterDescriptor就是引數描述符
package java.beans;
public class ParameterDescriptor extends FeatureDescriptor {
/**
* Public default constructor.
*/
public ParameterDescriptor() {
}
/**
* Package private dup constructor.
* This must isolate the new object from any changes to the old object.
*/
ParameterDescriptor(ParameterDescriptor old) {
super(old);
}
}
1.5 Introspector
Introspector將JavaBean中的屬性封裝起來進行操作(讀取)。在程式把一個類當做JavaBean來看,就是呼叫Introspector.getBeanInfo()方法,得到BeanInfo物件。那修改怎麼辦?當然是只能反射了(java.lang.reflect)
構造器 | java.lang.reflect.Constructor<T> |
方法 | java.lang.reflect.Method |
欄位 | java.lang.reflect.Field |
@Test
public void go_getDescriptor() throws IntrospectionException {
BeanInfo beanInfo = Introspector.getBeanInfo(User.class);
System.out.println(beanInfo.getBeanDescriptor());
System.out.println(Arrays.toString(beanInfo.getMethodDescriptors()));
System.out.println(Arrays.toString(beanInfo.getPropertyDescriptors()));
}
@Test
public void go_setAge() throws IntrospectionException, InvocationTargetException, IllegalAccessException {
User userInfo = new User();
String age = "age";
Object ageValue = 19;
BeanInfo beanInfo = Introspector.getBeanInfo(User.class);
PropertyDescriptor[] proDescrtptors = beanInfo.getPropertyDescriptors();
if (proDescrtptors != null && proDescrtptors.length > 0) {
for (PropertyDescriptor propDesc : proDescrtptors) {
if (propDesc.getName().equals(age)) {
Method methodSetUserName = propDesc.getWriteMethod();//很重要的原則
methodSetUserName.invoke(userInfo,ageValue);
Method methodGetUserName = propDesc.getReadMethod();
System.out.println(methodGetUserName.invoke(userInfo)); //output:19
break;
}
}
}
}
go_getDescriptor的輸出:
java.beans.BeanDescriptor[name=BeanInfo_test$User; beanClass=class com.example.introspector.BeanInfo_test$User]
[java.beans.MethodDescriptor[name=getClass; method=public final native java.lang.Class java.lang.Object.getClass()], java.beans.MethodDescriptor[name=getUserName; method=public java.lang.String com.example.introspector.BeanInfo_test$User.getUserName()], java.beans.MethodDescriptor[name=setAge; method=public void com.example.introspector.BeanInfo_test$User.setAge(int)], java.beans.MethodDescriptor[name=getAge; method=public int com.example.introspector.BeanInfo_test$User.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=setUserName; method=public void com.example.introspector.BeanInfo_test$User.setUserName(java.lang.String)], 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 java.lang.Object.toString()]]
[java.beans.PropertyDescriptor[name=age; propertyType=int; readMethod=public int com.example.introspector.BeanInfo_test$User.getAge(); writeMethod=public void com.example.introspector.BeanInfo_test$User.setAge(int)], 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=userName; propertyType=class java.lang.String; readMethod=public java.lang.String com.example.introspector.BeanInfo_test$User.getUserName(); writeMethod=public void com.example.introspector.BeanInfo_test$User.setUserName(java.lang.String)]]
這裡思考個問題:如果ageValue="19",它就是非int,會報錯(java.lang.IllegalArgumentException: argument type mismatch)該怎麼辦?其實是有解決方案的,繼續往下看
1.6 屬性變化監聽
一般事件觸發流程都是這樣的:傳遞一個字串(Text,它叫法更專業,所以型別的原生形態文字型)型別---》》》把這個Text型別轉換為對應的Java資料型別並賦值---》》》事件發生(不同的屬性對應不同的事件)
PropertyChangeEvent,事件
package java.beans;
public class PropertyChangeEvent extends EventObject {
private static final long serialVersionUID = 7042693688939648123L;
/**
* Constructs a new {@code PropertyChangeEvent}.
*
* @param source the bean that fired the event
* @param propertyName the programmatic name of the property that was changed
* @param oldValue the old value of the property
* @param newValue the new value of the property
*
* @throws IllegalArgumentException if {@code source} is {@code null}
*/
public PropertyChangeEvent(Object source, String propertyName,
Object oldValue, Object newValue) {
super(source);
this.propertyName = propertyName;
this.newValue = newValue;
this.oldValue = oldValue;
}
}
PropertyChangeListener,監聽器
package java.beans;
public interface PropertyChangeListener extends java.util.EventListener {
/**
* This method gets called when a bound property is changed.
* @param evt A PropertyChangeEvent object describing the event source
* and the property that has changed.
*/
void propertyChange(PropertyChangeEvent evt);
}
PropertyEditor,事件源
package java.beans;
public interface PropertyEditor {
void setAsText(String text) throws java.lang.IllegalArgumentException;
Object getValue();
}
public class PropertyEditorSupport implements PropertyEditor {
public void setAsText(String text) throws java.lang.IllegalArgumentException {
if (value instanceof String) {
setValue(text);
return;
}
throw new java.lang.IllegalArgumentException(text);
}
public void setValue(Object value) {
this.value = value;
firePropertyChange();
}
public void firePropertyChange() {
java.util.Vector<PropertyChangeListener> targets;
synchronized (this) {
if (listeners == null) {
return;
}
targets = unsafeClone(listeners);
}
// Tell our listeners that "everything" has changed.
PropertyChangeEvent evt = new PropertyChangeEvent(source, null, null, null);
for (int i = 0; i < targets.size(); i++) {
PropertyChangeListener target = targets.elementAt(i);
target.propertyChange(evt);
}
}
}
例項程式碼
Test
public void go_setAgeToListener() throws IntrospectionException {
User userInfo = new User();
String age = "age";
String ageValue = "19";
BeanInfo beanInfo = Introspector.getBeanInfo(User.class);
PropertyDescriptor[] proDescrtptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propDesc : proDescrtptors) {
if (propDesc.getName().equals(age)) {
propDesc.setPropertyEditorClass(IntegerEditor.class);//也可以自定義
PropertyEditor propertyEditor = propDesc.createPropertyEditor(userInfo);
propertyEditor.addPropertyChangeListener(x -> {
PropertyEditor source = (PropertyEditor) x.getSource();
Method methodSetUserName = propDesc.getWriteMethod();
try {
System.out.println("newValue:" + source.getValue());
methodSetUserName.invoke(userInfo, source.getValue());
} catch (Exception e) {
e.printStackTrace();
}
});
propertyEditor.setAsText(ageValue);
break;
}
}
}
2. spring bean
2.1 PropertyEditorRegistry
核心屬性修改註冊器,實現類就老厲害了
public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
private static Class<?> pathClass;
private static Class<?> zoneIdClass;
private ConversionService conversionService;
private boolean defaultEditorsActive = false;
private boolean configValueEditorsActive = false;
private Map<Class<?>, PropertyEditor> defaultEditors;
private Map<Class<?>, PropertyEditor> overriddenDefaultEditors;
private Map<Class<?>, PropertyEditor> customEditors;
private Map<String, PropertyEditorRegistrySupport.CustomEditorHolder> customEditorsForPath;
private Map<Class<?>, PropertyEditor> customEditorCache;
public PropertyEditorRegistrySupport() {
}
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
public ConversionService getConversionService() {
return this.conversionService;
}
protected void registerDefaultEditors() {
this.defaultEditorsActive = true;
}
public void useConfigValueEditors() {
this.configValueEditorsActive = true;
}
public void overrideDefaultEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
if(this.overriddenDefaultEditors == null) {
this.overriddenDefaultEditors = new HashMap();
}
this.overriddenDefaultEditors.put(requiredType, propertyEditor);
}
public PropertyEditor getDefaultEditor(Class<?> requiredType) {
if(!this.defaultEditorsActive) {
return null;
} else {
if(this.overriddenDefaultEditors != null) {
PropertyEditor editor = (PropertyEditor)this.overriddenDefaultEditors.get(requiredType);
if(editor != null) {
return editor;
}
}
if(this.defaultEditors == null) {
this.createDefaultEditors();
}
return (PropertyEditor)this.defaultEditors.get(requiredType);
}
}
//這裡處理xml中常規的轉換
private void createDefaultEditors() {
this.defaultEditors = new HashMap(64);
this.defaultEditors.put(Charset.class, new CharsetEditor());
this.defaultEditors.put(Class.class, new ClassEditor());
this.defaultEditors.put(Class[].class, new ClassArrayEditor());
this.defaultEditors.put(Currency.class, new CurrencyEditor());
this.defaultEditors.put(File.class, new FileEditor());
this.defaultEditors.put(InputStream.class, new InputStreamEditor());
this.defaultEditors.put(InputSource.class, new InputSourceEditor());
this.defaultEditors.put(Locale.class, new LocaleEditor());
if(pathClass != null) {
this.defaultEditors.put(pathClass, new PathEditor());
}
this.defaultEditors.put(Pattern.class, new PatternEditor());
this.defaultEditors.put(Properties.class, new PropertiesEditor());
this.defaultEditors.put(Reader.class, new ReaderEditor());
this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());
this.defaultEditors.put(TimeZone.class, new TimeZoneEditor());
this.defaultEditors.put(URI.class, new URIEditor());
this.defaultEditors.put(URL.class, new URLEditor());
this.defaultEditors.put(UUID.class, new UUIDEditor());
if(zoneIdClass != null) {
this.defaultEditors.put(zoneIdClass, new ZoneIdEditor());
}
this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));
this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));
this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));
this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));
this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));
this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());
this.defaultEditors.put(char[].class, new CharArrayPropertyEditor());
this.defaultEditors.put(Character.TYPE, new CharacterEditor(false));
this.defaultEditors.put(Character.class, new CharacterEditor(true));
this.defaultEditors.put(Boolean.TYPE, new CustomBooleanEditor(false));
this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));
this.defaultEditors.put(Byte.TYPE, new CustomNumberEditor(Byte.class, false));
this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true));
this.defaultEditors.put(Short.TYPE, new CustomNumberEditor(Short.class, false));
this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true));
this.defaultEditors.put(Integer.TYPE, new CustomNumberEditor(Integer.class, false));
this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));
this.defaultEditors.put(Long.TYPE, new CustomNumberEditor(Long.class, false));
this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));
this.defaultEditors.put(Float.TYPE, new CustomNumberEditor(Float.class, false));
this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));
this.defaultEditors.put(Double.TYPE, new CustomNumberEditor(Double.class, false));
this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true));
this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));
this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true));
if(this.configValueEditorsActive) {
StringArrayPropertyEditor sae = new StringArrayPropertyEditor();
this.defaultEditors.put(String[].class, sae);
this.defaultEditors.put(short[].class, sae);
this.defaultEditors.put(int[].class, sae);
this.defaultEditors.put(long[].class, sae);
}
}
}
2.2 PropertyEditorRegistrar
它是PropertyEditorRegistry中的defaultEditors的功能拓展
//它本身是null實現,實現類BeanWrapperFieldSetMapper是真正的實現(向PropertyEditorRegistry中新增DataBinder到customEditors,DataBinder就老厲害了(想想springmvc的@RequestBody就懂了))
public class DefaultPropertyEditorRegistrar implements PropertyEditorRegistrar {
private Map<Class<?>, PropertyEditor> customEditors;
/**
* Register the custom editors with the given registry.
*
* @see org.springframework.beans.PropertyEditorRegistrar#registerCustomEditors(org.springframework.beans.PropertyEditorRegistry)
*/
@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
if (this.customEditors != null) {
for (Entry<Class<?>, PropertyEditor> entry : customEditors.entrySet()) {
registry.registerCustomEditor(entry.getKey(), entry.getValue());
}
}
}
/**
* Specify the {@link PropertyEditor custom editors} to register.
*
*
* @param customEditors a map of Class to PropertyEditor (or class name to
* PropertyEditor).
* @see CustomEditorConfigurer#setCustomEditors(Map)
*/
public void setCustomEditors(Map<? extends Object, ? extends PropertyEditor> customEditors) {
this.customEditors = new HashMap<Class<?>, PropertyEditor>();
for (Entry<? extends Object, ? extends PropertyEditor> entry : customEditors.entrySet()) {
Object key = entry.getKey();
Class<?> requiredType = null;
if (key instanceof Class<?>) {
requiredType = (Class<?>) key;
}
else if (key instanceof String) {
String className = (String) key;
requiredType = ClassUtils.resolveClassName(className, getClass().getClassLoader());
}
else {
throw new IllegalArgumentException("Invalid key [" + key
+ "] for custom editor: needs to be Class or String.");
}
PropertyEditor value = entry.getValue();
this.customEditors.put(requiredType, value);
}
}
}
//處理xml中Resource、URL、File...、向PropertyEditorRegistry中新增registry.registerCustomEditor或overrideDefaultEditor(requiredType, editor)
public class ResourceEditorRegistrar implements PropertyEditorRegistrar {
private static Class<?> pathClass;
static {
try {
pathClass = ClassUtils.forName("java.nio.file.Path", ResourceEditorRegistrar.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
// Java 7 Path class not available
pathClass = null;
}
}
private final PropertyResolver propertyResolver;
private final ResourceLoader resourceLoader;
/**
* Create a new ResourceEditorRegistrar for the given {@link ResourceLoader}
* and {@link PropertyResolver}.
* @param resourceLoader the ResourceLoader (or ResourcePatternResolver)
* to create editors for (usually an ApplicationContext)
* @param propertyResolver the PropertyResolver (usually an Environment)
* @see org.springframework.core.env.Environment
* @see org.springframework.core.io.support.ResourcePatternResolver
* @see org.springframework.context.ApplicationContext
*/
public ResourceEditorRegistrar(ResourceLoader resourceLoader, PropertyResolver propertyResolver) {
this.resourceLoader = resourceLoader;
this.propertyResolver = propertyResolver;
}
/**
* Populate the given {@code registry} with the following resource editors:
* ResourceEditor, InputStreamEditor, InputSourceEditor, FileEditor, URLEditor,
* URIEditor, ClassEditor, ClassArrayEditor.
*/
@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
ResourceEditor baseEditor = new ResourceEditor(this.resourceLoader, this.propertyResolver);
doRegisterEditor(registry, Resource.class, baseEditor);
doRegisterEditor(registry, ContextResource.class, baseEditor);
doRegisterEditor(registry, InputStream.class, new InputStreamEditor(baseEditor));
doRegisterEditor(registry, InputSource.class, new InputSourceEditor(baseEditor));
doRegisterEditor(registry, File.class, new FileEditor(baseEditor));
if (pathClass != null) {
doRegisterEditor(registry, pathClass, new PathEditor(baseEditor));
}
doRegisterEditor(registry, Reader.class, new ReaderEditor(baseEditor));
doRegisterEditor(registry, URL.class, new URLEditor(baseEditor));
ClassLoader classLoader = this.resourceLoader.getClassLoader();
doRegisterEditor(registry, URI.class, new URIEditor(classLoader));
doRegisterEditor(registry, Class.class, new ClassEditor(classLoader));
doRegisterEditor(registry, Class[].class, new ClassArrayEditor(classLoader));
if (this.resourceLoader instanceof ResourcePatternResolver) {
doRegisterEditor(registry, Resource[].class,
new ResourceArrayPropertyEditor((ResourcePatternResolver) this.resourceLoader, this.propertyResolver));
}
}
/**
* Override default editor, if possible (since that's what we really mean to do here);
* otherwise register as a custom editor.
*/
private void doRegisterEditor(PropertyEditorRegistry registry, Class<?> requiredType, PropertyEditor editor) {
if (registry instanceof PropertyEditorRegistrySupport) {
((PropertyEditorRegistrySupport) registry).overrideDefaultEditor(requiredType, editor);
}
else {
registry.registerCustomEditor(requiredType, editor);
}
}
}
2.3 CustomEditorConfigurer
可以自定義PropertyEditorRegistry、PropertyEditor,它其實是一個BeanFactoryPostProcessor
public class CustomEditorConfigurer implements BeanFactoryPostProcessor, Ordered {
protected final Log logger = LogFactory.getLog(getClass());
private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered
private PropertyEditorRegistrar[] propertyEditorRegistrars;
private Map<Class<?>, Class<? extends PropertyEditor>> customEditors;
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
public void setPropertyEditorRegistrars(PropertyEditorRegistrar[] propertyEditorRegistrars) {
this.propertyEditorRegistrars = propertyEditorRegistrars;
}
public void setCustomEditors(Map<Class<?>, Class<? extends PropertyEditor>> customEditors) {
this.customEditors = customEditors;
}
//beanFactory中新增PropertyEditorRegistrar和PropertyEditor
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (this.propertyEditorRegistrars != null) {
for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
beanFactory.addPropertyEditorRegistrar(propertyEditorRegistrar);
}
}
if (this.customEditors != null) {
for (Map.Entry<Class<?>, Class<? extends PropertyEditor>> entry : this.customEditors.entrySet()) {
Class<?> requiredType = entry.getKey();
Class<? extends PropertyEditor> propertyEditorClass = entry.getValue();
beanFactory.registerCustomEditor(requiredType, propertyEditorClass);
}
}
}
}
總結,springBean其實是整合拓展了javaBean的內省解決方案,但在它的基礎上做了增強。