我们在实际的 Spring+Struts 的项目中一般都会定义一个 Struts Action 基类。基类中当然是提供一些公共的方法了,用上了 Spring 的话,里面放几个 getBean() 方法就会方便许多。Web 项目多数时候我想还是在 Action 中有经常性的获取 Bean 实例,如果有用在别处的话,也可以借鉴。
在没有泛型支持下,我们从 Spring Context 中获取 Bean 要强型转换类型,如:
SomeClass some = (SomeClass)context.getBean("someClass");
上面对应的方法原型是 Object getBean(String beanName);
从 jdk 1.5 开始有泛型了,可以加一个泛型,省却了转型步骤,看起来要舒服一些,如:
SomeClass some = getBean("someClass", SomeClass.class);
上面对应的方法原型是 <T> T getBean(String beanName, Class<T> requiredType);
再进一步,像 Struts2、Rails、Grails 那样我们讲求约定优于配置,假如我们在 Spring 配置文件中声明 Bean 时约定用首字母小写的短类名(如 SomeClass 类定义 BeanName 为 someClass) 的话,我们同样只需一个参数就可得到我们想要的 Bean 实例。这时候获取 Bean 实例的方式就如下:
SomeClass some = getBean(SomeClass.class);
上面对应的方未能原型是 <T> T getBean(Class<T> requiredType);
是不让代码更好写了,不用像很多项目中把所有的 Spring 的 BeanName 全定义成常量值,可以保证不会写错了。而且这也很通用,完全有理由在书写 Spring 配置时这样规范 Bean Name 定义,好像也没有太多的理由让一个 Class 在 Spring 中声明两次。而且 Spring 的注解方式也是这么取名的,见类:org.springframework.context.annotation.AnnotationBeanNameGenerator, 给类加个注解 @Component、@Controller、@Repository 或 @Service 时默认就这么取名的。
所以综合起来,我们在 Struts(1 或 2) 的基类 Action 中可以写这么两个实用的 getBean() 方法,完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
package cc.unmi.test.web.action; import java.beans.Introspector; import org.apache.struts2.ServletActionContext; import org.springframework.context.ApplicationContext; import org.springframework.util.ClassUtils; import org.springframework.web.context.support.WebApplicationContextUtils; import com.opensymphony.xwork2.ActionSupport; /** * Two more convenient methods to get Spring-initialized beans * * @author Unmi * */ public class TestBaseAction/* <M> */extends ActionSupport /* implements ModelDriven<M> */{ /** * Convenient method to get Spring-initialized beans * * @param name Bean name * @param requiredType Bean class type * @return Object bean from ApplicationContext */ protected <T> T getBean(String name, Class<T> requiredType) { ApplicationContext ctx = WebApplicationContextUtils .getRequiredWebApplicationContext(ServletActionContext.getServletContext()); return ctx.getBean(name, requiredType); } /** * More convenient method to get Spring-initialized beans with the default bean name * * @param requiredType Bean class type * @return Object bean from ApplicationContext */ protected <T> T getBean(Class<T> requiredType) { //get the default conventional bean name String shortClassName = ClassUtils.getShortName(requiredType); String beanName = Introspector.decapitalize(shortClassName); return getBean(beanName, requiredType); } } |
注释中 /* <M> */ 和 /* implements ModelDriven<M> */ 是 Action 的 ModelDriven 泛型写法。如果来得精悍一点你尽可以只保留第二个方法,需稍加改动,且当然要保证它的前提就是了,即规范的 BeanName。
前面获得 BeanName 的代码你完全可以自己来写,这里两行:
String shortClassName = ClassUtils.getShortName(requiredType);
String beanName = Introspector.decapitalize(shortClassName);
是借用自:org.springframework.context.annotation.AnnotationBeanNameGenerator 的 String buildDefaultBeanName(BeanDefinition definition) 方法里的代码。
改造完原来的基类 Action 之后,忽然又觉得,Spring 本身该有提供 <T> T getBean(Class<T> requiredType) 这个方法的吧。翻了下 API, 果如其然。那就是:org.springframework.beans.factory.BeanFactory 接口中就定义了该方法:
<T> T getBean(Class<T> requiredType) throws BeansException;
那就应该在各种 BeanFactory 的某个实现类中有相应的 getBean(requiredType) 方法实现。我们继续找到一个实现来看看:
org.springframework.beans.factory.support.DefaultListableBeanFactory 类中有:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public <T> T getBean(Class<T> requiredType) throws BeansException { Assert.notNull(requiredType, "Required type must not be null"); String[] beanNames = getBeanNamesForType(requiredType); if (beanNames.length > 1) { ArrayList<String> autowireCandidates = new ArrayList<String>(); for (String beanName : beanNames) { if (getBeanDefinition(beanName).isAutowireCandidate()) { autowireCandidates.add(beanName); } } if (autowireCandidates.size() > 0) { beanNames = autowireCandidates.toArray(new String[autowireCandidates.size()]); } } if (beanNames.length == 1) { return getBean(beanNames[0], requiredType); } else if (beanNames.length == 0 && getParentBeanFactory() != null) { return getParentBeanFactory().getBean(requiredType); } else { throw new NoSuchBeanDefinitionException(requiredType, "expected single bean but found " + beanNames.length + ": " + StringUtils.arrayToCommaDelimitedString(beanNames)); } } |
它的实现原理大概也看出来了,先从 Spring 配置中遍历出所有类型相符的 BeanName,然后定位到确定的 BeanName,再调用一次
<T> T getBean(String beanName, Class<T> requiredType);
方法,显然效率上没有完全依照约定直接通过 BeanName 获取 Bean 实例的效率高
,还是继续走自己的路吧。
但是:有一个问题要提请注意一下了,前面我在 Spring 中定义的 Bean 类未使用接口,对于面向接口的编程就更要保持良好的习惯了。比如 Spring 里这样的配置:
<bean id="userService" autowire="byName"/>
正常获取该 Bean 是:
UserService userService = getBean("userService", UserService.class);
如果按照上面的办法还是:
UserService userService = getBean(UserService.class);
没问题,因为依据传入的 UserService.class,依然可以用同样的规则得到 BeanName 是 userService。如果写成:
UserService userService = getBean(UserServiceImpl.class);
就爱莫能助了,不存在 userServiceImpl 这样的 BeanName,你要是使用 Spring BeanFactory 的
<T> T getBean(Class<T> requiredType)
比如 beanFactory.getBean(UserServiceImpl.class) 没问题,可以得到你想要的 Bean,因为它是判断类型的,子类型当然成立的。但是为何要写成 getBean(UserServiceImpl.class); 呢,就像回到从前也不会写成:
UserService userService = (UserServiceImpl)getBean("userService");
一样,这等于是把接口与实现绑定了。所以再次论证前面定义的那两个 getBean() 方法更强的通用性。
本文链接 https://yanbin.blog/two-useful-getbean/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。