这里所说的Spring框架是指SSM中的Spring,Spring是一个用于管理组件(Java类)的容器,它包含并管理对象的生命周期。Spring有两个核心技术,即IOC和AOP。
IOC
BeanFactory接口提供了管理任意类型的对象的预定义机制,提供了一些基础功能。
ApplicationContext接口是对继承了BeanFactory,它在BeanFactory的基础上实现更多的定制化的功能。该接口是IOC容器的具体体现,它负责实例化,配置和装配对象,这些对象以及对象之间的关系由配置文件(XML文件,Java注释)定义,容器会读取这些配置文件,从而创建和管理对象。
只需要向IOC容器提供简单的POJO类以及配置文件,IOC容器就会自动创建出对象并对对象进行管理
IOC容器创建对象:
实例化容器,需要向容器构造器提供字符串形式的bean配置文件资源路径:
实例化容器的同时,Spring会读取bean配置文件并实例化bean配置文件中配置的类;使用时,只需要调用容器的getBean方法即可
实例化容器并获取实例:// create and configure beans ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml"); // 拿到实例 PetStoreService service = context.getBean("petStore", PetStoreService.class); // 使用实例 List
userList = service.getUsernameList();
配置xml文件:
service.xml的一个例子:
然而,实际上不应该使用getBean或者类似的方法来获取实例而是通过配置文件,使用依赖注入的方法使用实例
dao.xml的例子
与service.xml基本相同
让bean定义跨多文件也是常见的做法:
1.3.1为bean起别名(不理解)
1.3.2实例化bean的三种方式
方法一: 使用构造函数进行实例化
在这种方式中,只需要满足(1)该类要有无参的构造函数,这一步是为了避免在实例化时直接提供属性的值,(2)提供各个属性的get和set方法,这一步是为了通过
示例:
note:建议当参数的数目超过四个时,就要提供无参的构造方法和为各个属性赋值的get和set方法,这样做的好处:
1.不用在实例化对象时就给出属性值
2.属性值可以灵活修改
方法二:使用静态工厂
这种方法不用创建工厂实例,因此工厂类中要有一个静态方法来创建bean实例。
配置实例:
class="examples.ClientService"
factory-method="createInstance"/>
静态工厂类示例:
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
方法三:使用实例工厂进行实例化
该方法实际上创建了工厂对象,因此不需要有返回bean对象的静态方法
配置示例:
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
实例工厂类示例:
public class DefaultServiceLocator {
//静态属性,类被加载的同时创建初始化该属性
private static ClientService clientService = new ClientServiceImpl();
//需要创建工厂实例,因此返回bean实例的方法不必要是静态方法
public ClientService createClientServiceInstance() {
return clientService;
}
}
另外,一个工厂也可以包含多个方法
1.4依赖项
依赖就是类之间的调用关系,如果一个类A在它的内部使用了另一个类B,那么就说这两个类之间产生了依赖。
依赖注入是个过程,就是将B类放入A类的这个过程,这个过程可以通过A类的构造函数实现,也可以通过set方法实现。
1.1.4依赖注入
一、基于构造函数的依赖注入
基于构造函数的 DI 是通过容器调用具有多个参数的构造函数来完成的,每个参数代表一个依赖项。
容器是通过参数类型进行参数匹配的,如果参数是不同类型并且没有继承关系,那么无需在
例:
package x.y;
public class ThingOne {
//两个参数属于不同类
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
如果参数类型是基本数据类型,由于提供的都是字符串,容器无法识别参数的目标类型,需要提醒容器参数的目标类型,然后Spring的类型转换才能将提供的字符串类型的值转换为目标类型
如果参数是同种类型,可以使用索引进行提示,索引下标从0开始
最好用的还是直接使用构造函数参数名称(不是属性名)进行提示
二、基于set方法的依赖注入
基于 Setter 的 DI 是通过容器在调用无参数构造函数或无参数static工厂方法来实例化bean后调用bean的setter方法来完成的。
总结:基于构造函数和基于静态工厂,基于工厂方式创建Bean实例的时候,都是通过
静态工厂注入:
基于构造函数和基于set方法可以混用,那么如何选择呢?
推荐对于必须的属性,通过构造函数进行注入,而对于可选属性以及可能修改的属性,通过get方法进行注入
1.4.2详细的依赖配置
idref标签:引用另一个bean的id值,而不是真正的bean对象。
效果等同于下边的这个,但是能防止theTargetBean这个bean不存在而引起错误
内部bean,它仅存在于一个bean的内部,其它bean无法调用它,因此也不需要id(即使设置了,容器也不会将它的id看作标识符)
集合与集合的合并
[email protected]
[email protected]
[email protected]
just some string
子bean中的集合可以继承(合并)父bean中的集合,如果集合是有序的,父集合中的元素会在子集合的元素之前
[email protected]
[email protected]
[email protected]
[email protected]
强类型集合
如果在类中定义了集合中元素的类型,那么在进行依赖注入时,Spring的类型转换机制会自动的将传递的参数转换为合适的类型
public class SomeClass {
private Map accounts;
public void setAccounts(Map accounts) {
this.accounts = accounts;
}
}
空和null
等价于exampleBean.setEmail("");
而
等价于exampleBean.setEmail(null);
p属性与
depends-on属性可以设置创建和销毁的时间依赖,实际上,depends-on显式指定了一个类对另一个类的依赖关系,创建该bean前,会先创建被依赖的类的实例,销毁该bean前,也会先销毁被依赖的bean的实例。
1.4.4 延迟加载
容器在启动时会创建bean的单例:默认情况下,作为初始化过程的一部分,ApplicationContext实现会急切地创建和配置所有 单例bean。通常,这种预实例化是可取的,因为配置或周围环境中的错误会立即发现,而不是几小时甚至几天之后延迟加载lazy-init属性:将原本实例化bean的时间从容器启动时延迟到容器被第一次请求时
但是当延迟加载的bean是另一个单例bean的依赖时,容器仍会在启动时创建该延迟加载bean的实例,原因是要满足另一个bean的依赖关系,这种情况下可以使用容器级别的延迟加载属性:default-lazy-init
1.4.5 自动装配
使用自动装配,Spring通过Java的反射机制在实例化bean时可以自动的为bean的属性,方法入参装配。这时就不用在配置文件中手动配置关联类
自动装配属性:
package com.baobaotao;
import org.springframework.beans.factory.annotation.Autowired;
public class Boss {
@Autowired
private Car car;
@Autowired
private Office office;
…
}
配置文件:
默认不使用自动装配,如果使用自动装配,默认是byType,除此之外还有byName,constructor
bean的作用范围
容器创建的bean实例默认是单例的
容器在创建bean实例时,关于bean实例的范围有四个可选项:
1.singleton 对于每个Spring IoC容器的单个bean实例的单个bean定义的范围 只创建该bean的唯一实例,所有请求和引用都只使用这个实例
2.prototype 对于任何对象实例的单个bean定义的范围,每次对该bean的请求都创建该bean的实例(向容器请求ClassB,容器会创建并返回ClassB的实例:)
applicationContext.getBean(ClassB.class);
建议将有状态(属性)的bean创建为prototype,无状态的创建为单例模式
3.request 每次HTTP请求生命周期的单个bean定义范围;即,每个HTTP请求返回一个bean实例。仅在ApplicationContext的上下文中有效
4.session 单个bean定义的HTTP会话生命周期的范围。仅在ApplicationContext的上下文中有效
5.global session 单个bean定义的全局HTTP会话的生命周期。一般地在门户导入的信息组件的上下文中有效。仅在ApplicationContext的上下文中有效
6.application 单个bean定义的一个ServletContext的生命周期。仅在ApplicationContext的上下文中有效
与其他范围相比,Spring 不管理原型 bean 的完整生命周期。容器实例化、配置和以其他方式组装原型对象并将其传递给客户端由客户端进行管理,而没有该原型实例的进一步记录。因此原型bean所占用的资源要手动释放。
1.4.6 方法注入
控制反转:原本创建对象是一个对象需要另一个对象时,就显式的创建一个对象出来。控制反转是将对象的创建权交给容器,容器统一创建并管理对象,当一个对象需要依赖另一个对象时,容器将创建好的对象注入给它。
注意由于对象有单例的和非单例的(prototype),因此当一个单例的对象依赖另一个非单例的对象时,使用原本的注入方法就会出现问题:单例对象仅在容器被初始化时创建一次,此时它依赖的非单例对象也被注入,后续使用单例对象时不会再创建新的非单例对象,这与非单例对象的初衷相违背,还会造成线程安全问题。
此时有两种解决办法
第一种:显式的调用Spring容器,创建一个新的B类的实例。
缺点是造成造成Spring的强耦合
@Component
public class ClassA implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void printClass() {
System.out.println("This is Class A: " + this);
getClassB().printClass();
}
public ClassB getClassB() {
return applicationContext.getBean(ClassB.class);
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
第二种方法就是使用方法注入,这是针对方法的注入方式,容器会根据方法的返回值创建一个与方法返回值类型相同的新的对象。从而不用显式的调用容器,实现解耦
基于注解:
@Component
public class ClassA {
public void printClass() {
System.out.println("This is Class A: " + this);
getClassB().printClass();
}
@Lookup
public ClassB getClassB() {
return null;
}
}
使用@Lookup标签对方法的签名有所要求,要满足:
[abstract] theMethodName(no-arguments);
基于xml:
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?在xml文件中进行配置,容器会创建一个与该方法返回值类型相同的对象
protected abstract Command createCommand();
}
xml文件:
作用域 Bean 作为依赖项
当单例bean依赖于作用域bean(request,session等等)时,就会出现问题。单例bean是在容器初始化时创建的,此时容器会试图注入单例bean依赖的作用域bean,但由于此时还不在作用域bean的生命周期中(一次请求或者一次会话),因此作用域bean还没有被创建,所以依赖就会出现错误。
解决这一问题的方法是使用代理,创建单例bean时注入依赖项的代理而不是依赖项本身(本身尚未被创建),该代理暴露与依赖项相同的方法,因此单例bean会将它当作依赖项。当单例调用依赖项的方法时,真正的依赖项已经被创建,代理会将调用交给真正的依赖项进行处理。
代理分为两类,一类是基于接口的代理(依赖项是一个接口);另一类是基于类的代理(依赖项是一个类);使用xml配置默认创建的是基于类的代理
基于类的代理:
基于接口的代理
1.6自定义bean的性质
1.6.1生命周期回调
回调由三种方式:1.基于注解的方式;2.基于xml配置文件;3.实现接口
实现接口方式会造成耦合,因此不推荐使用这种方式
三种方式的执行顺序(回调方法名不同时每种配置的方法都执行一次。如果方法名相同,只执行一次)为:
初始化回调:类构造器->注解——>接口——>xml配置
销毁回调:注解——>接口——>xml配置
三种方法的示例:
1.基于注解
public class User {
@PostConstruct
public void init() {
//添加初始化动作
}
}
基于xml
public class ExampleBean {
public void init() {
// do some initialization work
}
}
基于继承
public class AnotherExampleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
// do some initialization work
}
}
Spring 容器保证在为 bean 提供所有依赖项后立即调用配置的初始化回调,这意味着 AOP 拦截器等尚未应用于 bean。
1.6.2 ApplicationContextAware和BeanNameAware
可以通过实现ApplicationContextAware接口来获取ApplicationContext(容器),但是不推荐使用这种方式,因为会造成代码和Spring框架的耦合(代码知道了Spring的存在),推荐使用自动装配方式获取。
自动装配方式
只需要在类中定义ApplicationContext属性,并在该属性上添加@Autowired标签,框架就会在容器中寻找与该属性相匹配的对象(byType,byName)
@Autowired
ApplicationContext context;
ApplicationContextAware接口
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
还有一种方式是通过监听器获取ApplicationContext对象
https://blog.csdn.net/asd0001...
1.7 Bean继承
子bean可以继承父bean的范围,构造函数参数值,属性值,并且可以选择添加新值。为子bean指定的范围,初始化方法,销毁方法和静态工厂方法都会覆盖父bean的设置。
如果将父bean设置为抽象bean,那么该父bean将专门用于被继承,不能被实例化。