Spring+MyBatis 注入为NULL的一个case.md

这几天实习生同学遇到一个spring注入一直为null的情况。

代码:

  • 监听器代码 :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @WebListener
    public class ServletListener implements ServletContextListener{

    private org.apache.log4j.Logger logger = org.apache.log4j.Logger
    .getLogger(getClass());

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
    // TODO Auto-generated method stub
    }


    @Override
    public void contextInitialized(ServletContextEvent sce) {

    SingleCheck check = new SingleCheck();
    check.run();
    }

    }
  • 主入口代码:

    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
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    public class SingleCheck {

    private Logger logger = Logger
    .getLogger(getClass());

    @Resource (name = "MonitorMasterService")
    private MonitorMasterService monitorMasterService;

    @Resource (name = "MonitorSlaveService")
    private MonitorSlaveService monitorSlaveService;

    public void run() {

    try {
    monitorMasterService.MOnline();
    System.out.println("master 检查完成");
    } catch (Exception e) {
    e.printStackTrace();
    String strUri =
    "http://xxx:8080/oc_alarm.php?"
    + "mail=xx@xx.com"
    + "&oc_msg=主库连接失败";
    try {
    URI uri = new URI(strUri);
    Desktop.getDesktop().browse(uri);
    } catch (Exception e1) {
    // TODO Auto-generated catch block
    e1.printStackTrace();
    }


    }


    try {
    monitorSlaveService.SOnline();
    } catch (Exception e) {
    e.printStackTrace();
    }

    try {
    monitorSlaveService.testSlaveValues();
    } catch (Exception e) {
    e.printStackTrace();
    }


    }

    /**
    * @param args
    */

    public static void main(String[] args) {
    // TODO Auto-generated method stub
    System.out.println("test");
    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    System.out.println("ctx>>" + ctx);
    MonitorMasterServiceImpl mo = (MonitorMasterServiceImpl) ctx.getBean("monitorMasterServiceImpl");
    System.out.println("MonitorMasterService>>" + mo);
    mo.MOnline();
    }
    }
  • MonitorMasterService

    1
    2
    3
    4
    5
    public interface MonitorMasterService {

    int MOnline();

    }
  • MonitorMasterServiceImpl

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Service
    public class MonitorMasterServiceImpl implements MonitorMasterService{

    @Resource
    private MonitorMasterMapper monitorMasterMapper;

    @Override
    public int MOnline(){
    System.out.println("aaaaaaa");
    return monitorMasterMapper.testOnline();
    }

    }
  • MonitorMasterMapper

    1
    2
    3
    4
    5
    6
    public interface MonitorMasterMapper {

    @Select("select 1;")
    int testOnline();

    }
  • applicationContext.xml

    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
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">




    <context:property-placeholder location="classpath:config.properties" />
    <context:component-scan base-package="monitor" />
    <context:annotation-config />

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">

    <property name="driverClassName" value="${driverClassName}" />
    <property name="url" value="${jdbc_url}" />
    <property name="username" value="${jdbc_username}" />
    <property name="password" value="${jdbc_password}" />
    <!--maxActive: 最大连接数量 -->
    <property name="maxActive" value="150" />
    <!--minIdle: 最小空闲连接 -->
    <property name="minIdle" value="5" />
    <!--maxIdle: 最大空闲连接 -->
    <property name="maxIdle" value="20" />
    <!--initialSize: 初始化连接 -->
    <property name="initialSize" value="30" />
    <!-- 连接被泄露时是否打印 -->
    <property name="logAbandoned" value="true" />
    <!--removeAbandoned: 是否自动回收超时连接 -->
    <property name="removeAbandoned" value="true" />
    <!--removeAbandonedTimeout: 超时时间(以秒数为单位) -->
    <property name="removeAbandonedTimeout" value="10" />
    <!--maxWait: 超时等待时间以毫秒为单位 1000等于60秒 -->
    <property name="maxWait" value="1000" />
    <!-- 在空闲连接回收器线程运行期间休眠的时间值,以毫秒为单位. -->
    <property name="timeBetweenEvictionRunsMillis" value="10000" />
    <!-- 在每次空闲连接回收器线程(如果有)运行时检查的连接数量 -->
    <property name="numTestsPerEvictionRun" value="10" />
    <!-- 1000 * 60 * 30 连接在池中保持空闲而不被空闲连接回收器线程 -->
    <property name="minEvictableIdleTimeMillis" value="10000" />
    <property name="validationQuery" value="SELECT NOW() FROM DUAL" />
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="typeAliasesPackage" value="monitor.alias"/>
    </bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage"
    value="monitor.master_slave.mapper,monitor.master.mapper,monitor.slave.mapper" />

    </bean>

    <bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

    <property name="dataSource" ref="dataSource" />
    </bean>
    <tx:annotation-driven transaction-manager="transactionManager" />

    </beans>

