【JavaEE学习笔记】Spring_02_IoC/DI依赖注入,集合对象属性注入,自动装配,代理模式

Spring_02

A.IoC/DI依赖注入

1.控制反转和依赖注入的概念

IoC实现由容器控制程序之间的关系,而非传统实现中,由程序代码直接操控

控制权由应用代码中转到了外部容器,控制权的转移,是所谓控制反转

可供选择的IoC 容器: Apache Avalon、PicoContainer 和 HiveMind

Avalon 从没怎么流行,尽管它很强大而且有很长的历史

Avalon相当的重和复杂,并且看起来比新的IoC解决方案更具侵入性

PicoContainer是一个轻量级而且更强调通过构造函数

表达依赖性而不是JavaBean 属性

与Spring不同,它的设计允许每个类型一个对象的定义

可能是因为它拒绝任何Java代码外的元数据导致的局限性

Martin Fowler给IoC起更为直观的名字:Dependency Injection依赖注射DI

创建被调用者实例的工作通常由Spring容器来完成

然后注入调用者,因此也称为依赖注入

<bean id=”bb” class=”com.yan.Person” p:birth-ref=”now”/> 

针对Person属性birth由容器负责调用Person对象的setBirth将now对象设置进去

2.Spring和EJB的比较

相对于EJB来说,Spring是一个轻量级的JavaEE应用开发框架

使把各个技术层次之间的风险降低

EJB就是企业级JavaBean

EJB2是重量级,运行必须有对应的容器

EJB3使用JPA已经偏向于轻量级

EJB的内聚性较强,内聚性的白盒特征使我们必须放弃一部分可控性而去信任容器能力

而Spring则是考虑如何“不造轮子”,如何更好的组装这些轮子,让他们更好的转动

3.IoC/Di模式的优点

颠覆了“使用对象之前必须创建” 的基本Java语言定律

降低了模块之间的耦合度,提高了应用的灵活性和代码重用度

使用IoC模式,完全在一个抽象层次进行描述和技术架构

因此,IoC模式可以为容器、框架之类的软件实现提供了具体的实现手段

4.工厂模式和IoC的特点和区别

主要区别体现在调用的代码

如果使用IoC,在代码中将不需要嵌入任何工厂模式等的代码

因为这些工厂模式其实还是与被调用者有些间接的联系

这样使用IoC彻底解耦了工厂和被调用之间的联系

使用IoC带来的代价是:需要在客户端或其它某处进行工厂和被调用之间联系的组装

所以IoC并没有消除工厂和被调用之间这样的联系,只是转移了这种联系

这种联系转移实际也是一种分离关注,它的影响巨大,它提供了AOP实现的可能

5.依赖注入,使用Spring管理连接池

pom.xml

<!-- 连接池 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.5</version>
		</dependency>
		<!-- 数据库驱动 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.44</version>
		</dependency>
applicationContext.xml配置连接池

	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
		<!-- value用于注入8种简单类型及其包装类和String类型,ref用于注入另外一个受管bean对象 -->
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql:///test" />
		<property name="username" value="root" />
		<property name="password" value="root" />
	</bean>
将连接池注入dao

package org.wpf.spr;

public interface IUserDao {
	void pp();
}
实现类
package org.wpf.spr;

import javax.sql.DataSource;

public class UserDaoImpl implements IUserDao {

	private DataSource ds;

	@Override
	public void pp() {
		System.out.println("UserDaoImpl.pp()");
		try {
			System.out.println(ds.getConnection());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public DataSource getDs() {
		return ds;
	}

	public void setDs(DataSource ds) {
		this.ds = ds;
	}

}
单元测试

测试配置

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>4.3.14.RELEASE</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>

测试代码

package org.wpf.test;

import java.sql.SQLException;

import javax.annotation.Resource;
import javax.sql.DataSource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class) // 设置所使用的是Spring提供的执行器
@ContextConfiguration(locations = "classpath:applicationContext.xml") // 定义对应的配置文件的位置
public class Test_01 {
	@Resource(name = "dataSource") // 注入所需要的测试对象
	private DataSource ds;

