标签:简单 接口 add ref creat ops 优先 多个 委托
1. 类之间的关系
传统应用程序设计中所说的依赖一般指“类之间的关系”,那先让我们复习一下类之间的关系:
泛化:表示类与类之间的继承关系、接口与接口之间的继承关系;
实现:表示类对接口的实现;
依赖:当类与类之间有使用关系时就属于依赖关系,不同于关联关系,依赖不具有“拥有关系”,而是一种“相识 关系”,只在某个特定地方(比如某个方法体内)才有关系。
关联:表示类与类或类与接口之间的依赖关系,表现为“拥有关系”;具体到代码可以用实例变量来表示;
聚合:属于是关联的特殊情况,体现部分-整体关系,是一种弱拥有关系;整体和部分可以有不一样的生命周期;是 一种弱关联;
组合:属于是关联的特殊情况,也体现了体现部分-整体关系,是一种强“拥有关系”;整体与部分有相同的生命周 期,是一种强关联;
Spring IoC容器的依赖有两层含义:Bean依赖容器和容器注入Bean的依赖资源:
Bean依赖容器:也就是说Bean要依赖于容器,这里的依赖是指容器负责创建Bean并管理Bean的生命周期,正是由 于由容器来控制创建Bean并注入依赖,也就是控制权被反转了,这也正是IoC名字的由来,此处的有依赖是指Bean和 容器之间的依赖关系。
容器注入Bean的依赖资源:容器负责注入Bean的依赖资源,依赖资源可以是Bean、外部文件、常量数据等,在 Java中都反映为对象,并且由容器负责组装Bean之间的依赖关系,此处的依赖是指Bean之间的依赖关系,可以认为是 传统类与类之间的“关联”、“聚合”、“组合”关系。
2. 为什么要引入DI?
动态替换Bean依赖对象,程序更灵活:替换Bean依赖对象,无需修改源文件:应用依赖注入后,由于可以采用配置 文件方式实现,从而能随时动态的替换Bean的依赖对象,无需修改java源文件;
更好实践面向接口编程,代码更清晰:在Bean中只需指定依赖对象的接口,接口定义依赖对象完成的功能,通过容 器注入依赖实现;
更好实践优先使用对象组合,而不是类继承:因为IoC容器采用注入依赖,也就是组合对象,从而更好的实践对象组合
Bean,对象组合具有动态性,能更方便的替换掉依赖Bean,从而改变Bean功能;
承导致Bean与子Bean之间高度耦合,难以复用。
增加Bean可复用性:依赖于对象组合,Bean更可复用且复用更简单;
降低Bean之间耦合:由于我们完全采用面向接口编程,在代码中没有直接引用Bean依赖实现,全部引用接口,而且 不会出现显示的创建依赖对象代码,而且这些依赖是由容器来注入,很容易替换依赖实现类,从而降低Bean与依赖之间 耦合;
代码结构更清晰:要应用依赖注入,代码结构要按照规约方式进行书写,从而更好的应用一些最佳实践,因此代码 结构更清晰。
1. 构造器注入
构造器注入可以根据参数索引注入、参数类型注入或Spring3支持的参数名注入(但参数名注入是有限制的,个人觉得好麻烦啊,因此只介绍前两种)
(1)根据参数索引注入
使用标签“<constructor-arg index="1" value="1"/>"来指定注入的依赖,其中index表示索引,从0开始,即第一个参数索引为0,value来指定注入的常量值,配置方式如下:
<constructor-arg index="0" value="Hello World!"/>
(2)根据参数类型注入
使用标签“ <constructor-arg type="java.lang.String" value="Hello World!"/>” 来指定注入的依赖,其中“type”表示需要匹配的参数类型,可以是基本类型也可以是其它类型,如“int”、“java.lang.String”、“value”来指定注入的常量值,配置方式如下:
(3)举例:
<!-- 通过构造器参数索引方式依赖注入 --> <bean id="byIndex" class="com.test.spring.HelloImpl2"> <constructor-arg index="0" value="Hello World"></constructor-arg> <constructor-arg index="1" value="2"></constructor-arg> </bean> <!-- 通过构造器参数类型方式依赖注入 --> <bean id="byName" class="com.test.spring.HelloImpl2"> <constructor-arg type="java.lang.String" value="Hello Spring"></constructor-arg> <constructor-arg type="int" value="3"></constructor-arg> </bean>
1 public class HelloImpl2 implements HelloApi{ 2 private String message; 3 private int a; 4 public HelloImpl2(String message,int a) { 5 this.message=message; 6 this.a=a; 7 } 8 public void sayHello() { 9 System.out.println(message+": "+a); 10 } 11 }
1 public class HelloTest { 2 public static void main(String args[]) { 3 ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); 4 HelloImpl2 aHelloImpl2=(HelloImpl2) context.getBean("byIndex"); 5 aHelloImpl2.sayHello(); 6 7 HelloImpl2 bHelloImpl2=(HelloImpl2) context.getBean("byName"); 8 bHelloImpl2.sayHello(); 9 } 10 11 } 12 13 Hello World: 2 14 Hello Spring: 3
2. 静态工厂注入
<bean id="bystaticFactory" class="com.test.spring.HelloApiStaticFactory" factory-method="newInstance"> <constructor-arg index="0" value="haha"></constructor-arg> <constructor-arg index="1" value="3"></constructor-arg> </bean>
1 public class HelloApiStaticFactory { 2 //工厂方法 3 public static HelloApi newInstance(String message,int a) { 4 //返回需要的Bean实例 5 return new HelloImpl2(message,a); 6 } 7 8 } 9 10 11 public class HelloImpl2 implements HelloApi{ 12 private String message; 13 private int a; 14 public HelloImpl2(String message,int a) { 15 this.message=message; 16 this.a=a; 17 } 18 public void sayHello() { 19 System.out.println(message+": "+a); 20 } 21 }
1 public class HelloTest { 2 public static void main(String args[]) { 3 ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); 4 HelloImpl2 aHelloImpl2=(HelloImpl2) context.getBean("bystaticFactory"); 5 aHelloImpl2.sayHello(); 6 7 } 8 9 } 10 11 12 haha: 3
3. 实例工厂类
<bean id="byinstanceFactory" factory-bean="instanceFactory" factory-method="newInstance"> <constructor-arg index="0" value="haha"></constructor-arg> <constructor-arg index="1" value="3"></constructor-arg> </bean> <bean id="instanceFactory" class="com.test.spring.HelloApiInstanceFactory"></bean>
1 public class HelloApiInstanceFactory { 2 public HelloApi newInstance(String message,int a) { 3 return new HelloImpl2(message,a); 4 } 5 6 } 7 8 public class HelloImpl2 implements HelloApi{ 9 private String message; 10 private int a; 11 public HelloImpl2(String message,int a) { 12 this.message=message; 13 this.a=a; 14 } 15 public void sayHello() { 16 System.out.println(message+": "+a); 17 } 18 }
1 public class HelloTest { 2 public static void main(String args[]) { 3 ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); 4 HelloImpl2 aHelloImpl2=(HelloImpl2) context.getBean("byinstanceFactory"); 5 aHelloImpl2.sayHello(); 6 7 } 8 9 } 10 11 haha: 3
4. setter注入
setter注入,是通过在通过构造器、静态工厂或实例工厂实例好Bean后,通过调用Bean类的setter方法进行注入依赖。setter注入方式只有一种根据setter名字进行注入。如下:
<bean id="bySetter" class="com.test.spring.HelloImpl2">
<property name="message" value="Hello World!"></property>
<property name="a" value="3"></property>
</bean>
1 public class HelloImpl2 implements HelloApi{ 2 private String message; 3 private int a; 4 public void setMessage(String message) { 5 this.message = message; 6 } 7 public void setA(int a) { 8 this.a = a; 9 } 10 public void sayHello() { 11 System.out.println(message+": "+a); 12 } 13 14 }
1 public class HelloTest { 2 public static void main(String args[]) { 3 ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); 4 HelloImpl2 aHelloImpl2=(HelloImpl2) context.getBean("bySetter"); 5 aHelloImpl2.sayHello(); 6 7 } 8 9 } 10 11 12 Hello World!: 3
note: setter注入的方法名要遵循“ JavaBean getter/setter 方法名约定”:
JavaBean:是本质就是一个POJO类,但具有一下限制: 该类必须要有公共的无参构造器,如public HelloImpl4() {}; 属性为private访问级别,不建议public,如private String message; 属性必要时通过一组setter(修改器)和getter(访问器)方法来访问; setter方法,以“set” 开头,后跟首字母大写的属性名,如“setMesssage”,简单属性一般只有一个方法参 数,方法返回值通常为“void”; getter方法,一般属性以“get”开头,对于boolean类型一般以“is”开头,后跟首字母大写的属性名,如 “getMesssage”,“isOk”;
还有一些其他特殊情况,比如属性有连续两个大写字母开头,如“URL”,则setter/getter方法为:“setURL” 和“getURL”,其他一些特殊情况请参看“Java Bean”命名规范。
5. 注入常量
注入常量是依赖注入中最简单的。配置方式如下所示:
<property name="message" value="Hello World!"/>
注意此处“value”中指定的全是字符串,由Spring容器将此字符串转换成属性所需要的类型,如果转换出错,将抛出相应的异常。
Spring容器目前能对各种基本类型把配置的String参数转换为需要的类型。
注:Spring类型转换系统对于boolean类型进行了容错处理,除了可以使用“true/false”标准的Java值进行注入,还 能使用“yes/no”、“on/off”、“1/0”来代表“真/假”,所以大家在学习或工作中遇到这种类似问题不要觉得是人 家配置错了,而是Spring容错做的非常好。
6. 注入Bean ID(???)
7. 注入集合类型
包括Collection类型、Set类型、List类型
(1)List类型:需要使用<List>标签来配置注入,具体配置如下:
<bean id="listBean" class="com.test.spring.ListBean"> <property name="values"> <list value-type="int"> <value>1</value> <value>2</value> <value>3</value> </list> </property> </bean>
1 public class ListBean { 2 private List values; 3 4 public List getValues() { 5 return values; 6 } 7 8 public void setValues(List values) { 9 this.values = values; 10 } 11 12 }
1 public class HelloTest { 2 public static void main(String args[]) { 3 ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); 4 ListBean listBean=(ListBean) context.getBean("listBean"); 5 System.out.println(listBean.getValues().get(1)); 6 System.out.println(listBean.getValues().get(1).getClass()); 7 } 8 9 } 10 11 12 2 13 class java.lang.Integer
(2)set类型:需要使用<set>标签来配置注入。其配置参数和<list>完全一样。
8. 注入数组类型
需要使用<array>标签来配置注入,其中标签属性“value-type”和“merge”和<list>标签含义 完全一样,具体配置如下:
<bean id="arrayBean" class="com.test.spring.ArrayBean">
<property name="array">
<array value-type="int">
<value>1</value>
<value>2</value>
<value>3</value>
</array>
</property>
</bean>
1 public class ArrayBean { 2 private int array[]; 3 4 public int[] getArray() { 5 return array; 6 } 7 8 public void setArray(int[] array) { 9 this.array = array; 10 } 11 12 13 } 14 15 16 public class HelloTest { 17 public static void main(String args[]) { 18 ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); 19 ArrayBean arrayBean=(ArrayBean) context.getBean("arrayBean"); 20 System.out.println(arrayBean.getArray()[1]); 21 22 } 23 24 } 25 26 27 2
note:也可以注入二维数组:
9. 注入字典(Map)类型
需要使用<map>标签来配置注入,,其属性 “key-type”和“value-type”分别指定“键”和“值”的数据类型,其含义和<list>标签的“value-type”含义一 样,在此就不罗嗦了,并使用<key>子标签来指定键数据,<value>子标签来指定键对应的值数据,具体配置如下
<bean id="mapBean" class="com.test.spring.MapBean"> <property name="values"> <map key-type="java.lang.String" value-type="java.lang.Integer"> <entry key="hello" value="23"></entry> </map> </property> </bean>
1 public class MapBean { 2 private Map<String, Integer> values; 3 4 public Map<String, Integer> getValues() { 5 return values; 6 } 7 8 public void setValues(Map<String, Integer> values) { 9 this.values = values; 10 } 11 12 13 }
1 public class HelloTest { 2 public static void main(String args[]) { 3 ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); 4 MapBean mapBean=(MapBean) context.getBean("mapBean"); 5 System.out.println(mapBean.getValues().get("hello")); 6 } 7 8 } 9 10 23
10. Properties注入
Spring能注入java.util.Properties类型数据,需要使用<props>标签来配置注入,键和值类型必 须是String,不能变,子标签<prop key=”键”>值</prop>来指定键值对,具体配置如下
<bean id="propertiesBean" class="com.test.spring.PropertiesBean">
<property name="values">
<props value-type="int" merge="default"> <!-- 虽然指定了value-type,但实际上该属性不起作用,因为Properties的key和value必须都是String类型 -->
<prop key="1">a</prop>
<prop key="2">b</prop>
</props>
</property>
</bean>
1 public class PropertiesBean { 2 private Properties values; 3 4 public Properties getValues() { 5 return values; 6 } 7 8 public void setValues(Properties values) { 9 this.values = values; 10 } 11 12 13 }
1 public class HelloTest { 2 public static void main(String args[]) { 3 ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); 4 PropertiesBean propertiesBean=(PropertiesBean) context.getBean("propertiesBean"); 5 System.out.println(propertiesBean.getValues()); 6 } 7 8 } 9 10 {2=b, 1=a}
11. 注入Bean之间的关系(注入依赖Bean)
可以有两种方式,一种是通过构造函数,另外一种是通过setter方式来注入依赖的Bean
“<constructor-arg index="0" value="Hello World!"/>”和“<property name="message" value="Hello World!"/>”中的value属性替换成bean属性,其中bean属性指定配置文件中的其 他Bean的id或别名。另一种是把<value>标签替换为<.ref bean=”beanName”>
(1)构造参数方式注入依赖的Bean
<ref="beanName">----只可以是这种
<bean id="di" class="com.test.spring.HelloImpl2"> </bean> <!-- 通过过构造器注入 --> <bean id="bean1" class="com.test.spring.HelloApiDecorator"> <constructor-arg index="0" ref="di"/> </bean>
(2)setter方式注入依赖的Bean
<.ref bean=”beanName”>---只可以是这种
<!-- 通过构造器注入 -->
<bean id="bean2" class="com.test.spring.HelloApiDecorator">
<property name="helloApi">
<ref bean="di"/>
</property>
</bean>
(3)举例
<!-- 定义依赖Bean --> <bean id="di" class="com.test.spring.HelloImpl2"> </bean> <!-- 通过过构造器注入 --> <bean id="bean1" class="com.test.spring.HelloApiDecorator"> <constructor-arg index="0" ref="di"/> </bean> <!-- 通过构造器注入 --> <bean id="bean2" class="com.test.spring.HelloApiDecorator"> <property name="helloApi"> <ref bean="di"/> </property> </bean>
1 public interface HelloApi { 2 public void sayHello(); 3 4 } 5 6 7 public class HelloImpl2 implements HelloApi{ 8 public void sayHello() { 9 System.out.println("hello"); 10 } 11 12 } 13 14 15 public class HelloApiDecorator implements HelloApi{ 16 private HelloApi helloApi; 17 public HelloApiDecorator() { 18 19 } 20 public HelloApiDecorator(HelloApi helloApi) { 21 this.helloApi=helloApi; 22 } 23 public void sayHello() { 24 System.out.println("before decorator"); 25 helloApi.sayHello(); 26 System.out.println("after decorator"); 27 } 28 public void setHelloApi(HelloApi helloApi) { 29 this.helloApi = helloApi; 30 } 31 32 33 }
1 public class HelloTest { 2 public static void main(String args[]) { 3 ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); 4 //通过构造器方式注入 5 HelloApi helloApi=(HelloApi) context.getBean("bean1"); 6 helloApi.sayHello(); 7 //通过setter方法注入 8 HelloApi helloApi2=(HelloApi) context.getBean("bean2"); 9 helloApi2.sayHello(); 10 } 11 12 } 13 14 15 before decorator 16 hello 17 after decorator 18 before decorator 19 hello 20 after decorator
12. 内部定义Bean
内部Bean就是在<property>或<constructor-arg>内通过<bean>标签定义的Bean,该Bean不管是否指定id或 name,该Bean都会有唯一的匿名标识符,而且不能指定别名,该内部Bean对其他外部Bean不可见,具体配置如下
<bean id="bean1" class="com.test.spring.HelloApiDecorator"> <property name="helloApi"> <bean id="bean2" class="com.test.spring.HelloImpl2"/> </property> </bean>
1 public class HelloApiDecorator implements HelloApi{ 2 private HelloApi helloApi; 3 public HelloApiDecorator() { 4 5 } 6 public HelloApiDecorator(HelloApi helloApi) { 7 this.helloApi=helloApi; 8 } 9 public void sayHello() { 10 System.out.println("before decorator"); 11 helloApi.sayHello(); 12 System.out.println("after decorator"); 13 } 14 public void setHelloApi(HelloApi helloApi) { 15 this.helloApi = helloApi; 16 } 17 18 19 }
1 public class HelloImpl2 implements HelloApi{ 2 private String name; 3 private String index; 4 5 public void setName(String name) { 6 this.name = name; 7 } 8 9 public void setIndex(String index) { 10 this.index = index; 11 } 12 13 public void sayHello() { 14 System.out.println("hello"); 15 } 16 17 }
1 public class HelloTest { 2 public static void main(String args[]) { 3 ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); 4 HelloApi bean=context.getBean("bean1",HelloApi.class); 5 bean.sayHello(); 6 } 7 8 }
1 before decorator 2 hello 3 after decorator
13. 注入null值
Spring通过<value>标签或value属性注入常量值,所有注入的数据都是字符串,那如何注入null值呢?通过 “null”值吗?当然不是因为如果注入“null”则认为是字符串。Spring通过<null/>标签注入null值。即可以采用如下 配置方式:
<bean class="....HelloImpl"> <property name="message"><null/></property> <property name="index" value="1"/> </bean>
1. 什么是循环依赖
循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如CircleA引用CircleB,CircleB引用 CircleC,CircleC引用CircleA,则它们最终反映为一个环。此处不是循环调用,循环调用是方法之间的环调用。如图 所示:
循环调用是无法解决的,除非有终结条件,否则就是死循环,最终导致内存溢出错误。
Spring容器循环依赖包括构造器循环依赖和setter循环依赖,那Spring容器如何解决循环依赖呢?首先让我们来定 义循环引用类
循环引用类:
1 public class CircleA { 2 private CircleB circleB; 3 4 public CircleA() { 5 } 6 7 public CircleA(CircleB circleB) { 8 this.circleB = circleB; 9 } 10 11 public void setCircleB(CircleB circleB) { 12 this.circleB = circleB; 13 } 14 15 public void a() { 16 circleB.b(); 17 } 18 }
1 public class CircleB { 2 private CircleC circleC; 3 public CircleB() { 4 5 } 6 public CircleB(CircleC circleC) { 7 this.circleC=circleC; 8 } 9 public void setCircleC(CircleC circleC) { 10 this.circleC = circleC; 11 } 12 public void b() { 13 circleC.c(); 14 } 15 16 }
1 public class CircleC { 2 private CircleA circleA; 3 public CircleC() { 4 5 } 6 public CircleC(CircleA circleA) { 7 this.circleA=circleA; 8 } 9 public void setCircleA(CircleA circleA) { 10 this.circleA = circleA; 11 } 12 public void c() { 13 circleA.a(); 14 } 15 16 }
2. spring如何解决循环依赖
Spring容器循环依赖包括构造器循环依赖和setter循环依赖。
(1)构造器循环依赖-------无法解决,只能抛出异常
表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出 BeanCurrentlyInCreationException异常表示循环依赖。
如在创建CircleA类时,构造器需要CircleB类,那将去创建CircleB,在创建CircleB类时又发现需要CircleC类,则又去 创建CircleC,最终在创建CircleC时发现又需要CircleA;从而形成一个环,没办法创建。
Spring容器将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持 在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出 BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。
首先看一下配置文件:
<bean id="circleA" class="com.test.spring.CircleA"> <constructor-arg index="0" ref="circleB"/> 通过构造器方式 </bean> <bean id="circleB" class="com.test.spring.CircleB"> <constructor-arg index="0" ref="circleC"/> </bean> <bean id="circleC" class="com.test.spring.CircleC"> <constructor-arg index="0" ref="circleA"/> </bean>
然后测试一下:
@Test(expected=BeanCurrentlyInCreationException.class) public void testA() throws Throwable{ try { new ClassPathXmlApplicationContext("bean.xml"); }catch (Exception e) { Throwable e1=e.getCause().getCause().getCause(); throw e1; } }
分析过程:
(2)setter循环依赖
表示通过setter注入方式构成的循环依赖。
对于setter注入造成的依赖是通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的Bean来 完成的,而且只能解决单例作用域的Bean循环依赖。
如下代码所示,通过提前暴露一个单例工厂方法,从而使其他Bean能引用到该Bean。
1 addSingletonFactory(beanName, new ObjectFactory() { 2 public Object getObject() throws BeansException { 3 return getEarlyBeanReference(beanName, mbd, bean); 4 } 5 }); 6
具体步骤如下:
对于“prototype”作用域Bean,Spring容器无法完成依赖注入,因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。
举例:
配置文件:
<bean id="circleA" class="com.test.spring.CircleA"> <property name="circleB" ref="circleB"></property> </bean> <bean id="circleB" class="com.test.spring.CircleB"> <property name="circleC" ref="circleC"></property> 通过setter方式 </bean> <bean id="circleC" class="com.test.spring.CircleC"> <property name="circleA" ref="circleA"></property> </bean>
测试:
public static void main(String args[]) { ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); CircleA bean=context.getBean("circleA",CircleA.class); CircleB bean2=context.getBean("circleB",CircleB.class); CircleC bean3=context.getBean("circleC",CircleC.class); }
运行是不会抛出异常的,因为单例模式下提前暴露了一个正在创建的bean,可是prototype的话将会抛出异常,比如:
<bean id="circleA" class="com.test.spring.CircleA" scope="prototype">
<property name="circleB" ref="circleB"></property>
</bean>
这会抛出异常的
note:
对于“singleton”作用域Bean,可以通过“setAllowCircularReferences(false);”来禁用循环引用:
补充:出现循环依赖是设计上的问题,一定要避免!
参考文献:
https://jinnianshilongnian.iteye.com/blog/1415278
标签:简单 接口 add ref creat ops 优先 多个 委托
原文地址:https://www.cnblogs.com/Hermioner/p/10188709.html