Spring 父类变量注入失败的解决

时间:2021-11-23 08:38:50

Spring 父类变量注入失败

昨天遇到一个Action里面Service注入失败,换种说法应该说是根本没有发生注入,本来很简单的一个问题,但由于在项目中多个Action进行了继承,才最终导致了这个看似奇怪的问题。

下面小记下这个过程

收到同事问题,“有个Action请求一直调用报控指针,service一直是空的导致的!”

初步看了代码及配置,没有发现什么问题,起初怀疑是Action没有get方法所致,然后加上仍然无效;然后单步做了各种变量名的替换,一直一样问题 ,这过程中一直关注java代码确忽略了页面请求,通过页面请求发现代码真正逻辑是页面请求了一个子类Action的方法,而这个方法里面调用了父类的一个方法,此时父类里面的Service一直无法注入,对于上面所提的这种需求,实际上是需要在子类做Spring注入的同时也进行父类的Spring注入,那么这种需要这样的配置:

?
1
2
3
4
5
<bean id="****Action" class="com.**.**.contrl.**.mgr.action.**Action" scope="prototype" parent="termCommonAction">
  <property name="orderVerifyApiFacade" ref="ord.bizprov.orderVerifyApiFacade"/>
  <property name="orderListQryApiFacade" ref="ord.query.orderListQryApiFacade"/>
  <property name="channelQryApiFacade" ref="cfguse.channel.channelQryApiFacade" />
</bean>

经过上面的设置以后,请求子类的Action方法,子类方法中调用父类方法时,就不会出现父类不发生注入的问题了。

Spring通过父类注入公用属性的技巧

XML配置方式提取父类

在使用Spring + Hibernate框架,或者SSH2等框架的时候,在开发中只有一个基本的DAO是现在的非常流行的做法。然后,在看过多份这种代码以后,都是在每个业务类中声明了一个DAO属性,并且在Bean配置中,对每个业务类分别注入DAO。具体情形示例如下:

BaseDAO代码:

?
1
2
3
4
5
public class BaseDAO {
 public String service() {
  return "Success!";
 }
}

Services代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//第一个业务类
public class ServiceA {
 public String service() {
  return baseDAO.service();
 }
 protected BaseDAO baseDAO;
 public void setBaseDAO(BaseDAO baseDAO) {
  this.baseDAO = baseDAO;
 }
}
 
//第二个业务类
public class ServiceB {
 public String service() {
  return baseDAO.service();
 }
 
 protected BaseDAO baseDAO;
 public void setBaseDAO(BaseDAO baseDAO) {
  this.baseDAO = baseDAO;
 }
}

Spring的Bean配置如下:

?
1
2
3
4
5
6
7
<bean id="baseDAO" class="com.watson.BaseDAO" />
<bean id="serviceA" class="com.watson.ServiceA">
 <property name="baseDAO" ref="baseDAO" />
</bean>
<bean id="serviceB" class="com.watson.ServiceB">
 <property name="baseDAO" ref="baseDAO" />
</bean>

这样的做法是现在的主流。这样做不是说那里错了,还是那句老话:这样做肯定不优美,谁让人有时候是一根筋呢?

能够想到的办法是用一个父类来包含一些业务层公用的业务逻辑和属性。所以可以将上面的代码和配置。

Services代码改写如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//所有业务类的父类
public class BaseService {
 protected BaseDAO baseDAO;
 public void setBaseDAO(BaseDAO baseDAO) {
  this.baseDAO = baseDAO;
 }
}
//第一个业务类
public class ServiceA extends BaseService  {
 public String service() {
  return baseDAO.service();
 }
}
//第二个业务类
public class ServiceB extends BaseService  {
 public String service() {
  return baseDAO.service();
 }
}

Spring的Bean配置改写如下:

?
1
2
3
4
5
6
<bean id="baseDAO" class="com.watson.BaseDAO" />
<bean id="BaseService" class="com.watson.BaseService" />
 <property name="baseDAO" ref="baseDAO" />
</bean>
<bean id="serviceA" class="com.watson.ServiceA" />
<bean id="serviceB" class="com.watson.ServiceB" />

这样一来是不简洁了很多?尤其在实际项目有太多Bean的时候。然后,这里不会达到我们预想的结果,因为这里会出现如下的错误:

exception:

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is

java.lang.NullPointerException

......

root cause:

java.lang.NullPointerException:......

而出错代码就是每个业务中调用baseDAO的那行代码。这说明注入失败了。翻阅Spring的Bean注入详解之后,很快就可以找应该设置子类Bean配置的parent属性。所以这里可以修改设置。

Spring的Bean配置改写如下:

?
1
2
3
4
5
6
<bean id="baseDAO" class="com.watson.BaseDAO" />
<bean id="BaseService" class="com.watson.BaseService" />
 <property name="baseDAO" ref="baseDAO" />
</bean>
<bean id="serviceA" class="com.watson.ServiceA" parent="baseService" />
<bean id="serviceB" class="com.watson.ServiceB" parent="baseService" />

这个时候再运行,就不会报错了。原理是:在Spring的子类Bean配置中,其parent属性作用是指定其父类,并继承父类的注入属性。不仅如此,子类还可以修改或者覆盖父类的属性值。例如上述代码中的子类修改父类的baseDAO到属性:

?
1
2
3
4
5
6
<bean id="BaseService" class="com.watson.BaseService" />
 <property name="baseDAO" ref="baseDAO" />
</bean>
<bean id="serviceA" class="com.watson.ServiceA" parent="baseService" />
<property name="baseDAO" ref="baseDAO2" />
</bean>

而对于父类的List等集合属性,子类可以继承父类的值,并且在其基础上进行增加新的值:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<bean id="BaseService" class="com.watson.BaseService" />
 <property name="listValue"
  <list
   <value>listValue1</value
   <value>listValue2</value
  </list
 </property>
</bean>
<bean id="serviceA" class="com.watson.ServiceA" parent="baseService" />
 <property name="listValue"
  <list
   <value>listValue3</value
   <value>listValue4</value
  </list
 </property>
</bean>

Annotation方式提取父类

上面的方法是在XML配置文件中进行的配置。而对现在Spring3流行的Annotation方式,其实更加的方便,完整示例如下:

BaseDAO代码:

?
1
2
3
4
5
6
@Component
public class BaseDAO {
 public String service() {
  return "Success!";
 }
}

Services代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//所有业务类的父类
public class BaseService {
 @Autowired
 protected BaseDAO baseDAO;
}
 
//第一个业务类
@Component
public class ServiceA extends BaseService  {
 public String service() {
  return baseDAO.service();
 }
}
//第二个业务类
@Component
public class ServiceB extends BaseService  {
 public String service() {
  return baseDAO.service();
 }
}

Action层代码:

?
1
2
3
4
5
6
7
8
9
10
11
@Controller
@RequestMapping(value = "/testaction")
public class TestAction {
 @Autowired
 private ServiceA service;
 
 @RequestMapping(value = "/")
 public @ResponseBody String home(Model model) {
  return service.service();
 }
}

这里根本就不需要进行parent属性子类的配置,可以完美的提取父类,并且可以顺利的使用父类的公用属性。至于原理,没有去看源码的处理方式,估计和上述XML配置是异曲同工的,只是在这里增加了对父类的检测。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/posonrick/article/details/79589788