过程中遇到的问题整理

  • @Resource 和 @Autowired 的区别及优劣。
    @Autowired + @Autowired = @Resource
    @Resource的注解是J2EE提供的
    一般建议使用@Resource

  • Mybatis注入异常

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Exception in thread "main" org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): monitor.master.mapper.MonitorMasterMapper.testOnline
    at org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:189)
    at org.apache.ibatis.binding.MapperMethod.<init>(MapperMethod.java:43)
    at org.apache.ibatis.binding.MapperProxy.cachedMapperMethod(MapperProxy.java:58)
    at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:51)
    at com.sun.proxy.$Proxy10.testOnline(Unknown Source)
    at monitor.master.service.impl.MonitorMasterServiceImpl.MOnline(MonitorMasterServiceImpl.java:23)
    at monitor.master_slave.check.SingleCheck.main(SingleCheck.java:75)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

后来发现IDEA在编译的时候没有把mapper的xml文件编译进war包里所有会有这个错误,
改成注解的形式就没有这个问题了。

  • MyBatis注入异常续集
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    * @param args
    */
    public static void main(String[] args) {
    // TODO Auto-generated method stub
    System.out.println("test");
    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    System.out.println("ctx>>" + ctx);
    MonitorMasterServiceImpl mo = (MonitorMasterServiceImpl) ctx.getBean("monitorMasterServiceImpl");
    System.out.println("MonitorMasterService>>" + mo);
    mo.MOnline();
    }

在main里面能跑通了,但是启动web 依然报了null 的错误。

按理说注入应该是没问题了。但程序放在监听器里就是有问题,由于之前没有Spring项目的经验。
猜测是不是执行到那一步没有加载applicationContext.xml
debug之后发现已经存在这个容器
补充下spring ApplicationContext配置文件的加载过程

ContextLoaderListener位于spring-web-{$version}.RELEASE.jar中,
ServletContextListener实现了ServletContextListener接口,系统启动时会回调contextInitialized()方法。
在这个方法中,利用context-param中指定的配置文件初始化ApplicationContext,然后以WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE为key,注册为servletContext的一个属性。

更具体的可以看参考链接

加载容器也没问题,那就只能是Listener的问题。

看listener的写法:

1
2
3
4
5
6
@Override
public void contextInitialized(ServletContextEvent sce) {


SingleCheck check = new SingleCheck();
check.run();
}

按我过去的经验,spring是IOC框架,实习生同学居然用了new的方式生成一个对象,感觉这里应该是有问题的,猜测应该是使用new方式生成的对象是不会加入到application context容器里的。
于是改成:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void contextInitialized(ServletContextEvent sce) {

try {
MonitorMasterServiceImpl mo = (MonitorMasterServiceImpl) WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext())
.getBean("monitorMasterServiceImpl");
mo.MOnline();
} catch (Exception e) {
e.printStackTrace();
}

}

果然就完成了。
至于是不是我猜测的那样,继续查了下资料。

new的对象 是不能注入东西的,能注入其他组件的的必须是在application context中加载的单例对象。

参考资料: