xml property标签注入一个类变量_关于Spring IOC (DI依赖注入)你需要知道的一切

《spring入门经典》

Spring IOC 的原理概述

在我们的日常开发中,创建对象的操作随处可见以至于对其十分熟悉的同时又感觉十分繁琐,每次需要对象都需要亲手将其new出来,甚至某些情况下由于坏编程习惯还会造成对象无法被回收,这是相当糟糕的。但更为严重的是,我们一直倡导的松耦合,少入侵原则,这种情况下变得一无是处。于是前辈们开始谋求改变这种编程陋习,考虑如何使用编码更加解耦合,由此而来的解决方案是面向接口的编程,于是便有了如下写法:

public class BookServiceImpl { //class private  BookDaoImpl bookDaoImpl; public void oldCode(){     //原来的做法     bookDaoImpl=new bookDaoImpl();     bookDaoImpl.getAllCategories(); } }  //=================new====================public class BookServiceImpl { //interface private BookDao bookDao; public void newCode(){     //变为面向接口编程     bookDao=new bookDaoImpl();     bookDao.getAllCategories();     }}

BookServiceImpl类中由原来直接与BookDaoImp打交互变为BookDao,即使BookDao最终实现依然是BookDaoImp,这样的做的好处是显而易见的,所有调用都通过接口bookDao来完成,而接口的真正的实现者和最终的执行者就是bookDaoImpl,当替换bookDaoImpl类,也只需修改bookDao指向新的实现类。

因此我们需要寻找一种方式,它可以令开发者在无需触及BookServiceImpl内容代码的情况下实现修改bookDao的实现类,以便达到最低的耦合度和最少入侵的目的。实际上存在一种称为反射的编程技术可以协助解决上述问题,反射是一种根据给出的完整类名(字符串方式)来动态地生成对象,这种编程方式可以让对象在生成时才决定到底是哪一种对象,因此可以这样假设,在某个配置文件,该文件已写好bookDaoImpl类的完全限定名称,通过读取该文件而获取到bookDao的真正实现类完全限定名称,然后通过反射技术在运行时动态生成该类,最终赋值给bookDao接口,也就解决了刚才的存在问题,这里为简单演示,使用properties文件作为配置文件,className.properties如下:

bookDao.name=com.zejian.spring.dao.BookDaoImpl

获取该配置文件信息动态为bookDao生成实现类:

public class BookServiceImpl implements BookService{    //读取配置文件的工具类    PropertiesUtil propertiesUtil=new PropertiesUtil("conf/className.properties");    private BookDao bookDao;    public void DaymicObject() throws ClassNotFoundException, IllegalAccessException, InstantiationException {        //获取完全限定名称        String className=propertiesUtil.get("bookDao.name");        //通过反射        Class c=Class.forName(className);        //动态生成实例对象        bookDao= (BookDao) c.newInstance();    }}

的确如我们所愿生成了bookDao的实例,这样做的好处是在替换bookDao实现类的情况只需修改配置文件的内容而无需触及BookServiceImpl的内部代码,从而把代码修改的过程转到配置文件中,相当于BookServiceImpl及其内部的bookDao通过配置文件与bookDao的实现类进行关联,这样BookServiceImpl与bookDao的实现类间也就实现了解耦合,当然BookServiceImpl类中存在着BookDao对象是无法避免的,毕竟这是协同工作的基础,我们只能最大程度去解耦合。

3949034a9c34d8a0610932876d0e684a.png

了解了上述的问题再来理解IOC就显得简单多了。Spring IOC 也是一个Java对象,在某些特定的时间被创建后,可以进行对其他对象的控制,包括初始化、创建、销毁等。简单地理解,在上述过程中,我们通过配置文件配置了BookDaoImpl实现类的完全限定名称,然后利用反射在运行时为BookDao创建实际实现类,包括BookServiceImpl的创建,Spring的IOC容器都会帮我们完成,而我们唯一要做的就是把需要创建的类和其他类依赖的类以配置文件的方式告诉IOC容器需要创建那些类和注入哪些类即可。Spring通过这种控制反转(IoC)的设计模式促进了松耦合,这种方式使一个对象依赖其它对象时会通过被动的方式传送进来(如BookServiceImpl被创建时,其依赖的BookDao的实现类也会同时被注入BookServiceImpl中),而不是通过手动创建这些类。我们可以把IoC模式看做是工厂模式的升华,可以把IoC看作是一个大工厂,只不过这个大工厂里要生成的对象都是在配置文件(XML)中给出定义的,然后利用Java的反射技术,根据XML中给出的类名生成相应的对象。从某种程度上来说,IoC相当于把在工厂方法里通过硬编码创建对象的代码,改变为由XML文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性,更是达到最低的耦合度,因此我们要明白所谓为的IOC就将对象的创建权,交由Spring完成,从此解放手动创建对象的过程,同时让类与类间的关系到达最低耦合度。

快速入门案例

accountService声明中多出了一个property的标签,这个标签指向了我们刚才创建的accountDao对象,它的作用是把accountDao对象传递给accountService实现类中的accountDao属性,该属性必须拥有set方法才能注入成功,我们把这种往类accountService对象中注入其他对象(accountDao)的操作称为依赖注入,这个后面会分析到,其中的name必须与AccountService实现类中变量名称相同,到此我们就完成对需要创建的对象声明。接着看看如何使用它们。

<bean name="accountService" class="com.zejian.spring.springIoc.service.impl.AccountServiceImpl">    <property name="accountDao" ref="accountDao"/>bean>public class AccountServiceImpl implements AccountService {    /**     * 需要注入的对象     */    private AccountDao accountDao;    public void setAccountDao(AccountDao accountDao) {        this.accountDao = accountDao;    } }

使用这些类需要利用Spring提供的核心类,ApplicationContext,通过该类去加载已声明好的配置文件,然后便可以获取到我们需要的类了。

@Testpublic void testByXml() throws Exception {    //加载配置文件    ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring/spring-ioc.xml");//        AccountService accountService=applicationContext.getBean("accountService",AccountService.class);    //多次获取并不会创建多个accountService对象,因为spring默认创建是单实例的作用域    AccountService accountService= (AccountService) applicationContext.getBean("accountService");    accountService.doSomething();}

基于注解的自动装配(@Autowired&@Resource&@Value)

基于@Autowired注解的自动装配

通过上述的分析,我们已对自动装配有所熟悉,但是在bean实例过多的情景下,手动设置自动注入属性还是不太完美,好在Spring 2.5 中引入了 @Autowired 注释,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。通过 @Autowired的使用标注到成员变量时不需要有set方法,请注意@Autowired 默认按类型匹配的,先看示例用注解演示前面注入userDao实例的三种方式。当然使用注解前必须先注册注解驱动,这样注解才能被正确识别

public class UserServiceImpl implements UserService {    //标注成员变量    @Autowired    private UserDao userDao;    //标注构造方法    @Autowired    public UserServiceImpl(UserDao userDao){        this.userDao=userDao;    }    //标注set方法    @Autowired    public void setUserDao(UserDao userDao) {        this.userDao = userDao;    }    @Override    public void done(){        userDao.done();    }}

显然上述代码我们通过3种方式注入userDao实例,xml配置文件只需声明bean的实例即可,在实际开发中,我们只需选择其中一种进行注入操作即可,建议使用成员变量注入,这样可以省略set方法和构造方法,相当简洁。

public class UserServiceImpl implements UserService {    //标注成员变量    @Autowired    private UserDao userDao;    }

基于@Resource注解的自动装配

与@Autowried具备相同功效的还有@Resource,默认按 byName模式 自动注入,由J2EE提供,需导入Package: javax.annotation.Resource,可以标注在成员变量和set方法上,但无法标注构造函数。@Resource有两个中重要的属性:name和type。Spring容器对于@Resource注解的name属性解析为bean的名字,type属性则解析为bean的类型。因此使用name属性,则按byName模式的自动注入策略,如果使用type属性则按 byType模式自动注入策略。倘若既不指定name也不指定type属性,Spring容器将通过反射技术默认按byName模式注入。

//@Autowired标注成员变量@Autowired@Qualifier("userDao")private UserDao userDao;  //上述代码等价于@Resource@Resource(name=“userDao”)private UserDao  userDao;//用于成员变量//也可以用于set方法标注@Resource(name=“userDao”)public void setUserDao(UserDao userDao) {   this.userDao= userDao;}

基于@Value注解的自动装配以及properties文件读取

关于@Autowired和@Resource都分析完了,但这里存在一个问题,上述两种自动装配的依赖注入并不适合简单值类型,如int、boolean、long、String以及Enum等,对于这些类型,Spring容器也提供了@Value注入的方式,这是非常具备人性化的,可以解决很多硬编码问题。@Value接收一个String的值,该值指定了将要被注入到内置的java类型属性值,放心,不必关系类型转换,大多数情况下Spring容器都会自动处理好的。一般情况下@Value会与properties文件结合使用,也分两种情况一种是SpEL(有点类似于jsp的EL),另外一种是占位符方式,看一个简单例子jdbc.properties文件如下:

jdbc.driver=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8&allowMultiQueries=truejdbc.username=rootjdbc.password=root

利用注解@Value获取jdbc.url和jdbc.username的值,实现如下:

public class UserServiceImpl implements UserService {    //标注成员变量    @Autowired    @Qualifier("userDao")    private UserDao userDao;    //占位符方式    @Value("${jdbc.url}")    private String url;    //SpEL表达方式,其中代表xml配置文件中的id值configProperties    @Value("#{configProperties['jdbc.username']}")    private String userName;    @Override    public void done(){        System.out.println("url:"+url);        System.out.println("username:"+userName);        userDao.done();    }}

Bean的作用域

Singleton作用域

所谓Bean的作用域是指spring容器创建Bean后的生存周期即由创建到销毁的整个过程。之前我们所创建的所有Bean其作用域都是Singleton,这是Spring默认的,在这样的作用域下,每一个Bean的实例只会被创建一次,而且Spring容器在整个应用程序生存期中都可以使用该实例。因此之前的代码中spring容器创建Bean后,通过代码获取的bean,无论多少次,都是同一个Bean的实例。我们可使用<bean>标签的scope属性来指定一个Bean的作用域,如下:

prototype作用域

除了Singleton外还有另外一种比较常用的作用域,prototype,它代表每次获取Bean实例时都会新创建一个实例对象,类似new操作符。我们来简单测试一下:

@Scope("prototype")public class AccountDaoImpl {    //......}

这里还需要说明一种特殊的情景,当一个作用域为Singleton的Bean依赖于一个作用域为prototype的Bean时

在这样的情况下希望的是每次getBean(“accountService”)处理的都是一个新的accountDao实例对象,但是由于accountService的依赖是在Bean被创建时注入的,而且accountService是一个Singleton,整个生存周期中只会创建一次,因此它所依赖的accountDao实例对象也只会被注入一次,此后不会再注入任何新的accountDao实例对象。为了解决这种困境,只能放弃使用依赖注入的功能,使用代码实现,如下:通过实现ApplicationContextAware接口,重写setApplicationContext,这样的话Spring容器在创建AccountServiceImpl实例时会自动注入ApplicationContext对象,此时便可以通过ApplicationContext获取accountDao实例了,这样可以保证每次获取的accountDao实例都是新的(这里的代码只是演示该过程,实际开发中一般不会要求accountDao每次都是新实例,因为accountDao无需记录状态信息,即无状态bean,一般默认singleton即可)

request与session作用域

在spring2.5中专门针对Web应该程序引进了request和session这两种作用域。关于request作用域,对于每次HTTP请求到达应用程序,Spring容器会创建一个全新的Request作用域的bean实例,且该bean实例仅在当前HTTP request内有效,整个请求过程也只会使用相同的bean实例,因此我们可以根据需要放心的更改所建实例的内部状态,而其他请求HTTP请求则创建新bean的实例互不干扰,当处理请求结束,request作用域的bean实例将被销毁,如在接收参数时可能需要一个bean实例来装载一些参数,显然每次请求参数几乎不会相同,因此希望bean实例每次都是足够新的而且只在request作用域范围内有效。关于Session可能你也已猜到,每当创建一个新的HTTP Session时就会创建一个Session作用域的Bean,并该实例bean伴随着会话的存在而存在。下面看一个测试程序:

@Component@Scope(value = "singleton")public class SingletonBean {    //......}@Component@Scope(value = "prototype" , proxyMode = ScopedProxyMode.TARGET_CLASS)public class PrototypeBean {    //......}@Component@Scope(value = "request" , proxyMode = ScopedProxyMode.TARGET_CLASS)public class RequestBean {    //......}@Component@Scope(value = "session" , proxyMode = ScopedProxyMode.TARGET_CLASS)public class SessionBean {    //........}

上述代码,分别创建4个不同作用域的Bean,并使用注解的方式开发,@Component表名它们是组件类,需要Spring容器帮忙创建,@Scope注明作用域,value指明是哪种作用域,除了SingletonBean外,其它Bean还使用proxyMode指明哪种代理模式创建,这里没有接口,因此使用CGLib代理生成(后面会分析为什么这么做)。接着需要在xml注明扫描包告诉Spring容器它们在哪里(4个类都声明在com.zejian.spring.dto包下)。

@Controllerpublic class BookController{    @Autowired    private RequestBean requestBean;    @Autowired    private SessionBean sessionBean;    @Autowired    private PrototypeBean prototypeBean;    @Autowired    private SingletonBean singletonBean;    @RequestMapping(value = "/test")    public void test()    {        print();    }    public void print() {        System.out.println("first  time singleton is :" + singletonBean);        System.out.println("second time singleton is :" + singletonBean);        System.out.println("first  time prototype is :" + prototypeBean);        System.out.println("second time prototype is :" + prototypeBean);        System.out.println("first  time requestBean is :" + requestBean);        System.out.println("second time requestBean is :" + requestBean);        System.out.println("first  time sessionBean is :" + sessionBean);        System.out.println("second time sessionBean is :" + sessionBean);        System.out.println("===========================================");    }
first  time singletonBean is :com.zejian.spring.dto.SingletonBean@2ebdd720second time singletonBean is :com.zejian.spring.dto.SingletonBean@2ebdd720first  time prototypeBean is :com.zejian.spring.dto.PrototypeBean@1ed53cdesecond time prototypeBean is :com.zejian.spring.dto.PrototypeBean@35c052befirst  time requestBean is :com.zejian.spring.dto.RequestBean@15b9dfe1second time requestBean is :com.zejian.spring.dto.RequestBean@15b9dfe1first  time sessionBean is :com.zejian.spring.dto.SessionBean@5b355daesecond time sessionBean is :com.zejian.spring.dto.SessionBean@5b355dae===========================================first  time singletonBean is :com.zejian.spring.dto.SingletonBean@2ebdd720second time singletonBean is :com.zejian.spring.dto.SingletonBean@2ebdd720first  time prototypeBean is :com.zejian.spring.dto.PrototypeBean@7775fd09second time prototypeBean is :com.zejian.spring.dto.PrototypeBean@79b20d97first  time requestBean is :com.zejian.spring.dto.RequestBean@7d8d9679second time requestBean is :com.zejian.spring.dto.RequestBean@7d8d9679first  time sessionBean is :com.zejian.spring.dto.SessionBean@5b355daesecond time sessionBean is :com.zejian.spring.dto.SessionBean@5b355dae===========================================

显然singletonBean永远只有一个实例,而PrototypeBean则每次被获取都会创建新的实例,对应RequestBean,在同一次Http请求过程中是同一个实例,当请求结束,RequestBean也随着销毁,在新的Http请求则会生成新的RequestBean实例,对于SessionBean,由于是在同一个浏览器中访问属于同一次会话,因此SessionBean实例都是同一个实例对象。现在使用另外一个浏览器启动访问,观察SessionBean是否变化。

显然SessionBean已改变,也就说明不同的会话中SessionBean实例是不同的。但我们还是很诧异,为什么需要在其他3种作用域上设置代理模式?事实上这个问题的本质与前面Singleton作用域的bean依赖于Prototype作用域的bean原理是相同,Prototype前面已分析过了(使用注解时也必须声明代理模式),这里我们主要分析request和session作用域,由于Spring容器只会在创建bean实例时帮助我们注入该实例bean所依赖的其他bean实例,而且只会注入一次,这并不是request、session作用域所希望看到的,毕竟它们都需要在不同的场景下注入新的实例对象而不是唯一不变的实例对象。为了解决这种困境,必须放弃直接在xml中注入bean实例,改用java代码方式(实现ApplicationContextAware接口)或者注解的方式(@Autowired)注入。示例中选择了后者,并在bean的声明中声明了动态代理模式(关于动态代理自行查阅相关资料),幸运地是,Spring容器足够聪明,以至于spring容器通过代理的方式生成新的代理实例bean,以此来满足创建新实例的需求。在程序运行过程中,当一个方法调用到达该代理对象时,Spring容器便尝试在当前的请求(Request)或者会话(Session)获取目标对象Bean(真正的实例Bean)。如果已存在则使用该Bean,否则,代理方法将创建新实例bean处理请求或者会话,请注意,这里指的的是一次Http请求或者一次会话的过程。如果希望request和session作用域通过xml配置文件方式声明时必须在<bean>标签中放置<aop:scoped-proxy>作为子标签,该作用于注解声明代理模式效果相同(需要引用aop命名空间,关于aop不清楚的,这里可简单理解为声明代理即可)。请注意,经过测试这种声明代理的方式不适合prototype作用域,该作用域生效的方式目前测试中只有基于注解方式和前面基于实现ApplicationContextAware接口两种方式。

IOC 与依赖注入的区别

IOC:控制反转:将对象的创建权,由Spring管理。

DI(依赖注入):在Spring创建对象的过程中,把对象依赖的属性注入到类中。 

它们两就这样,其他什么毛线都没有了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值