	@Test
	public void testDs() throws SQLException {
		System.out.println(ds.getConnection());
	}
}

6.依赖注入方法1——接口注入

接口注入方式发展的比较早,在实际中也得到了普遍的应用

在编程时,常常借助接口来将调用者与实现者相分离

对于一个接口注入型IoC容器而言

加载接口实现并创建其实例的工作由容器完成

J2EE开发中常用的Context.lookup()都是接口注入型IoC的表现形式

Servlet中的doGet()和doPost()方法是接口注入

HttpServletRequest和HttpServletResponse实例由Servlet Container在运行期动态注入

由于其在灵活性、易用性上不如其他两种注入模式

因而在IoC的专题世界内并不被看好

7.依赖注入方法3——设置注入

要求无参构造方法和对应的set方法

设值注入是指通过setter()方法传入被调用者的实例,使用<property>进行设置

这种注入方式简单、直观,因而在Spring的依赖注入中大量使用

Person类

package org.wpf.spr;

import java.util.Date;

public class Person {
	private Date birth;
	private String firstName;
	private String lastName;

	public Person() {
	}

	public Person(Date birth, String firstName, String lastName) {
		super();
		this.birth = birth;
		this.firstName = firstName;
		this.lastName = lastName;
	}

	public Date getBirth() {
		return birth;
	}

	public void setBirth(Date birth) {
		this.birth = birth;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	@Override
	public String toString() {
		return "Person [birth=" + birth + ", firstName=" + firstName + ", lastName=" + lastName + "]";
	}

}

配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:p="http://www.springframework.org/schema/p" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- 注入日期 -->
	<bean id="now" class="java.util.Date" />

	<!-- 创建Person对象 -->
	<!--  Person的运行需要依赖于birth属性,由容器负责创建now对象和Person对象,同时将now对象采用setBirth方法设置到birth属性上 -->
	<bean id="person" class="org.wpf.spr.Person">
		<property name="birth" ref="now" />
		<property name="lastName" value="Jack" />
		<property name="firstName" value="Chan" />
	</bean>
</beans>
测试类
package org.wpf.spr;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
	public static void main(String[] args) {
		// 加载文件
		AbstractApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
		
		// 反转
		Person per = ac.getBean("person", Person.class);
		System.out.println(per);
		
		// 关闭容器
		ac.close();
	}
}
bean中要么不写构造方法( 未定义构造方法时,默认生成无参构造),要么就必须写无参

习惯了传统JavaBean开发,通过setter方法设定依赖关系显得更加直观,更加自然

如果依赖关系较为复杂,那么构造器注入模式的构造函数也会相当庞大

此时设置器注入模式往往更为简洁

对于某些第三方类库而言,可能要求组件必须提供一个默认的构造函数

(如Struts中的Action),此时构造器注入类型的依赖注入机制就体现出其局限性

难以完成期望的功能

8.依赖注入方法2——构造器注入

通过类的带参构造器创建对象,并注入依赖对象

在构造期即创建一个完整、合法的对象

对于这条Java设计原则,构造器注入无疑是最好的响应者

避免了繁琐的setter方法的编写,所有依赖关系均在构造函数中设定

依赖关系集中呈现,更加易读

由于没有setter方法,依赖关系在构造时由容器一次性设定

因此组件在被创建之后即处于相对“不变”的稳定状态

同样由于关联关系仅在构造函数中表达

组件中的依赖关系处于黑盒之中,对上层屏蔽不必要的信息

通过构造子注入,意味着可以在构造函数中决定依赖关系的注入顺序   

package org.wpf.spr;

import java.util.Date;

public class Person {
	private Date birth;
	private String username;
	private int age;

	public Person() {
	}

	public Person(Date birth, String username, int age) {
		super();
		this.birth = birth;
		this.username = username;
		this.age = age;
	}

	public Date getBirth() {
		return birth;
	}

	public void setBirth(Date birth) {
		this.birth = birth;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	@Override
	public String toString() {
		return "Person [birth=" + birth + ", username=" + username + ", age=" + age + "]";
	}

}
正确配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="now" class="java.util.Date" />

	<bean id="person" class="org.wpf.spr.Person">
		<constructor-arg ref="now" />
		<constructor-arg value="Jack" />
		<constructor-arg value="20" />
	</bean>

</beans>
但要注意构造器的参数列表,如下代码

	<bean id="now" class="java.util.Date" />

