Web应用程序由大量的程序组件组成,这些组件一起工作完成业务逻辑的执行。 这些组件通常是一个依赖另一个,互相协作实现所需功能。
Spring提供容器,也就是IoC容器,来管理这些组件,我们叫做Bean,Spring容器负责创建、初始化和销毁其管理的各种Bean,并根据Bean的依赖关系注入依赖的Bean,也就是依赖注入(DI)。
Spring IoC容器的类型:
下面给出一个例子,结合使用面向接口编程(将方法的声明/定义放入接口,将方法的具体实现放入实现类),使用IoC实现解耦(decoupling)
假设我们有若干个动物类:狗、猪、羊,它们都要吃东西,有一个方法eat()。
我们将这个方法的定义放入接口:
public interface Animal {
void eat();
}
将这个方法的具体实现放入实现类:
public class Dog implements Animal{
public Dog() {
System.out.println("实例化了Dog!!!");
}
@Override
public void eat() {
System.out.println("这是一只狗,狗吃骨头!");
}
}
使用IoC思想,将bean交给容器管理。若需要换掉使用的bean,修改spring配置文件中的bean类型即可,实现降低程序的耦合性,方便后期维护
在XML文件中定义一个bean:
在主类中,首先加载Spring配置文件,创建并启动容器
ApplicationContext ctx
=new ClassPathXmlApplicationContext("config/spring-beans.xml");
需要该bean的地方通过调用getBean()或依赖注入获取bean
Animal animal1=(Animal)ctx.getBean("animal");
animal1.eat();
容器控制着bean的创建和销毁。
为了执行一些自定义代码,容器提供了回调方法,回调方法主要包括:
Spring框架提供了以下方法来控制Bean的生命周期事件:
a. 使用InitializingBean和DisposableBean回调接口,实现初始化和释放资源任务。
public class Pig implements InitializingBean,DisposableBean{
public Pig() {
System.out.println("实例化了Pig!!!");
}
public void eat() {
System.out.println("这是一只猪,猪吃饲料~");
}
@Override
public void afterPropertiesSet() throws Exception {
//在这里编写你的初始化工作的代码
System.out.println("......初始化代码.");
}
@Override
public void destroy() throws Exception {
//在bean被摧毁前,如果要执行一些任务,在此方法中编写代码
System.out.println("~~~~~~我要die了,在这里做点释放资源的操作");
}
}
b. bean配置文件中的自定义init()和destroy()方法
1. 局部定义
可通过bean标记的init-method属性指定初始化时要调用的方法
可通过bean标记的destroy-method属性指定bean被摧毁前要调用的方法
在class中声明的Dog类中实现自定义init()和destory()方法:
public class Dog {
//方法名自定义,但是方法不能有参数
//自定义的一个方法init,用于包含初始化工作代码
public void init() {
System.out.println("调用了Dog类中的init......");
}
//方法名自定义,但是方法不能有参数
//自定义的一个方法dipsose,用于包含bean摧毁前要执行的工作代码
public void dispose() {
System.out.println("调用了Dog类中的dispose......");
}
public Dog() {
System.out.println("实例化了Dog!!!");
}
public void eat() {
System.out.println("这是一只狗,狗吃骨头!");
}
}
2. 全局定义
可全局配置初始化方法和摧毁bean之前要调用的方法。
可通过配置
可通过配置
考虑到代码重复利用,可以考虑保持各个类的初始化方法和摧毁前要调用的方法同名
c. @PostConstruct 和 @PreDestroy注解
使用@PostConstruct注解和@PreDestroy注解修饰自定义的init和destroy方法。
如果希望一个方法在bean初始化的时候被调用,执行初始化工作,则使用@PostConstruct注解修饰该方法:
@PostConstruct
public void init() {
System.out.println("调用了Sheep类中的init......");
}
如果希望一个方法在bean被摧毁之前被调用,执行释放资源等工作,则使用@PreDestroy注解修饰该方法:
@PreDestroy
public void cleanUp() {
System.out.println("调用了Sheep类中的cleanUp......");
}
d. 用于注入特定依赖的Aware接口
有时候可能需要获取某个特定的依赖,如想获取bean所在的上下文容器,则可以实现对应的Aware接口,并实现对应的方法
spring提供一些xxxAware接口,用于注入需要的一些依赖,例如,用于获取bean所属的上下文容器。如果希望在一个bean中获取bean所属的上下文容器,可以让bean实ApplicationContextAware
public class Mouse implements ApplicationContextAware{
//使用一个引用变量,用于引用所属的容器对象
private ApplicationContext ctx;
public Mouse() {
System.out.println("实例化Mouse!!!");
}
public void eat() {
System.out.println("这是一只Mouse,老鼠吃大米!!!");
Cat cat=ctx.getBean(Cat.class);
cat.eat();
}
//该方法将bean所属的容器对象传入该类,使用一个变量接收,然后就可以使用上下文容器了
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ctx=applicationContext;
}
}
DI(Dependency Injection)- 依赖注入是IoC思想的一种实现方式,就是将 依赖对象的创建和维护转移到 被依赖对象类的外部 来实现。
也就是说:一个对象是被动接受依赖对象而不是自己主动去找,换句话说就是对象自己不需要负责寻找或创建它所依赖的对象。
而是由容器(container)来维护对象之间的依赖关系,并根据依赖关系将需要的对象注入。
如果对象A中使用了对象B,则对象A依赖对象B,不直接在A中new一个B对象,而是交给容器,让容器根据A和B的依赖关系,实例化B,并将B对象传递给A,这个过程就是依赖注入。
通过value指定要注入的值
spring还提供一种简写形式 : p:属性名="属性值"
在Address类中,必须要给对应的属性生成setter访问器,因为要使用setXXX()方法来注入/设置需要的值
public class Address {
private String province; //省
private String city; //市
private String detailedAddress; //详细地址
@Override
public String toString() {
return "地址::" + province + "省" + city + "市" + detailedAddress;
}
public void setProvince(String province) {
System.out.println("setProvince()方法被调用........");
this.province = province;
}
public void setCity(String city) {
this.city = city;
}
public void setDetailedAddress(String detailedAddress) {
this.detailedAddress = detailedAddress;
}
}
使用ref属性指定要注入的bean的id或name
引用的其他bean使用p:属性名-ref="引用的bean的id或name"
set标记用于注入set集合的值
山东分公司
山西分公司
广东分公司
广西分公司
海南分公司
list标记用于注入list集合的值
Oracle
Microsoft
IBM
Google
map标记用于注入map集合的值
注入Properties集合,可以使用props标记或value标记
1000000
小胖
Joint venture
registeredCapital=2000000
legalPerson=Allen
type=Joint venture
constructor-arg标记用于实现以setter的方式注入依赖。
如果使用setter注入,constructor-arg标记的name属性是类中的属性名;
如果使用构造函数注入,constructor-arg标记的name属性指定的是构造函数的参数的名称。
使用构造函数注入,需要生成对应参数的构造函数
使用构造函数注入,也可不写参数名称,但是必须保证参数的顺序是对的。
可以使用参数索引(0表示第一个参数),这样参数顺序随便写。
但是如果不写参数名称,也不写参数索引,则参数顺序必须正确。
spring还提供一种简写形式 : c:参数名="值"
在Address类中要实现构造函数:
public class Address {
private String province; //省
private String city; //市
private String detailedAddress; //详细地址
//如果使用property标记实现依赖注入,则必须要给对应的属性生成setter访问器
//因为使用setXXX()方法来注入/设置需要的值
@Override
public String toString() {
return "地址::" + province + "省" + city + "市" + detailedAddress;
}
public Address() {
super();
}
public Address(String pro, String city, String detailedAddress) {
System.out.println("调用了带3个参数的构造函数实现依赖注入!!!");
this.province = pro;
this.city = city;
this.detailedAddress = detailedAddress;
}
public void setProvince(String province) {
System.out.println("setProvince()方法被调用........");
this.province = province;
}
public void setCity(String city) {
this.city = city;
}
public void setDetailedAddress(String detailedAddress) {
this.detailedAddress = detailedAddress;
}
}
使用ref属性指定要注入的bean的id或name
如果使用c:参数名写法,则简单值使用c:参数名="参数值"
引用的其他bean使用c:参数名-ref="引用的bean的id或name"
set标记用于注入set集合的值
山东分公司
山西分公司
广东分公司
广西分公司
海南分公司
list标记用于注入list集合的值
Oracle
Microsoft
IBM
Google
map标记用于注入map集合的值
注入Properties集合,可以使用props标记或value标记
1000000
小胖
Joint venture
registeredCapital=2000000
legalPerson=Allen
type=Joint venture
在应用程序对象之间创建和管理关联的过程叫做装配,这构成了DI的核心。以上两种方法为显示装配(显式声明Bean之间的依赖),自动装配(autowiring)不显式声明Bean之间的依赖,让容器选择合适的依赖项注入。
byName是按照名称自动装配,意思是要注入的属性的属性名和要注入的bean的id或name(别名)相同,即可实现按照名称自动装配。
byName
通过匹配 bean 的 id是否跟 setter 对应,对应则自动装配。比如以上代码,bean的id为person;如果我的person类中有一个setName(),则能够自动装配。
public interface Person {
void kanChai();
}
public class Boy implements Person {
private String name;
private Axe axe;
private Pet pet;
public void setAxe(Axe axe) {
this.axe = axe;
}
public void setName(String name) {
this.name = name;
}
public void setPet(Pet pet) {
this.pet = pet;
}
@Override
public void kanChai() {
System.out.println("砍柴人:"+name);
System.out.println("Boy砍柴,力气大!!!");
axe.cut();
pet.showSkill();
}
}
当一个bean带有autowire byName的属性时:
byType是按照类型自动装配。如果要注入的属性的数据类型和容器中某个bean的类型匹配,则实现自动装配。
自动装配跟 id 无关,在配置bean 时不写 id 都不会报错。
但是 byType
的自动装配存在一个很严重的问题,因为不是通过唯一的 id 来匹配,而是通过类型来匹配,所以容器中不能存在多个相同类型的 bean,否则会抛出NoUniqueBeanDefinitionException异常。
constructor表示使用对应的构造函数实现自动装配,所以需要生成对应的构造函数
public class Girl implements Person {
private String name;
private Axe axe;
private Pet pet;
@Override
public void kanChai() {
System.out.println("砍柴人:"+name);
System.out.println("Girl砍柴,力气更大!!!");
pet.showSkill();
axe.cut();
}
public Girl() {
super();
}
//如果使用构造函数实现自动装配,需要生成对应的构造函数
//使用constructor实现自动装配,只要构造函数的数据类型对了即可,参数名无所谓
public Girl(Axe a, Pet p) {
super();
System.out.println("带2个参数的构造函数~~~");
this.axe = a;
this.pet = p;
}
public void setName(String name) {
this.name = name;
}
public void setAxe(Axe axe) {
this.axe = axe;
}
public void setPet(Pet pet) {
this.pet = pet;
}
}
除了使用bean标记注册类为spring管理的bean,也可直接开启包扫描:component-scan标记用于开启组件的包扫描,指定包名,多个包名用逗号隔开。
默认会扫描指定包下使用了@Component、@Controller、@Service、@Repository等注解修饰的类,将类注册为spring管理的bean。
@Component("person")
public class Girl implements Person {
private String name;
@Autowired
private Axe axe;
//@Resource(name="dog")
//@Resource
private Pet pet;
@Autowired
public Girl(Axe axe) {
System.out.println("用于注入axe属性的构造函数~~~");
this.axe=axe;
}
@Autowired
public void setPet(Pet pet) {
System.out.println("用于注入pet属性的setter方法~~~");
this.pet=pet;
}
@Override
public void kanChai() {
System.out.println("砍柴人:"+name);
System.out.println("Girl砍柴,力气更大!!!");
axe.cut();
pet.showSkill();
}
}
如果使用@Autowired,那么是按照类型进行自动装配;如果存在多个匹配类型,会抛出异常 可使用@Qualifier注解进一步限定要注入的bean,这个注解不能在构造函数前面使用
@Component
public class Girl implements Person {
private String name;
private Axe axe;
@Autowired
@Qualifier("pig")
private Pet pet;
public Girl() {
}
public Girl(Pet pet) {
System.out.println("调用构造函数注入pet属性..................");
this.pet = pet;
}
//qualifier 限定符
@Autowired
@Qualifier("steelAxe")
//@Required //表示该属性必须要注入,在spring5.1开始,已经废除,不用关注了,直接使用构造函数注入替代即可
public void setAxe(Axe axe) {
System.out.println("调用setAxe()注入axe属性..................");
this.axe = axe;
}
public void setName(String name) {
this.name = name;
}
public void setPet(Pet pet) {
this.pet = pet;
}
@Override
public void kanChai() {
System.out.println("Girl砍柴,力气更大!!!");
axe.cut();
pet.showSkill();
}
}
上述代码中,我们使用 @Qualifier 注解,并指定一个唯一的值,选择我们要注入的bean
如pet我们选择注入Pig类,Pig类上@Component并未指定bean的名称,故使用默认名称。
在使用bean标记注册一个bean为spring管理的bean时,可使用parent属性指定继承的bean的id或name,这样一个bean会继承另一个bean的配置和依赖注入(如init-method)。
如果又单独针对某个属性指定了依赖注入,会覆盖继承的依赖注入。
默认情况下,spring容器调用无参构造函数创建bean实例。如果希望调用指定方法创建交给spring容器管理的bean实例,可通过bean标记的factory-method属性指定工厂方法的名称。
如果想通过一个工厂类的静态工厂方法创建bean,可通过factory-method属性指定创建实例的方法的名称。class指定工厂类的类型,也就是工厂类的全限定名称。
A类的代码:
public class A {
//即使构造函数是私有的,spring也能通过反射访问该构造函数,实现实例化
private A() {
System.out.println("A的私有构造函数!!!");
}
//有的时候,我们不希望直接简单调用构造函数创建一个对象
//有的时候,我们希望spring通过调用我们指定的方法来获取bean的对象
public static A getA() {
System.out.println("调用了A类的静态工厂方法返回A的实例!!!");
return new A();
}
public void methodOfA() {
System.out.println("调用了A类的方法MethodOfA.............");
}
}
假设有一个Animal接口,在此基础上我们实现了dog、pig、sheep类,我们要调用Animal的静态方法来获取dog的实例。
其中,class指定的应该是工厂方法所在的类的名称,factory-method属性指定创建实例的方法的名称。 实际创建的对象不是AnimalFactory类型,而是dog类型
AnimalFactory类:
public class AnimalFactory {
public static Animal getAnimal1() {
System.out.println("调用AnimalFactory类的静态工厂方法getAnimal1().................");
return new Dog();
}
}
接上例,这次我们希望在工厂类AnimalFactory中用非静态方法实例化一个pig类
首先我们要在XML配置文件中注册AnimalFactory
如果希望通过工厂类A的非静态方法来实例化B ,这时候不再使用class属性来指定生成的bean的类型。
factory-bean属性指定创建bean实例(如这里的Pig)的工厂对象是哪个,指定其id或name。
factory-method属性指定创建bean实例的工厂方法的名称。
id为animal的bean实际是一个Pig实例,也即是getAnimal2()方法返回的实例
AnimalFactory类:
public class AnimalFactory {
public Animal getAnimal2() {
System.out.println("调用AnimalFactory类的实例工厂方法getAnimal2().................");
return new Pig();
}
}
接上例,我们希望通过AnimalFactory这个工厂类的一个带参数的静态方法getAnimal3(int type)来创建spring管理的实例。
constructor-arg在构造函数注入的时候是构造函数参数的名称,但是对于工厂方法,就是指定一个普通方法参数的信息(如这里传入的参数是type,传入的值为2)
AnimalFactory类:
public class AnimalFactory {
private static Map map;
static {
map=new HashMap<>();
map.put(1,new Dog());
map.put(2,new Pig());
map.put(3,new Sheep());
}
public static Animal getAnimal3(int type) {
System.out.println("调用AnimalFactory类的静态工厂方法getAnimal3().................");
return map.get(type);
}
}