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
applicationContext.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>
将连接池注入dao<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>
实现类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>
bean中要么不写构造方法( 未定义构造方法时,默认生成无参构造),要么就必须写无参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(); } }
习惯了传统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
如果再添加一个构造器,将参数列表调整,则报错消失
这就意味着Spring框架可以自动识别对应类型,调用最合适的构造器public Person(Date birth, int age, String username) { super(); this.birth = birth; this.age = age; this.username = username; }
如果数据具备一定的二义性时,可以考虑使用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; } }
通过序号强制定义这个参数的序号,注意序号从0开始算起<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>
通过构造器中形参名称强制定义这个参数,注意这个定义方法在Spring4-不支持<bean id="person" class="org.wpf.spr.Person"> <constructor-arg ref="now" /> <constructor-arg value="男" /> <constructor-arg value="Jack" index="2" /> </bean>
<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(); } }