`
nnwq
  • 浏览: 203593 次
社区版块
存档分类
最新评论

Spring依赖注入(IOC)那些事

阅读更多

小菜使用Spring有几个月了,但是对于它的内部原理,却是一头雾水,这次借着工作中遇到的一个小问题,来总结一下Spring。

     Spring依赖注入的思想,就是把对象交由Spring容器管理,使用者只需声明什么时候需要对象 ,这个可以说是常识,在这就不多说啦。

     小菜的项目中,为了提高代码运行效率,需要在类实例化的时候初始化一个列表,避免重复查询,于是小菜想当然的写了如下代码:

 

@Component
public class ApplyStatusHandler{
  @Autowired
  private DictMgr dictMgr;
  @Autowired
  private ApplyMgr applyMgr;
    
  public ApplyStatusHandler(){
      //这里初始化列表,使用了dictMgr、applyMgr
  }
}

  但实际测时,发现列表是空的。。。小菜刚开始还以为是构造方法没有执行,但通过异常捕获发现原来是出现了空指针。

 

     接下来分析一下为啥会出现空指针。

     @Component注解,意思大致就是告诉Spring,要把ApplyStatusHandler类的对象放到容器里,以后可以方便的使用@Autowired进行注入。

     @Autowired注解,有以下两个重要特点:

        

          +可以对成员变量、方法构造函数进行标注,来完成自动注入

          +根据类型进行自动注入的,如果spring配置文件中存在多个相同类型的bean时,或者不存在指定类型的bean,都会抛出异常。

 

     其中,对成员变量的注解,就如上例所示,可以直接从Spring容器中拿到此类型的对象,注入到成员变量中。

     对方法的注解,小菜的理解就是对方法的参数进行初始化。例如:

 @Autowired
 public void initXXXX(DictMgr dictMgr){
   //这里可以拿到DictMgr类的对象dictMgr
 }

  此方法因为有@Autowired标识,所以Spring会自动执行此方法,并且在执行的时候,去自己的容器找寻找和该方法参数类型一致的对象,进行注入,这样在方法中就可以拿到需要的对象了,其实和成员变量的注解大同小异,只不过把变量换了一个地方而已。

 

     对于以上两种方法,有一个必要的前提:对象必须是存在的(ApplyStatusHandler类的对象)!

     很容易理解,无论是对成员变量的注入,还是对方法参数的注入,都必须保证变量所在的对象是存在的,否则无从注入。

     到这,读者应该能明白为什么会出现空指针,因为Spring先调用的构造方法,此时还没有进行注入。

     幸好还有构造方法注入(和方法注入一样的道理),既然是构造方法注入,那么在Spring调用构造方法时,应该就可以拿到对象,然后再使用,就不会出现空指针,于是小菜把代码改成如下形式:

 

@Component
public class ApplyStatusHandler{
  
  private DictMgr dictMgr;
  private ApplyMgr applyMgr;
    
  @Autowired
  public ApplyStatusHandler(DictMgr dictMgr,ApplyMgr applyMgr){
      this.dictMgr=dictMgr;
      this.applyMgr=applyMgr;
    
      //这里初始化列表,使用了dictMgr、applyMgr
  }
    
}

    小菜满怀信心的启动项目,的确是没报空指针异常,但却报了很多Spring内部的异常。。。

 

     经过一番搜索,原来是由于小菜声明了一个带参数的构造方法,导致默认的无参数构造方法被抹掉,而这种情况下Spring实例化ApplyStatusHandler类,必须要有无参数的构造方法,因此加上即可(方法中可以什么也不做,但必须要有):

public ApplyStatusHandler(){}

  这下再启动项目,完美运行,说明对象已经成功注入到了构造方法中。

     如果我们不继续思考,事情可能就到此结束了,但是:既然这个无参构造方法是必须的,就说明Spring必然要调用这个方法,但调用了无参的构造方法,小菜写的有参构造方法是怎么调用的呢?总不会同时调用两个吧?

     其实,这和Spring底层的实例化方式有关。

     读者可能非常了解什么依赖注入,交由Spring容器管理,但底层究竟是怎么实现的呢?

     据小菜不完全了解,应该是有两种实现方式:JDK动态代理和Cglib动态代理。

     JDK动态代理,需要实现InvocationHandler 接口,也就是说如果想使用这种代理方式创建对象,需要让类先实现InvocationHandler 接口才行,最终创建的对象是一个新类的对象。

     Cglib动态代理,采用的是继承方式,它会在底层创建一个类,来继承原有的类,但是这个子类所有的方法都是直接调用父类去实现,相当于父类的一个代理、封装(封装的目的是支持事务处理),实际上我们在程序中使用的是这个子类的对象,并不是ApplyStatusHandler的对象。

     通过这两种代理方式,才让Spring可以支持事务、管理对象。

     本例中,小菜的这个类并没有实现InvocationHandler 接口,也就是说,不会使用JDK动态代理,而是使用Cglib动态代理来实例化对象,因此Spring会创建一个类来继承ApplyStatusHandler,然后根据ApplyStatusHandler类的构造方法实例化ApplyStatusHandler,再把子类实例化,让子类持有这个父类的引用,最终注入到变量中的是子类。

     由此可以看出,我们通过在构造方法上使用@Autowired注入对象是正确的,ApplyStatusHandler类能成功实例化,但由于有子类需要继承ApplyStatusHandler,因此ApplyStatusHandler中必须有一个空的构造方法,否则子类是无法实例化的(java基础。。。)。

     总之,ApplyStatusHandler类中的无参构造方法,是用来实例化Cglib生成的代理子类;有参构造方法是为了完成注入。

     好啦,小菜的分享到此结束~~

     水平有限,高手勿喷

 

 

9
0
分享到:
评论
5 楼 sosojustdo 2013-11-04  
crazyowen 写道
sosojustdo 写道
请教一个问题啊,就是我使用AnnotationConfigApplicationContext 获取注解的bean老是提示can not auto writed?是什么意思啊?

例子:
@Service(vlaue="issueService")
public IssueServiceImpl  implements IssueService{
    

//里面业务逻辑实现


}
获取IssueService Bean方法:
AnnotationConfigApplicationContext  context = new AnnotationConfigApplicationContext (IssueServiceImpl.class);


调用上面context.getBean("issueService");就会报上面的错。求解

value拼写错误


在网上看的列子和你差不多,3Q。@Service我最终还是去ApplicationContext中取的
4 楼 crazyowen 2013-11-03  
crazyowen 写道
sosojustdo 写道
请教一个问题啊,就是我使用AnnotationConfigApplicationContext 获取注解的bean老是提示can not auto writed?是什么意思啊?

例子:
@Service(vlaue="issueService")
public IssueServiceImpl  implements IssueService{
    

//里面业务逻辑实现


}
获取IssueService Bean方法:
AnnotationConfigApplicationContext  context = new AnnotationConfigApplicationContext (IssueServiceImpl.class);


调用上面context.getBean("issueService");就会报上面的错。求解

value拼写错误


AnnotationConfigApplicationContext 好像不是这样用的,初始化参数传的是标注@Configuration的类。这个类通过java类来实现spring配置信息。


@Configuration
public class MyConfig{
    @Bean
    public MyClass getMyClass(){
       return new MyClass;
    }
}

类似于
<bean id="myClass" class="MyClass路径"/>

如有错误请指正
3 楼 crazyowen 2013-11-03  
sosojustdo 写道
请教一个问题啊,就是我使用AnnotationConfigApplicationContext 获取注解的bean老是提示can not auto writed?是什么意思啊?

例子:
@Service(vlaue="issueService")
public IssueServiceImpl  implements IssueService{
    

//里面业务逻辑实现


}
获取IssueService Bean方法:
AnnotationConfigApplicationContext  context = new AnnotationConfigApplicationContext (IssueServiceImpl.class);


调用上面context.getBean("issueService");就会报上面的错。求解

value拼写错误
2 楼 sosojustdo 2013-11-02  
请教一个问题啊,就是我使用AnnotationConfigApplicationContext 获取注解的bean老是提示can not auto writed?是什么意思啊?

例子:
@Service(vlaue="issueService")
public IssueServiceImpl  implements IssueService{
    

//里面业务逻辑实现


}
获取IssueService Bean方法:
AnnotationConfigApplicationContext  context = new AnnotationConfigApplicationContext (IssueServiceImpl.class);


调用上面context.getBean("issueService");就会报上面的错。求解
1 楼 icezx 2013-11-01  
你理解错了,spring默认是jdk代理,只当你需要代理的类无接口继承的时候才使用cglib代理,也可强制spring采取cglib代理.
而所有的类都无需你自己实现 InvocationHandler 接口.如果spring需要你自己实现动态代理,你还需要spring干嘛..

相关推荐

Global site tag (gtag.js) - Google Analytics