一,spring核心概念理解
控制反转:
控制反转即IoC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。
所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。
没有控制反转这种模式前,你创建一个对象,在什么地方用,你得单独通过关键字new出来用,
但现在可以不用这样,你把new对象的权利交给spring配置文件,通过配置文件来‘new‘,
就是权利的反转,你以前干的事,现在交给别人干了。
IoC是一个很大的概念,可以用不同的方式来实现。
其主要实现方式有两种:
(1)依赖查找(Dependency Lookup):容器提供回调接口和上下文环境给组件。
(2)依赖注入(Dependency Injection):组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。
后者是时下最流行的IoC类型,其又有接口注入(Interface Injection),设值注入(Setter Injection)和构造子注入(Constructor Injection)三种方式。
控制反转和依赖注入到底是什么关系?
控制反转是个模式,是个大概念,而对控制反转最具体的解释就是依赖注入,前者是说个大范围,
后者是具体到这件事情的实质上。就好比你朋友说等你过生日一定要送你个礼物,等过生日时送了你一个手机,
而送礼物就是个大概念,好比控制反转,具体送了一个手机,就是依赖注入,但是两者说明的是送礼物的这件事。
当然了,依赖注入有三种方式,就好比你朋友可以选择送你个苹果手机或送你台宝马或送你套别墅三种注入方式,
概率没有必要咬文嚼字的去理解,先把车会开了,慢慢就会知道车是怎么组成的。
二,IoC概念实例理解
在写一个程序,有多中实现方式,你如果每次都通过改程序代码,通过new来实现不同的功能,耦合性太高,
IoC是为解决松耦合而生的,以下通过代码理解。
(1)创建接口和实现类
接口:
package com.lanhuigu.spring.impl; public interface ISayHello { /** * 讲不同语言 * @return */ public String doSay(); }
说中文实现类:
package com.lanhuigu.spring.impl; public class ChHelloImpl implements ISayHello { private String msg; public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } /** * 讲汉语 */ @Override public String doSay() { // TODO Auto-generated method stub return "中文:"+msg; } }
说英文实现类:
package com.lanhuigu.spring.impl; public class EnHelloImpl implements ISayHello { private String msg; public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } /** * 讲英语 */ @Override public String doSay() { // TODO Auto-generated method stub return "英文:"+msg; } }
(2)配置spring文件
package com.lanhuigu.spring.impl; public class EnHelloImpl implements ISayHello { private String msg; public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } /** * 讲英语 */ @Override public String doSay() { // TODO Auto-generated method stub return "英文:"+msg; } }
(3)测试程序
package com.lanhuigu.spring.test; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.lanhuigu.spring.impl.ISayHello; public class TestHelloWorld { @Test public void testMyHelloWorld(){ //1.读取spring初始化的配置文件 ApplicationContext acxt = new ClassPathXmlApplicationContext("/applicationContext.xml"); //2.根据bean获取ISayHello实现类对象 ISayHello sayHello = (ISayHello) acxt.getBean("sayHello"); //3.调用接口方法 System.out.println(sayHello.doSay()); } }
通过以上代码对IoC进行实例体会:
(1)在测试程序中,如果不使用IoC,你可以通过new来实现讲什么语言
ISayHello sayHello = new ChHelloImpl();//讲汉语
ISayHello sayHello = new EnHelloImpl();//讲英语
或许会觉得我就new就可以了,领导让我讲英语,我就new讲英语,领导要我讲汉语,我就讲汉语,来回注释掉就行了,
但是,如果一个应用程序中有一万个地方用到了这段代码, 你是不是得注释一万个地方,心里绝对10万个对领导不满,改来改去有病吧!
你或许还不服,我就注释掉一万个地方,如果有100万个地方呢,你是不是100万个‘草泥马‘在草原上奔腾啊!
这个时候IoC来为你解决烦恼,一剂药就能治好,再也不用担心领导让你说啥你确有苦说不出口了。
通过配置文件控制该讲什么语言:
<bean id="sayHello" class="com.lanhuigu.spring.impl.ChHelloImpl">
<!-- 将变量msg值依赖注入 -->
<property name="msg">
<value>测试</value>
</property>
</bean>
你的配置文件通过实现类配置让讲啥都行,class="com.lanhuigu.spring.impl.ChHelloImpl",
而应用代码不受影响:
ISayHello sayHello = (ISayHello) acxt.getBean("sayHello");
System.out.println(sayHello.doSay());
应用程序只管拿到的bean对象是啥,我就说啥,至于你容器怎么配置,应用程序不屌你,
而容器则配置自己的实现类,至于你应用程序实现成啥样,容器才不管,通过这样的关系
实现低耦合的实效,实现控制反转的职能。
三,依赖注入(DI)
依赖注入的含义:
让主键依赖于抽象,当组件要与其他实际对象发生关系时,通过抽象来注入依赖的实际对象。
依赖注入的三种实现方式分别是接口注入(interface injection),set方法注入(setter injection),构造注入(constructor injection)。
(1)接口注入(interface injection):
定义一个将英语的接口:
package com.lanhuigu.spring.impl; public interface ISayHello { /** * 讲英语 * @return */ public String doSay(EnHelloImpl en); }
讲英语接口实现类:
package com.lanhuigu.spring.impl; public class EnHelloImpl implements ISayHello { private String msg; public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } /** * 讲英语 */ @Override public String doSay(EnHelloImpl en) { // TODO Auto-generated method stub return "英文:"+msg; } }
如果采用接口注入的方式,原本能说各种语言的接口,被定义为只能讲英语的接口,整个业务逻辑类依赖于这个接口。
在spring中接口注入是在IOC概念没有确立时定义的,现在spring中不推荐用接口注入,接口注入业务逻辑类依赖于接口,不是重点。
(2)set方法注入(setter injection):
set注入就是指在接受注入的类中定义一个set方法,并在参数中定义需要注入的参数。
为了能够将多种语言,接口注入方式是不适用的,我们把接口定义成开放的,这样每个实现类根据需要做自己
的业务逻辑,通过set方法传入参数。
接口:
package com.lanhuigu.spring.impl; public interface ISayHello { /** * 讲不同语言 * @return */ public String doSay(); }
实现类:
汉语实现类:
package com.lanhuigu.spring.impl; public class ChHelloImpl implements ISayHello { private String msg; public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } /** * 讲汉语 */ @Override public String doSay() { // TODO Auto-generated method stub return "中文:"+msg; } }
英语实现类:
package com.lanhuigu.spring.impl; public class EnHelloImpl implements ISayHello { private String msg; public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } /** * 讲英语 */ @Override public String doSay() { // TODO Auto-generated method stub return "英文:"+msg; } }
配置文件传入参数:
<?xml version="1.0" encoding="UTF-8"?> <!-- - Application context definition for JPetStore's business layer. - Contains bean references to the transaction manager and to the DAOs in - dataAccessContext-local/jta.xml (see web.xml's "contextConfigLocation"). --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <!-- 定义一个id为sayHello的bean, 通过spring配置文件变换实现类,实现不同的功能,无需修改别的程序 --> <bean id="sayHello" class="com.lanhuigu.spring.impl.ChHelloImpl"> <!-- 将变量msg值依赖注入 --> <property name="msg"> <value>测试</value> </property> </bean> </beans>
每一个接口实现类对应一种逻辑,spring配置文件负责调配。
配置文件bean的property有个默认属性name,这个name对应的是bean的class类对象中的属性名msg,
代码样式private String msg,value为属性值,通过类中的set方法,将配置文件中的属性值注入,
在类中通过get获取属性值。
(3)构造注入(constructor injection):
构造注入就是在接受注入的类中定义一个构造方法,并在参数中定义需要注入的元素。
咱们将讲英语改成构造方法注入:
接口:
package com.lanhuigu.spring.impl; public interface ISayHello { /** * 讲不同语言 * @return */ public String doSay(); }
实现类:
package com.lanhuigu.spring.impl; public class ChHelloImpl implements ISayHello { private String msg; private String two; /** * 构造方法注入 * @param msg * @param two */ public ChHelloImpl(String msg,String two){ this.msg = msg; this.two = two; } /*public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; }*/ /** * 讲汉语 */ @Override public String doSay() { // TODO Auto-generated method stub return "中文:"+msg+two; } }
spring配置文件:
<?xml version="1.0" encoding="UTF-8"?> <!-- - Application context definition for JPetStore's business layer. - Contains bean references to the transaction manager and to the DAOs in - dataAccessContext-local/jta.xml (see web.xml's "contextConfigLocation"). --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <!-- 定义一个id为sayHello的bean, 通过spring配置文件变换实现类,实现不同的功能,无需修改别的程序 --> <bean id="sayHello" class="com.lanhuigu.spring.impl.ChHelloImpl"> <!-- 将变量msg值依赖注入 --> <!-- <property name="msg"> <value>测试</value> </property> --> <!-- 构造放入注入, 如果不写index,默认构造方法只有一个参数, 如果注入多个,就会报错; 如果注入多个参数,可以通过index的下标指定与构造方法参数的对应关系, 下标从0开始,0表示构造方法的第一个参数,依次类推 --> <constructor-arg index="0"> <value>测试</value> </constructor-arg> <constructor-arg index="1"> <value>two arg</value> </constructor-arg> </bean> </beans>
测试程序:
package com.lanhuigu.spring.test; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.lanhuigu.spring.impl.ISayHello; public class TestHelloWorld { @Test public void testMyHelloWorld(){ //1.读取spring初始化的配置文件 ApplicationContext acxt = new ClassPathXmlApplicationContext("/applicationContext.xml"); //2.根据bean获取ISayHello实现类对象 ISayHello sayHello = (ISayHello) acxt.getBean("sayHello"); //3.调用接口方法 System.out.println(sayHello.doSay()); } }
输出结果:
中文:测试two arg
关于构造方法注入的注意点:
(1)在接受注入的构造方法中写需要注入的参数
(2)配置文件中构造属性constructor-arg不写index时默认一个参数,如果构造方法有多个参数,将报错
(3)如果注入多个参数,需在配置文件中constructor-arg通过index明确value对应构造方法中的参数情况
四,总结
依赖注入有三种注入方式,到底哪种注入方式好,一般推荐使用set注入
接口注入不考虑,构造方法注入通过上面的实例可以看出,如果注入参数太多,
构造方法中就会有一堆参数,看着都迷糊,而spring配置文件还得对参数做对应的传值,
如果注入类太多,参数太多,构造器配置越多,就会迷糊;
而set则是根据名字注入,能够避免构造方法注入的麻烦,但是,使用set方法注入是动态的注入参数,
可能被人篡改,构造方法注入在类启动时就完成,就不会出现篡改的可能,相对安全性高,二者各有
千秋,但是通常用set注入,构造方法注入酌情处理。
来注入的,这一点比构造方法注入好