# SpringBean注入规范
V5平台基于Spring框架进行Java后台对象管理,三层模型中的Manager和Dao均要求注册到Spring容器中,采用依赖注入的形式实例化调用。
Spring Bean的注册和引用有很多种方法,但V5有对应规范,我们希望用规范的写法来完成开发,避免出现未知问题。
# 引用已有Bean实例
# 最推荐使用代码
如果你正在开发新功能,此时需要获取组织机构的一些数据,则必然会用到组织机构的核心接口OrgManager。
一个规范的引用Bean方法为:
// 特别注意:DemoManagerImpl一定也要是一个Spring Bean对象,此实例需要交给Spring容器管理
public class DemoManagerImpl implements DemoManager {
// 第一步:引用OrgManager接口
private OrgManager orgManager;
// 第二步:编写set方法,Spring会将实例对象自动注入,详见Spring官方文档
public void setOrgManager(OrgManager orgManager) {
this.orgManager = orgManager;
}
@Override
public void saveDemo(Map<String, Object> params) throws BusinessException {
// 第三步:使用orgManager已经实例化的对象
V3xOrgMember member = orgManager.getMemberById((Long) params.get("memberId"));
......
}
}
# 谨慎推荐使用代码
在查阅V5现有代码的时候,大家可能会看到@Inject注解,这个注解引用之后,似乎没有编写set方法也能注入对象。
Inject注解是Ctp依赖注入注解。有别于JSR 330,目前只支持Field注入。
使用@Inject注解之后,就可以省略set方法了,如下代码一般也是有效的:
public class DemoManagerImpl implements DemoManager {
// 第一步:引用OrgManager接口并引入@Inject注解
@Inject
private OrgManager orgManager;
@Override
public void saveDemo(Map<String, Object> params) throws BusinessException {
// 第二步:使用orgManager已经实例化的对象
V3xOrgMember member = orgManager.getMemberById((Long) params.get("memberId"));
......
}
}
注意:为什么要谨慎推荐使用? 因为这个注解默认会强制实例化对象,如果Bean对象不存在会抛出异常,致使系统无法启动。
什么时候Bean对象会不存在呢?我们产品是插件化开发,如果客户没买插件,则对应插件下的Bean都不会实例化。比如你引入了任务Bean,但是客户没购买任务插件,使用@Inject注解后会导致无法启动:
public class DemoManagerImpl implements DemoManager {
// 警告:如果task任务插件不存在会导致Bean注入失败并且抛出异常无法启动
@Inject
private TaskmanageApi taskmanageApi;
@Override
public void saveDemo(Map<String, Object> params) throws BusinessException {
if(taskmanageApi != null){
TaskInfoBO taskInfoBO = taskmanageApi.getTaskInfo((Long) params.get("taskId"));
}
.....
}
}
针对以上问题,标准产品提供了一个解决方案:追加@PluginQualifier注解,标识这个Bean来自哪个插件,如果带这个注解后,就不会抛出异常了。
如下代码才是最规范的编写方式:
public class DemoManagerImpl implements DemoManager {
// PluginQualifier注解用于标识此Bean属于哪个插件
@Inject
@PluginQualifier(pluginName = "taskmanage")
private TaskmanageApi taskmanageApi;
@Override
public void saveDemo(Map<String, Object> params) throws BusinessException {
if(taskmanageApi != null){
TaskInfoBO taskInfoBO = taskmanageApi.getTaskInfo((Long) params.get("taskId"));
}
.....
}
}
# 没有办法时才使用的方法
需要注意,使用前面两种依赖注入需要要求实现类需要交给Spring容器管理。而我们的Utils工具类一般不会交给Spring,恰恰在Utils中需要使用一些Bena对象时就无法通过依赖注入的形式获取,需要换一种方式获取。
V5平台的AppContext.getBean提供了在方法引用中引用Spring Bean示例的机会:
public class DemoUtils {
private static Log logger = CtpLogFactory.getLog(DemoUtils.class);
public static String getMemberName(Long memberId){
// 通过AppContext.getBean强行获取Bean实例
OrgManager orgManager = (OrgManager) AppContext.getBean("orgManager");
try {
V3xOrgMember member = orgManager.getMemberById(memberId);
if(member != null){
return member.getName();
}
} catch (BusinessException e) {
logger.error("",e);
};
return "";
}
}
需要注意AppContext是V5平台封装的API,这是推荐使用的。而Spring本身的ApplicationContext对象虽然也可以获取Bean,但这是不允许使用的,尽量使用V5平台封装,方便后续遇到问题统一调整。
# 绝对禁止编写的代码示例
上一节说到AppContext.getBean可以应急使用,但需要注意此代码绝对不要出现在成员变量后面,如下所示代码是绝对错误,绝对错误,绝对错误!这样会导致Spring初始化的混乱,甚至引发启动异常!
public class DemoManagerImpl implements DemoManager {
// Bad Code:绝对禁止的代码写法
private OrgManager orgManager = (OrgManager) AppContext.getBean("orgManager");
@Override
public void saveDemo(Map<String, Object> params) throws BusinessException {
V3xOrgMember member = orgManager.getMemberById((Long) params.get("memberId"));
......
}
}
有人说“我看到过有些地方这样写是没问题的”,是的,目前唯一允许在成员变量中引入AppContext.getBean的地方是我们的后台Rest接口类:
/**Rest接口中直接引用是被允许的,因为他未交给Spring管理,Spring初始化完成之后才初始化Rest接口类,所以不受影响**/
@Path("meeting")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces(MediaType.APPLICATION_JSON)
@Api(module = ApiModuleEnum.meeting, type = ApiTypeEnum.REST, value = "会议",subModelName = "会议")
public class MeetingResource extends BaseResource {
private static final Log LOGGER = LogFactory.getLog(MeetingResource.class);
private MtMeetingManager mtMeetingManager = (MtMeetingManager) AppContext.getBean("mtMeetingManager");
private MeetingManager meetingManager = (MeetingManager) AppContext.getBean("meetingManager");
private MeetingM3Manager meetingM3Manager = (MeetingM3Manager) AppContext.getBean("meetingM3Manager");
private MeetingRoomM3Manager meetingRoomM3Manager = (MeetingRoomM3Manager) AppContext.getBean("meetingRoomM3Manager");
.....
}
# 如何编写注册一个新的Bean
如果你需要编写一个新的实现类,并将其注册到Spring中,方便自己使用或被别人调用,则按照如下步骤完成即可。
大前提依然是以插件化开发规范进行开发。
首先,定义一个Java接口:
package com.seeyon.apps.demo.manager;
public interface DemoManager {
public void saveDemo(Map<String,Object> params) throws BusinessException;
}
然后,编写接口的实现类:
package com.seeyon.apps.demo.manager;
public class DemoManagerImpl implements DemoManager {
@Inject
private DemoDao demoDao;
@Override
public void saveDemo(Map<String, Object> params) throws BusinessException {
Demo demo = new Demo();
ParamUtil.mapToBean(params, demo, true);
demo.setIdIfNew();
demo.setCreateMember(AppContext.currentUserId());
demo.setCreateTime(new Date());
demo.setDeleteFlag(Constants4Demo.deleteFlag.ENABLE.ordinal());
demoDao.save(demo);
}
}
最后将对象通过XML配置的形式注册到Spring容器中,XML存放于各自插件下:
<!-- 插件化开发,XML注册位置 \seeyon\WEB-INF\cfgHome\plugin\demo\spring\srping-demo-manager.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans default-autowire="byName">
<bean id="demoManager" class="com.seeyon.apps.demo.manager.DemoManagerImpl"/>
</beans>
最后调用方注册对应Bena即可使用:
public class DemoController extends BaseController{
private DemoManager demoManager;
public void setDemoManager(DemoManager demoManager) {
this.demoManager = demoManager;
}
}
# 红线、禁忌
1)被Spring管理的对象中,依赖注入对象绝对禁止使用AppContext.getBean来完成!
public class DemoManagerImpl implements DemoManager {
// Bad Code:绝对禁止的代码写法
private OrgManager orgManager = (OrgManager) AppContext.getBean("orgManager");
@Override
public void saveDemo(Map<String, Object> params) throws BusinessException {
V3xOrgMember member = orgManager.getMemberById((Long) params.get("memberId"));
......
}
}
2)不要在Spring容器初始化前、初始化中就进行Bean的使用
public class DemoManagerImpl implements DemoManager {
@Inject
private DemoDao demoDao;
DemoManagerImpl(){
// 不要在构造方法中使用Bean对象
demoDao.findDemoList();
}
static {
// 不要在static代码块中使用Bean对象
demoDao.findDemoList();
}
private void init() {
// 不要在init-method中使用Bean对象
demoDao.findDemoList();
}
}
Spring XML中的init-method属性是不推荐使用的,很多人期望在初始化对象时同时进行缓存初始化,这种写法是极其危险的,我们有标准的初始化接口,在后面介绍。
<beans default-autowire="byName">
<!-- init-method是不允许被使用的 -->
<bean init-method="init" id="demoManager" class="com.seeyon.apps.demo.manager.DemoManagerImpl" />
</beans>
Spring XML中的init-method的统一替代方案如下:
平台提供了一个标准组件:可以在Spring容器初始化完成之后,进行缓存等数据初始化,标准的写法如下:
有开发场景需要在启动系统过程中做一些初始化操作,比如定时任务初始化,请统一使用SystemInitializer机制。
public class DemoManagerImpl extends AbstractSystemInitializer implements DemoManager {
// 经典开头:养成记录日志的好习惯
private Log LOGGER = CtpLogFactory.getLog(DemoManagerImpl.class);
// 使用注解标准注入Bean
@Inject
private OrgManager orgManager;
@Inject
private IndexApi indexApi;
// 重写AbstractSystemInitializer下的initialize方法,该方法的执行时间是:系统所有Spring初始化完成之后,根据平台默认任务编排依次执行继承了SystemInitializer的initialize方法
@Override
public void initialize() {
// 此时所有Bean已经初始化完成,可以放心调用所有Bean
orgManager.xxx
}
← DAO数据层开发 Event事件监听开发 →
