文档章节

Spring +0配置+0注解Autowire Bean对象

cwalet
 cwalet
发布于 2012/08/10 11:34
字数 1786
阅读 2639
收藏 8

说明:我们知道Spring有一个<context:component-scan base-package="" />组件用于实现包搜索并加载bean到Spring容器中(参见:对受管组件的Classpath扫描)。但是这样一来还是要为每个bean对象标注相应的注解,如@Resource 和@Autowired等(参见:基于注解(Annotation-based)的配置

现在的问题是,已经有了一整套的程序,使用Spring-XML的方式配置所有bean,由于bean数量过多,导致配置文件的数量同样很多(超过50个,并在持续增加中),于是想改用component-scan的方式,来自动注册某个包下符合命名规则条件的所有bean,当然,重点是不想对原有代码进行任何修改。不想使用注解去对每一个bean进行标注,从而单纯的组件扫描方式是不可行的。

分析:于是想到了Struts2的Spring插件。

我们知道这个插件有一个奇妙的能力,对于Struts2 Action中引用的bean,只需要有对应的setter方法即可实现对该bean对象的自动注入(如果你使用@Autowired,你甚至无需写setter方法,只需一个私有变量即可),Spring容器透明的完成了这一点。当然,一切都是在下面一个拦截器中完成的。

Struts2采用Spring生成对象时,默认的对象工厂变成了StrutsSpringObjectFactory,这是一个对SpringObjectFactory进行了简单包装的对象工厂,主要实现还是基于SpringObjectFactory。对象的自动注入依靠的是ActionAutowiringInterceptor这个拦截器,Struts2-Spring-plugin配置文件中首次声明并引用了该拦截器。

Object bean = invocation.getAction();
factory.autoWireBean(bean);
拦截器的 before(ActionInvocation invocation)方法中有上面这两行代码,回调 SpringObjectFactory中的 autoWireBean(Object bean)方法,实现对bean对象的自动注入:(代码见StrutsSpringObjectFactory类)
public Object autoWireBean(Object bean, AutowireCapableBeanFactory autoWiringFactory) {
        if (autoWiringFactory != null) {
            autoWiringFactory.autowireBeanProperties(bean,
                    autowireStrategy, false);
        }
        injectApplicationContext(bean);
        injectInternalBeans(bean);
        return bean;
    }

说了许多,还没有进入正题~

上面说到了Struts2的处理方式,但实际上这里用不上。在参考了“了解bean的一生”系列文章后明白了Spring初始、以及实例化bean的过程(流程图如下),推荐查看原文。

因此,基本得出了本文的一个解决方案。

实现:由于项目中良好的命名习惯,所有服务接口的名称均是以“Service”结尾,实现类则是“*ServiceImpl”,而所有对实现类的引用均是以服务名首字母小写的非限定类名的形式,即AbcService - AbcServiceImpl - abcService的对应关系。于是要把所有实现类注册为bean,要做的就很明确了。

1.首先需要一个BeanNameGenerator,并注册到组件扫描器中,以为bean类重命名。代码如下:

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;

public class MyBeanNameGenerator implements BeanNameGenerator {
	@Override
	public String generateBeanName(BeanDefinition bd, BeanDefinitionRegistry bdr) {
		String classFullName = bd.getBeanClassName();
		String beanName = classFullName.substring(classFullName.lastIndexOf(".") + 1);
		beanName = String.valueOf(beanName.charAt(0)).toLowerCase() + beanName.substring(1);
		int end = beanName.lastIndexOf("Impl");
		if (end > 0)
			beanName = beanName.substring(0, end);
		return beanName;
	}
}

2.按照上图的理解,你可以知道一个BeanPostProcessor的实现类在bean对象的实例化过程中有何作用。实际上就是一层接口,用于在Spring实例化bean的前后执行一些附加的自定义动作。简单到输出一行debug信息,复杂可以重定义整个bean,至于是前置方法还是后置方法区别不大。具体栗子如下:

public class MyBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {

	private ApplicationContext ac;
	@Override
	public Object postProcessAfterInitialization(Object bean, String arg1) throws BeansException {
		if (null != bean && bean.getClass().getName().endsWith("Impl")) {
			try {
				BeanInfo bi = Introspector.getBeanInfo(bean.getClass());
				for (PropertyDescriptor pd : bi.getPropertyDescriptors()) {
					String beanName = pd.getName();
					Method m = pd.getWriteMethod();
					if ((!"class".equals(beanName)) && this.ac.containsBean(beanName) && null != m
							&& Modifier.isPublic(m.getModifiers()) && m.getParameterTypes().length == 1) {
						try {
							Object param = this.ac.getBean(beanName);
							if (m.getParameterTypes()[0].isInstance(param))
								m.invoke(bean, param);
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				}
			} catch (IntrospectionException e1) {
				e1.printStackTrace();
			}
			ReflectionUtils.doWithFields(bean.getClass(), new FieldCallback() {
				@Override
				public void doWith(Field f) throws IllegalArgumentException, IllegalAccessException {
					if (!f.isAccessible())
						f.setAccessible(true);
					Object param = MyBeanPostProcessor.this.ac.getBean(f.getName());
					if (f.get(bean) == null && f.getType().isInstance(param))
						f.set(bean, param);
				}
			}, new FieldFilter() {
				@Override
				public boolean matches(Field f) {
					// 只处理private(not static or final)参数不要求为interface
					if (Modifier.isPrivate(f.getModifiers()) && !Modifier.isFinal(f.getModifiers())
							&& !Modifier.isStatic(f.getModifiers()) && ac.containsBean(f.getName()))
						return true;
					return false;
				}
			});
		}
		return bean;
	}
	@Override
	public Object postProcessBeforeInitialization(final Object arg0, String arg1) throws BeansException {
		return arg0;
	}
	@Override
	public void setApplicationContext(ApplicationContext ac) throws BeansException {
		this.ac = ac;
	}
}

上面的代码实现了对bean中引用的其他bean对象的自动注入,根据项目中已经使用的规则,凡是有setter方法、且属性名可以在Spring容器中找到对应名字的bean的属性均会被自动注入(私有、非static及final的属性,属性类型可以是接口亦可以是普通类)。此外,更支持对私有字段(没有公共setter方法)的赋值注入(利用Java反射特性)。

3.最后,只需在Spring配置文件中加上简单的几行:

<bean class="test.MyBeanPostProcessor" />

<context:component-scan base-package="test.beans"
		name-generator="test.MyBeanNameGenerator"
		use-default-filters="false" annotation-config="false">
		<context:include-filter type="regex" expression=".*Impl" />
</context:component-scan>

 然后就可以把之前在配置文件中的绝大部分bean定义给删除了(除了部分需要特殊定义的、或是DataSource等bean对象)。上面的配置中,其中name-generator就是指定命名器的选项,另外,关于annotation-config这个参数,我们知道还有一个类似的配置项:<context:annotation-config />,这个参数则是指定可被扫描到的bean都可以使用annotation配置(即文首所说的autowire注解等),默认是为true,即默认启用。

4.what's more,设置annotation-config会同时引用进其他几个BeanPostProcessor,参见:Spring配置项<context:annotation-config/>解释说明。这就造成一个 BeanPostProcessor的执行顺序和默认覆盖问题。参考AbstractApplicationContext类的invokeBeanFactoryPostProcessors和registerBeanPostProcessors方法,其中有关于order顺序的特殊处理。因为系统定义的BeanPostProcessor实现类都有同时实现了order接口,因此会以一定的顺序执行(具体顺序以OrderComparator类的sort方法执行结果为准),对于没有实现order接口或自定义的BeanPostProcessor实现类,都将在最后执行,此外,也受到Spring配置文件中的配置顺序的影响。关于默认覆盖问题,则同样以配置文件为准。

至此,本文方休。

EOF ? 最后再总结一下本文讲了些什么,好吧,我不想把开头重复一遍,所以我讲一个小插曲:让我们回到MyBeanPostProcessor类中,有一行这样的代码使用JDK的标准bean定义解析器来解析bean对象的get/set方法,

BeanInfo bi = Introspector.getBeanInfo(bean.getClass());
for (PropertyDescriptor pd : bi.getPropertyDescriptors()) {...

然而属性包装器(PropertyDescriptor)似乎是有一个bug(找了很多bean定义都没有关于此的特殊说明)。譬如,对于首字母是小写而次字母是大写的属性(如aBc),则其setter方法是setABc,对这个setter方法进行反向解析时得到的属性名确是ABc,即对应关系成了aBc - setAbc - ABc

-_-  ~很奇妙吧~这样注入对象时就会在Spring容器中找不到对应名字的bean。

Finaly.本文还参考了以下链接中的内容:

Spring BeanPostProcessor类 (在Spring实例化bean的前后执行一些附加操作)

Spring开闭原则的表现-BeanPostProcessor扩展点系列文章,

Spring英文档中关于BeanPostProcessor的部分:http://sina.lt/d5b

© 著作权归作者所有

共有 人打赏支持
cwalet
粉丝 44
博文 111
码字总数 87663
作品 0
其他
私信 提问
Spring中依赖注入的四种方式

在Spring容器中为一个bean配置依赖注入有三种方式: · 使用属性的setter方法注入 这是最常用的方式; · 使用构造器注入; · 使用Filed注入(用于注解方式). 使用属性的setter方法注入 首...

Zero零_度
2015/04/07
0
0
Spring依赖注入的方式

Spring通过property()和构造函数()对bean的依赖注入进行配置. 对String以及基本类型的注入:通过name/value的形式,如果是数字类型,boolean类型,value指定的值会被自动转换成期望的类型. 与之类...

晨曦之光
2012/04/25
1K
0
Spring 3.0 基于 Annotation 的依赖注入实现

使用 @Repository、@Service、@Controller 和 @Component 将类标识为 Bean Spring 自 2.0 版本开始,陆续引入了一些注解用于简化 Spring 的开发。@Repository 注解便属于最先引入的一批,它用...

宝贝-凤
2013/10/23
0
0
Spring学习笔记1——基础知识

1.在java开发领域,Spring相对于EJB来说是一种轻量级的,非侵入性的Java开发框架,曾经有两本很畅销的书《Expert one-on-one J2EE Design and Development》和《Expert one-on-one J2EE deve...

李长春
2011/10/09
0
0
详解 Spring 3.0 基于 Annotation 的依赖注入实现

作者:张 建平, 项目经理, iSoftStone Co.,Ltd 简介: Spring 的依赖配置方式与 Spring 框架的内核自身是松耦合设计的。然而,直到 Spring 3.0 以前,使用 XML 进行依赖配置几乎是唯一的选择...

红薯
2010/05/21
1K
5

没有更多内容

加载失败,请刷新页面

加载更多

tomcat编译超过64k大小的jsp文件报错原因

  今天遇到一个问题,首先是在tomcat中间件上跑的web项目,一个jsp文件,因为代码行数实在是太多了,更新了几个版本之后编译报错了,页面打开都是报500的错误,500的报错,知道http协议返回...

SEOwhywhy
16分钟前
0
0
flutter http 请求客户端

1、pubspec文件管理Flutter应用程序的assets(资源,如图片、package等)。 在pubspec.yaml中,通过网址“https://pub.dartlang.org/packages/http#-installing-tab-”确认版本号后,将http(0...

渣渣曦
16分钟前
0
0
Django基本命令及moduls举例

一、Django基本命令 1.创建项目 django-admin.py startproject mysite 创建后的项目结构:- mysite - mysite #对整个程序进行配置 - init #导入包专用- settings ...

枫叶云
31分钟前
4
0
zabbix安装

rpm -ivh http://repo.webtatic.com/yum/el6/latest.rpm 安装jdk rpm -ivh (自行在网上下载rpm包) 安装php并修改相应参数 yum -y install php56w php56w-gd php56w-mysqlnd php56w-bcmath......

muoushi
32分钟前
3
0
MySQL自增属性auto_increment_increment和auto_increment_offset

MySQL的系统变量或会话变量auto_increment_increment(自增步长)和auto_increment_offset(自增偏移量)控制着数据表的自增列ID。 mysql> show tables;Empty set (0.00 sec)mysql> CREATE TA......

野雪球
今天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部