Spring目标
- 基于POJO的轻量级和最小侵入性编程
- 通过依赖注入和面向接口实现松耦合
- 基于切面和惯例进行声明式编程
- 通过切面和模板减少样板式代码
起源
java应用程序中的对象是相互依赖的(对象间关系,最少也是依赖,不然什么都i做不了)。java平台提供了许多功能,但是缺少一种把各个对象,各个功能组织在一起的手段,而是将这个任务交给了设计者。虽然我们可以利用设计模式来组织类与对象,但是设计模式仅仅是提出了一种实践方案,需要自己实现设计模式的最佳实践。。
Spring Framework的IOC(控制反转)组件解决了这个问题,提供了一种将不同的组件组织在一起的手段。spring将形式化的设计模式编码为一类可以集成到自己应用中的对象。
进一步
不使用IOC之前,软件中的各个对象就像齿轮一样互相咬合在一起。通过使用IOC思想,将各对象分离开,借助“第三方”(IOC容器)来实现对象之间的解耦,好比引入一个中间齿轮,该齿轮和其他齿轮咬合,而其他齿轮之间不咬合。如果没有该齿轮,那么其他齿轮之间就没了关系,所以又称其为粘合剂。
为什么叫IOC
A依赖B,本来A需要自己创建或者使用B,总之控制权在A的手上。而使用了IOC后,A与B之间没有直接联系,当A需要B的时候,IOC容器会主动将B注入到A需要的地方,控制权不再A,而在IOC,所以就叫控制反转。
DI依赖注入
思想:很多情况下对象需要引用其他对象,那么会造成紧耦合。所以演变成,对象中引用的是接口,然后需要用到的对象继承该接口,再被装配进来。
public class Demo{ private Test test; public Demo(Test test){ this.test = test; } public void run(){ test.run(); } } public interface Test{ public void run(); } public Test1 implements Test{ public void run(){ System.out.println("测试") } }
上述代码就是DI思想
像这种非自己主动初始化依赖,而通过外部来传入依赖的方式,我们就称为依赖注入。外部如何传进来依赖呢?就需要spring的装配
2. 装配:Spring有xml装配和注解装配,通过配置,就是将Test1装配到Demo中,Demo并不知道到底装配的是什么,减少耦合。
3. 小结:装配:创建应用组件之间协作的行为;DI:通过DI,对象的依赖关系将由系统中负责协调各对象的第三方组件在创建对象的时候进行设定。
IOC和DI
IoC框架使用DI作为实现控制反转的方式,但是控制反转还有其他的实现方式。
IOC是一种思想,依赖注入是一种设计模式
Spring Framework结构图
Core Container
这是真的核心,因为本来spring就是为了解耦的问题提出,通过托管对象,DI来做。所以这层以上的都是额外的附加功能
core,beans
core和beans是最基础的,提供了IoC和DI特性。BeanFactory基于工厂模式(已经修改的很复杂了):主要是消除了工厂模式(问题1)要求的单例性以及加入了bean配置与具体实现的分离(问题2);
context
context模块基于beans和core模块。该模块之所以叫context,是因为其实现了spring风格的方式,类似于JNDI的注册对象方式。了解该模块有必要了解一下JNDI
JNDI
JNDI(java命名和目录接口),将名称和对象联系起来,使得我们可以通过名称来访问对象。为什么需要这样做呢?一个简单的例子
就像你找人一样,如果他还没有和你建立联系的话(建立联系就是保存一份对象的引用,如果两个对象彼此没有创建另外一个而且也没有被中间的第三方建立这种关系问题就出现了),请问你如何和他打交道?
现实中是:
a. 我们拨通 114
Java 中:Context ctx = new InitialContext();
b. 请问哪里有通马桶的?114 答,xxx… 为您转接中,请稍候。
Java 中:DataSource ds = (DataSource) ctx.lookup(“便民服务公司”);
c. 过了一会儿,人来了,你说:师付,请帮我通马桶吧。
Java 中: ds.getConnection();
再议spring中对象管理
spring为了解耦,把对象放到IoC容器中,可以认为context模块就是org.springframework.context.ApplicationContext,该容器负责实例化(用到了类似于JNDI的变形spring context),装配bean(用到了bean和core),装配我们已经了解,那么如何实例化,就是类似于JNDI,首先获得容器的引用,然后通过容器中存放的对象的名字来获取该对象的引用。
spring功能之DI与装配(wire)
“在spring中,对象无需自己查找或者创建与其相关联的对象,spring容器会把需要相互协作的对象引用赋予各个对象。Spring负责创建应用程序中的bean并通过DI来协调这些对象之间的关系。”
也即,要让spring帮你管理对象,首先要把对象注册到bean容器中,然后通过装配将容器中被需要的对象引用装配给容器中的需要该对象的bean
spring装配对象三种方案
- 自动化装配
(1) 组件扫描:Spring自动发现应用上下文中创建的bean
(2) 自动装配:Spring自动满足bean之间的依赖
其实最根本的注解就三个,一个是@component表示组件(也即容器中的bean?),一个是@componentscan表示开启组件扫描,一个是@autowired自动装配
需要与被需要装配的Bean都必须在spring中注册,那么通过@autowired会把被需要的bean装配进来。
但是面对第三方库中的组件就没办法了加@component注解,所以要用下面两种 java显示配置
使用@configuration和@Bean注解
@configuration注解表明这个类是一个配置类,该类应该包含如何创建bean的细节。
@Bean注解就类似于,在这里面创建bean。
创建好bean后再通过@autowired注入进来。
注:基于注解的使用,注入就通过@autowired注入
3.xml配置
忽略
注:xml配置好后如何注入呢?也在xml配置,比如配置<bean class="com.baobaotao.anno.LogonService"> <property name="logDao" ref="logDao"></property> <property name="userDao" ref="userDao"></property> </bean>
这是通过setter方法注入到需要的bean中(该bean必须有setter方法)
<bean class="com.baobaotao.anno.LogonService"> <constructor-arg ref="logDao"></constructor-arg> <constructor-arg ref="userDao"></constructor-arg> </bean>
这是通过构造器注入,需要bean的类构造器需要包含参数userDao,logDao等
spring装配bean时的高级操作
spring profile
可以指定在不同的环境下创建bean
bean的作用域
@scope注解指定不同的作用域:默认单例,原型作用域(每个需要被注入的地方都会创建一个新的bean),web会话(一个完整的session),web请求(一次请求)
条件化装配
依赖
依赖注入
- 通过构造器
- 通过setter方法
构造器注入
- 注入的是自定义类:和上面的一样
注入的是基本类型集合等非自定义类
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg type="int" value="7500000"/> <constructor-arg type="java.lang.String" value="42"/> </bean>
将type换成name,index都可以;type指的是参数类型,name就是参数名称,index就是参数序号。最好不要用name
setter方法注入
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using the nested ref element -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- setter injection using the neater ref attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
选择哪种注入方式
首选是构造器注入,好处在于:可以进行依赖的非空检查;可以通过构造器看出依赖项,多的话说明需要重构代码;可以设计出不变化的依赖项
而setter方法注入主要用在该类的属性值是确定的情况。可以使该类适合重新配置或者重新注入(因为就是一个setter方法么)。
构造器方法注入可能会引起循环依赖的问题,解决方法是改成setter注入
循环依赖:A依赖B,B依赖A,二者均采用构造器注入bean,那么这就成了先有鸡还是先有蛋的问题
依赖和配置细节
- p参数
- 引入 xmlns:p=”http://www.springframework.org/schema/p”
p:integerProperty=1
等价于<property name="integerProperty" value="1"/>
<property/>
中的value属性,可替换为<value/>
基于注解的容器配置
基于注解的容器配置,比xml更精简好用,但是需要侵入代码,配置分散。
Autowired
@Autowired
和@Resource
对比:@Resource标注的Bean在编译时是一定要在容器中配置好的(强制),而@Autowired是可选的- 如果类只有一个构造器,那么其实也不用加
@Autowired
注解,Spirng会自动帮忙注入的
配置元数据
@Controller,@Service,@Repository,都是基于@Component的,Spring会自动将他们转为@component。之所以分成这些注解,是为了更好的表明分层