	<bean id="person" class="org.wpf.spr.Person">
		<constructor-arg ref="now" />
		<constructor-arg value="20" type="int" />
		<constructor-arg value="Jack" type="java.lang.String" />
	</bean>

注意在编译阶段,IDE工具不能发现数据异常

但是在具体运行时20按照顺序赋值给username,这里没有问题

但是将Jack赋值给age时

由于不能进行数据类型转换则UnsatisfiedDependencyException

如果再添加一个构造器,将参数列表调整,则报错消失

	public Person(Date birth, int age, String username) {
		super();
		this.birth = birth;
		this.age = age;
		this.username = username;
	}
这就意味着Spring框架可以自动识别对应类型,调用最合适的构造器

如果数据具备一定的二义性时,可以考虑使用type说明参数类型的方法解决

	<bean id="now" class="java.util.Date" />

	<bean id="person" class="org.wpf.spr.Person">
		<constructor-arg ref="now" />
		<constructor-arg name="20" type="int" />
		<constructor-arg name="Jack" type="java.lang.String" />
	</bean>
但当两个参数类型一致,则不能靠类型进行区分
public class Person {
	private Date birth;
	private String username;
	private String sex;

	public Person() {
	}

	public Person(Date birth, String username, String sex) {
		super();
		this.birth = birth;
		this.username = username;
		this.sex = sex;
	}

}
系统识别参数时,按照默认的配置顺序识别两个字串类型属性

	<bean id="now" class="java.util.Date" />

	<bean id="person" class="org.wpf.spr.Person">
		<constructor-arg ref="now" />
		<constructor-arg value="男" />
		<constructor-arg value="Jack" />
	</bean>
通过序号强制定义这个参数的序号,注意序号从0开始算起
	<bean id="person" class="org.wpf.spr.Person">
		<constructor-arg ref="now" />
		<constructor-arg value="男" />
		<constructor-arg value="Jack" index="2" />
	</bean>
通过构造器中形参名称强制定义这个参数,注意这个定义方法在Spring4-不支持
	<bean id="person" class="org.wpf.spr.Person">
		<constructor-arg ref="now" />
		<constructor-arg value="男" />
		<constructor-arg value="Jack" name="username" />
	</bean>

9.构造注入和设置注入的选择

设置器注入和构造器模式各有千秋

而Spring对构造器注入和设置器注入类型的依赖注入机制提供了良好支持

理论上,以构造器注入类型为主,辅之以设置器注入类型机制作为补充

可以达到最好的依赖注入效果

一般来说,对于基于Spring Framework开发的应用而言,设置器注入使用更加广泛

B.集合对象属性注入和自动装配

1.集合对象属性注入

集合对象

package org.wpf.spr;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class Demo {
	private String[] someStrArray;
	private Object[] someObjArray;
	private Collection collection;
	private List someList;
	private Set set;
	private Map someMap;
	private Properties ps;

	public Demo() {
		super();
		// TODO Auto-generated constructor stub
	}

	public Demo(String[] someStrArray, Object[] someObjArray, Collection collection, List someList, Set set,
			Map someMap, Properties ps) {
		super();
		this.someStrArray = someStrArray;
		this.someObjArray = someObjArray;
		this.collection = collection;
		this.someList = someList;
		this.set = set;
		this.someMap = someMap;
		this.ps = ps;
	}

	public String[] getSomeStrArray() {
		return someStrArray;
	}

	public void setSomeStrArray(String[] someStrArray) {
		this.someStrArray = someStrArray;
	}

	public Object[] getSomeObjArray() {
		return someObjArray;
	}

	public void setSomeObjArray(Object[] someObjArray) {
		this.someObjArray = someObjArray;
	}

	public Collection getCollection() {
		return collection;
	}

	public void setCollection(Collection collection) {
		this.collection = collection;
	}

	public List getSomeList() {
		return someList;
	}

	public void setSomeList(List someList) {
		this.someList = someList;
	}

	public Set getSet() {
		return set;
	}

	public void setSet(Set set) {
		this.set = set;
	}

	public Map getSomeMap() {
		return someMap;
	}

	public void setSomeMap(Map someMap) {
		this.someMap = someMap;
	}

	public Properties getPs() {
		return ps;
	}

	public void setPs(Properties ps) {
		this.ps = ps;
	}

}

配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="a1" class="org.wpf.spr.Demo">
		<!-- 字符数组 -->
		<property name="someStrArray">
			<array>
				<value>zhangsan</value>
				<value>lisi</value>
				<value>wangwu</value>
			</array>
		</property>
		<!-- 对象数组 -->
		<property name="someObjArray">
			<value>object</value>
			<bean class="java.util.Random" />
			<bean class="java.util.Date" />
		</property>
		<!-- collection -->
		<property name="collection">
			<!-- 底层是Arraylist -->
			<list>
				<value>zhangsan</value>
				<value>123</value>
				<bean class="java.util.Date"></bean>
			</list>
		</property>
		<property   name="someList">
			<list>
				<value>yanjun</value>
				<value>123</value>
				<bean   class="java.util.Date"   />
			</list>
		</property>
		<property   name="set">
			<set>
				<!-- 底层实现采用的是LinkedHashSet -->
				<value>yanjun</value>
				<value>yanjun</value>
				<bean   class="java.util.Date"   />
			</set>
		</property>
		<property   name="someMap">
			<map>
				<entry   key="abc"   value="123" />
				<entry   key-ref="userDao"   value="wertyu" />
				<entry   key="bbc"   value-ref="userDao" />
				<entry   key-ref="userv"   value-ref="userDao" />
			</map>
		</property>
		<property   name="ps">
			<props>
				<prop   key="abc">1234</prop>
				<prop   key="bbc">asdfjlasdkf</prop>
			</props>
		</property>
	</bean>

</beans>

2.自动装配

自动装配autowire协作者

具体开发实践中强烈不建在xml配置方式中使用,只在注解开发中可以使用

A.Java

package com.wpf.di;

public class A {
	private B b;
	
