两个非常实用的获取 Spring Bean 的 Struts Action 基类方法
我们在实际的 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() 方法,完整代码如下:
注释中 /* <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 类中有:
它的实现原理大概也看出来了,先从 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's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
在没有泛型支持下,我们从 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() 方法,完整代码如下:
1package cc.unmi.test.web.action;
2
3import java.beans.Introspector;
4
5import org.apache.struts2.ServletActionContext;
6import org.springframework.context.ApplicationContext;
7import org.springframework.util.ClassUtils;
8import org.springframework.web.context.support.WebApplicationContextUtils;
9
10import com.opensymphony.xwork2.ActionSupport;
11
12/**
13 * Two more convenient methods to get Spring-initialized beans
14 *
15 * @author Unmi
16 *
17 */
18public class TestBaseAction/* <M> */extends ActionSupport
19/* implements ModelDriven<M> */{
20
21 /**
22 * Convenient method to get Spring-initialized beans
23 *
24 * @param name Bean name
25 * @param requiredType Bean class type
26 * @return Object bean from ApplicationContext
27 */
28 protected <T> T getBean(String name, Class<T> requiredType) {
29 ApplicationContext ctx = WebApplicationContextUtils
30 .getRequiredWebApplicationContext(ServletActionContext.getServletContext());
31 return ctx.getBean(name, requiredType);
32 }
33
34 /**
35 * More convenient method to get Spring-initialized beans with the default bean name
36 *
37 * @param requiredType Bean class type
38 * @return Object bean from ApplicationContext
39 */
40 protected <T> T getBean(Class<T> requiredType) {
41 //get the default conventional bean name
42 String shortClassName = ClassUtils.getShortName(requiredType);
43 String beanName = Introspector.decapitalize(shortClassName);
44
45 return getBean(beanName, requiredType);
46 }
47}注释中 /* <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 类中有:
1public <T> T getBean(Class<T> requiredType) throws BeansException {
2 Assert.notNull(requiredType, "Required type must not be null");
3 String[] beanNames = getBeanNamesForType(requiredType);
4 if (beanNames.length > 1) {
5 ArrayList<String> autowireCandidates = new ArrayList<String>();
6 for (String beanName : beanNames) {
7 if (getBeanDefinition(beanName).isAutowireCandidate()) {
8 autowireCandidates.add(beanName);
9 }
10 }
11 if (autowireCandidates.size() > 0) {
12 beanNames = autowireCandidates.toArray(new String[autowireCandidates.size()]);
13 }
14 }
15 if (beanNames.length == 1) {
16 return getBean(beanNames[0], requiredType);
17 }
18 else if (beanNames.length == 0 && getParentBeanFactory() != null) {
19 return getParentBeanFactory().getBean(requiredType);
20 }
21 else {
22 throw new NoSuchBeanDefinitionException(requiredType, "expected single bean but found " +
23 beanNames.length + ": " + StringUtils.arrayToCommaDelimitedString(beanNames));
24 }
25}它的实现原理大概也看出来了,先从 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's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。