Spring基础
Spring是为了简化开发而生,它是轻量级的IoC和AOP的容器框架,主要是针对Bean的生命周期进行管理的轻量级容器,并且它的生态已经发展得极为庞大。
IoC容器基础
IoC理论
之前的Web应用各层角色分工都明确,流水线上的一套操作必须环环相扣,这是一种高度耦合的体系。目前App更新频繁,对于高耦合代码出现问题,无法高效进行维护和更新。
IOC是Inversion of Control的缩写,翻译为:“控制反转”,把复杂系统分解成相互合作的对象,这些对象类通过封装以后,内部实现对外部是透明的,从而降低了解决问题的复杂度,而且可以灵活地被重用和扩展。
将对象交给IoC容器进行管理,不用再关心我们要去使用哪一个实现类了,我们只需要关心,给到我的一定是一个可以正常使用的实现类,能用就完事了。
public static void main(String[] args) { A a = new A(); a.test(IoC.getBean(Service.class)); //容器类 //比如现在在IoC容器中管理的Service的实现是B,那么我们从里面拿到的Service实现就是B } class A{ private List<Service> list; //一律使用Service,具体实现由IoC容器提供 public Service test(Service b){ return null; } } interface Service{ } //使用Service做一个顶层抽象 class B implements Service{} //B依然是具体实现类,并交给IoC容器管理
当具体实现类发生修改时,我们同样只需要将新的实现类交给IoC容器管理,这样我们无需修改之前的任何代码。
高内聚,低耦合,是现代软件的开发的设计目标,而Spring框架就给我们提供了这样的一个IoC容器进行对象的的管理,一个由Spring IoC容器实例化、组装和管理的对象,我们称其为Bean。
第一个Spring程序
创建实体类:
@ToString public class Student { }
注册Bean:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean name="student" class="com.test.bean.Student"/> </beans>
获取Bean对象:
public class Test { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("test.xml"); Student student = context.getBean(Student.class); System.out.println(student); } }
这个对象不需要我们再去创建了,而是由IoC容器自动进行创建并提供,我们可以直接从上下文中获取到它为我们创建的对象,这里得到的Student对象是由Spring通过反射机制帮助我们创建的
Bean注册与配置
配置一个Bean,并指定对应的类:
<bean class="com.test.bean.Student"/>
据类型向容器索要Bean实例对象:
ApplicationContext context = new ClassPathXmlApplicationContext("test.xml"); //getBean有多种形式,其中第一种就是根据类型获取对应的Bean //容器中只要注册了对应类的Bean或是对应类型子类的Bean,都可以获取到 Student student = context.getBean(Student.class);
为Bean指定一个名称用于区分:
<bean name="a" class="com.test.bean.Student"/> <bean name="b" class="com.test.bean.Student"/>
<bean name="a" class="com.test.bean.Student"/> <alias name="a" alias="test"/>#为Bean创建别名
默认情况下,通过IoC容器进行管理的Bean都是单例模式的,这个对象只会被创建一次。
对象作用域配置:
singleton
,默认单例,全局唯一实例; prototype
,原型模式,每次获取新的对象
当Bean的作用域为单例模式时,那么容器加载配置时就被创建,之后拿到的都是这个对象,容器没有被销毁,对象将一直存在;而原型模式下,只有在获取时才会被创建
单例模式下的Bean懒加载(获取时再加载):
<bean class="com.test.bean.Student" lazy-init="true"/>
维护Bean的加载顺序:
<bean name="teacher" class="com.test.bean.Teacher"/> <bean name="student" class="com.test.bean.Student" depends-on="teacher"/>
xml配置文件是可以相互导入的:
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <import resource="test.xml"/> </beans>
依赖注入
依赖注入(Dependency Injection, DI)是一种设计模式,也是Spring框架的核心概念之一。
实体类需要其他实体类,创建时保证其他实体类也能进行动态加载。
public class Student { private Teacher teacher; //要使用依赖注入,我们必须提供一个set方法(无论成员变量的访问权限是什么)命名规则依然是驼峰命名法 public void setTeacher(Teacher teacher) { this.teacher = teacher; } ...
public interface Teacher { void teach(); } public class ArtTeacher implements Teacher{ @Override public void teach() { System.out.println("我是美术老师,我教你画画!"); } } public class ProgramTeacher implements Teacher{ @Override public void teach() { System.out.println("我是编程老师,我教你学Golang!"); } }
有了依赖注入之后,Student中的Teacher成员变量,可以由IoC容器来选择一个合适的Teacher对象进行赋值,也就是说,IoC容器在创建对象时,需要将我们预先给定的属性注入到对象中
使用property
标签:
<bean name="teacher" class="com.test.bean.ProgramTeacher"/> <bean name="student" class="com.test.bean.Student"> <property name="teacher" ref="teacher"/> </bean>
name指定成员对象名,ref指定依赖的Bean名称
依赖注入并不一定要注入其他的Bean,也可以是一个简单的值:
<bean name="student" class="com.test.bean.Student"> <property name="name" value="卢本伟"/> </bean>
在构造方法中去完成初始化:
public class Student { private final Teacher teacher; //构造方法中完成,所以说是一个final变量 public Student(Teacher teacher){ //Teacher属性是在构造方法中完成的初始化 this.teacher = teacher; } ...
IoC容器默认只会调用无参构造,需要指明一个可以用的构造方法
constructor-arg
标签:
<bean name="teacher" class="com.test.bean.ArtTeacher"/> <bean name="student" class="com.test.bean.Student"> <constructor-arg name="teacher" ref="teacher"/> </bean>
constructor-arg
就是构造方法的一个参数,这个参数可以写很多个,会自动匹配符合里面参数数量的构造方法
type指定参数的类型
<constructor-arg value="1" type="int"/>
其他特殊类型:
<bean name="student" class="com.test.bean.Student"> <!-- 对于集合类型,我们可以直接使用标签编辑集合的默认值 --> <property name="list"> <list> <value>AAA</value> <value>BBB</value> <value>CCC</value> </list> </property> </bean>
<bean name="student" class="com.test.bean.Student"> <property name="map"> <map> <entry key="语文" value="100.0"/> <entry key="数学" value="80.0"/> <entry key="英语" value="92.5"/> </map> </property> </bean>
自动装配
自动装配就是让IoC容器自己去寻找需要填入的值,只需要将set方法提供好就可以了
autowire
属性有两个值普通,一个是byName,还有一个是byType,顾名思义,一个是根据类型去寻找合适的Bean自动装配,还有一个是根据名字去找,就不需要显式指定property
了
<bean name="student" class="com.test.bean.Student" autowire="byType"/>
byName自动装配,依据的Name是setter方法的后缀和bean的name属性进行匹配:
public class Student { Teacher teacher; public void setArt(Teacher teacher) { this.teacher = teacher; } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean name="art" class="com.test.bean.ArtTeacher"/> <bean name="student" class="com.test.bean.Student" autowire="byName"/> </beans>
使用构造方法完成的依赖注入,也支持自动装配:
<bean name="student" class="com.test.bean.Student" autowire="constructor"/>
当自动装配发生歧义时:
Bean属性autowire-candidate
设定false时,这个Bean将不再作为自动装配的候选Bean
Bean设定primary属性,当出现歧义时,也会优先选择
<bean name="teacher" class="com.test.bean.ArtTeacher" primary="true"/> <bean name="teacher2" class="com.test.bean.ProgramTeacher" autowire-candidate="false"/> <bean name="student" class="com.test.bean.Student" autowire="byType"/>
生命周期与继承
除了修改构造方法,我们也可以为Bean指定初始化方法和销毁方法,以便在对象创建和被销毁时执行一些其他的任务:
public void init(){ System.out.println("我是对象初始化时要做的事情!"); } public void destroy(){ System.out.println("我是对象销毁时要做的事情!"); }
通过init-method
和destroy-method
来指定:
<bean name="student" class="com.test.bean.Student" init-method="init" destroy-method="destroy"/>
如果Bean不是单例模式,而是采用的原型模式,那么就只会在获取时才创建,并调用init-method,而对应的销毁方法不会被调用(因此,对于原型模式下的Bean,Spring无法顾及其完整生命周期,而在单例模式下,Spring能够从Bean对象的创建一直管理到对象的销毁)
Bean之间也是继承属性的:
public class SportStudent { private String name; public void setName(String name) { this.name = name; } } public class ArtStudent { private String name; public void setName(String name) { this.name = name; } }
配置Bean之间的继承关系了,我们可以让SportStudent这个Bean直接继承ArtStudent这个Bean配置的属性:
<bean name="artStudent" class="com.test.bean.ArtStudent"> <property name="name" value="小明"/> </bean> <bean class="com.test.bean.SportStudent" parent="artStudent"/>
在ArtStudent Bean中配置的属性,会直接继承给SportStudent Bean(注意,所有配置的属性,在子Bean中必须也要存在,并且可以进行注入,否则会出现错误)
如果子类中某些属性比较特殊,也可以在继承的基础上单独配置:
<bean name="artStudent" class="com.test.bean.ArtStudent" abstract="true"> <property name="name" value="小明"/> <property name="id" value="1"/> </bean> <bean class="com.test.bean.SportStudent" parent="artStudent"> <property name="id" value="2"/> </bean>
只是希望某一个Bean仅作为一个配置模版供其他Bean继承使用,那么我们可以将其配置为abstract:
<bean name="artStudent" class="com.test.bean.ArtStudent" abstract="true"> <property name="name" value="小明"/> </bean> <bean class="com.test.bean.SportStudent" parent="artStudent"/>
一旦声明为抽象Bean,那么就无法通过容器获取到其实例化对象了
全文Bean属性配置:
整个上下文中所有的Bean都采用某种配置,我们可以在最外层的beans标签中进行默认配置
即使Bean没有配置某项属性,但是只要在最外层编写了默认配置,那么同样会生效,除非Bean自己进行配置覆盖掉默认配置。
工厂模式和工厂Bean
正常情况下需要使用工厂才可以得到Student对象,现在我们希望Spring也这样做,不要直接去反射搞构造方法创建
public class Student { Student() { System.out.println("我被构造了"); } } public class StudentFactory { public static Student getStudent(){ System.out.println("欢迎光临电子厂"); return new Student(); } }
通过factory-method进行指定:
<bean class="com.test.bean.StudentFactory" factory-method="getStudent"/>
public class Test { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("test.xml"); Student student = context.getBean(Student.class); System.out.println(student); } }
Bean类型需要填写为Student类的工厂类,并且添加factory-method指定对应的工厂方法
某些工厂类需要构造出对象之后才能使用:
public class StudentFactory { public Student getStudent(){ System.out.println("欢迎光临电子厂"); return new Student(); } }
将某个工厂类直接注册为工厂Bean,再使用factory-bean
来指定Bean的工厂Bean
<bean name="studentFactory" class="com.test.bean.StudentFactory"/> <bean factory-bean="studentFactory" factory-method="getStudent"/>
Student bean = (Student) context.getBean("studentFactory"); StudentFactory bean = (StudentFactory) context.getBean("&studentFactory");