	public A(){
		System.err.println("构造A.....");
	}
	
	private A(B b) {
		this.b=b;
	}
	
	public void bb() {
		System.err.println("this is A.bb()........");
		System.err.println("调用被依赖对象b的方法");
		b.pp();
		System.err.println("this is A.bb()........");
	}

	public B getB() {
		return b;
	}

	public void setB(B b) {
		this.b = b;
	}
	
}

B.Java

package com.wfp.di;

public class B {
	public B(){
		System.err.println("构造B.....");
	}
	public void pp(){
		System.err.println("this is B.pp().........");
		System.err.println("this is B.pp().........");
	}
}

配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
	default-autowire="byName">
	<!-- 采用自动装配的方式注入依赖对象 -->
	<bean   id="aa"   class="com.yan.wpf.A"   autowire="byName" />
	<bean   id="b"   class="com.yan.wpf.B" />

</beans>

3.优点

Spring IoC容器可以自动装配(autowire)相互协作bean之间的关联关系

因此,如果可能的话,可以自动让Spring通过检查BeanFactory中的内容

来替我们指定bean的协作者(其他被依赖的bean)

优点包括:

a.自动装配能显著减少配置的数量。不过,采用bean模板(见这里)也可以达到同样的目的
b.自动装配可以使配置与java代码同步更新。例如,如果你需要给一个java类增加一个依赖,那么该依赖将被自动实现而不需要修改配置。因此强烈推荐在开发过程中采用自动装配,而在系统趋于稳定的时候改为显式装配的方式

4.自动装配有几种类型

a.no 是默认值,表示不使用autowiring。 必须显示的使用"<ref />"标签明确地指定bean合作者,对于部署给予更大的控制和明了

b.byName 根据属性名自动装配。此选项将检查容器并根据名字查找与属性完全一致的bean,并将其与属性自动装配。例如,在bean定义中将 autowire设置为by name,而该bean包含master属性(同时提供setMaster(..)方法),Spring就会查找名为master的bean定义,并用它来装配给master属性

c.byType 如果容器中存在一个与指定属性类型相同的bean,那么将与该属性自动装配。如果存在多个该类型的bean,那么将会抛出异常,并指出不能使用byType方式进行自动装配。若没有找到相匹配的bean,则什么事都不发生,属性也不会被设置。如果不希望这样,那么可以通过设置 dependency-check="objects"让Spring抛出异常

d.constructor 与byType的方式类似,不同之处在于它应用于构造器参数。如果在容器中没有找到与构造器参数类型一致的bean,那么将会抛出异常

e.default采用默认自动装配方法

5.缺点

a.尽管自动装配比显式装配更神奇,但是,正如上面所提到的,Spring会尽量避免在装配不明确的时候进行猜测,因为装配不明确可能出现难以预料的结果,而且Spring所管理的对象之间的关联关系也不再能清晰的进行文档化

b.对于那些根据Spring配置文件生成文档的工具来说,自动装配将会使这些工具没法生成依赖信息

c.自动装配可以减轻配置的工作量,但同时使得配置文件的可读性变得很差,因为你不可能从配置文件中获知这些对象之间得依赖关系,从而维护困难

C.代理模式

1.概述

在代理模式Proxy Pattern中,一个类代表另一个类的功能

这种类型的设计模式属于结构型模式

在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口

意图:为其他对象提供一种代理,以控制对这个对象的访问

主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上

在面向对象系统中,有些对象由于某些原因

(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问)

直接访问会给使用者或者系统结构带来很多麻烦

我们可以在访问此对象时加上一个对此对象的访问层

优点: 职责清晰,高扩展性,智能化

缺点: 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢;实现代理模式需要额外的工作,有些代理模式的实现非常复杂

使用场景:按职责来划分,通常有以下使用场景:

远程代理,Cache代理,防火墙(Firewall)代理

注意事项:

a.和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口

b.和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

2.静态代理模式

举例:球员转会,一般都是直接联系经纪人,减少球员的被访问量

经纪人就是代理,球员只负责踢球,现在模拟一下这个关系

接口:Player运动员

package org.wpf.spr;

public interface IPlayer {
	void play();
}

球员:SoccerPlayer

package org.wpf.spr;

public class SoccerPlayer implements IPlayer {

	@Override
	public void play() {
		System.out.println("球员上场踢比赛!");
	}

}

经纪人:ProxyPlayer

package org.wpf.spr;

public class ProxyPlayer implements IPlayer {

	// 定义
	private IPlayer player;
	
	public ProxyPlayer(IPlayer player) {
		super();
		this.player = player;
	}

	@Override
	public void play() {
		System.out.println("与球队签约!");
		this.player.play();
		System.out.println("收取转会费!");
	}

}

测试类:

package org.wpf.spr;

public class Test {
	public static void main(String[] args) {
		IPlayer player = new ProxyPlayer(new SoccerPlayer());
		// 从形式上看调用的是经纪人的踢球方法,但是实际上执行踢球的是球员,经纪人限制了对球员的直接访问
		player.play();
	}
}

3.动态代理模式

如果球员参与广告代言,是不还要有代言的经纪人

这样就需要再创建一个接口,会形成类爆炸

使用动态代理,不管执行哪个,都会经过代理的回调方法

代言接口

package org.wpf.spr;

public interface IShow {
	void show();
}

代理回调处理类,jdk提供了接口

package org.wpf.spr;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyProxy implements InvocationHandler {

	// 不知道接口是什么类型
	private Object obj;

	public MyProxy(Object obj) {
		super();
		this.obj = obj;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

		// 如果方法是play(),则为踢球
		if ("play".equals(method.getName())) {
			// 由代理对象限制目标对象的访问
			System.out.println("签约转会合同!");
		}
		
		// 如果方法时show(),则为代言
		if ("show".equals(method.getName())) {
			// 由代理对象限制目标对象的访问
			System.out.println("签约代言合同!");
		}
		
		Object res = method.invoke(obj, args);
		
		
		if ("play".equals(method.getName())) {
			System.out.println("收取转会费!");
		}
		
		if ("show".equals(method.getName())) {
			System.out.println("收取代言费!");
		}

		return res;
	}

}
测试类

package org.wpf.spr;

import java.lang.reflect.Proxy;

public class Test {
	public static void main(String[] args) {
		IPlayer player = (IPlayer) Proxy.newProxyInstance(IPlayer.class.getClassLoader(),
				new Class[] { IPlayer.class, IShow.class }, new MyProxy(new SoccerPlayer()));
		player.play();
	}
}

猜你喜欢

转载自blog.csdn.net/wpf719971425/article/details/79176225