一、IOC介绍
IOC:Inversion of Control 控制反转
spring核心容器也可以称为IOC容器,该容器主要负责管理各种对象,主要包括项目的生命周期(对象的创建、初始化、销毁等)、不同对象之间依赖关系的建立、将不同的对象及对象代码在程序运行期间动态的结合在一起等等。
IOC容器并没有实现更多的功能,但它的存在使我们不需要很多代码、不需要考虑对象间复杂的耦合关系就能从IOC容器中获取合适的对象,而且提供了各种对象的可靠的管理,极大地降低了开发的复杂性。
控制反转:是一种编程思想,或者说是一种新的设计模式,由于推出较晚,所以没有纳入GoF23种设计模式当中。
控制反转了什么呢?
- 不需要使用硬编码的方式创建对象;
- 不需要使用硬编码的方式维护对象之间的关系;
DI:Dependency Injection 依赖注入
DI的概念的提出是用来代替IOC的,表示让调用类对某一接口实现类的依赖关系由容器注入,以移除调用类对某一接口实现类的依赖。
依赖注入 这个名词显然比 控制反转 更直接明了,并且易于理解。 它是控制反转的一种实现方式,并不是唯一的方式,例如:依赖查找、服务定位器、容器注入、策略模式和观察者模式等,也可以实现控制反转。
二、IOC容器
Spring的IoC(Inversion of Control,控制反转)之所以又称为IoC容器,主要是因为IoC在Spring框架中承担了一个核心的角色——作为容器来管理应用程序中的对象(通常称为Bean)。
Spring提供了两种主要的IoC容器实现:BeanFactory
和ApplicationContext
- BeanFactory:
- 是Spring中最基础的IoC容器接口,提供了IoC服务的基本支持。
- 主要用于访问容器中的Bean,但功能相对简单,不提供诸如国际化、事件发布等高级功能。
- ApplicationContext:
- 是
BeanFactory
的子接口,提供了更丰富的功能,如国际化、资源访问、事件发布等。 - 是Spring框架推荐使用的IOC容器实现,因为它提供了比
BeanFactory
更全面的服务支持。
- 是
核心API
// 根据指定名称返回一个Bean实例
Object getBean(String name)
// 判断名称为name的Bean是否是原型,即是否总是返回一个新实例(非单例)
boolean isPrototype(String name)
// 判断名称为name的Bean是否是单例
boolean isSingleton(String name)
// 判断容器中是否包含给定名称的Bean实例
boolean containsBean(String name)
// 如果名称为name的Bean有别名则返回其别名
String[] getAliases(String name)
三、IOC的注入功能
1. set方式注入(必须依靠set方法)
1)基本类型的装配
XML配置
<bean id="student" class="com.it.bean.Student">
<property name="id" value="001"></property>
<property name="age">
<value>23</value>
</property>
</bean>
java配置类
@Bean
public Student student() {
Student student = new Student();
student.setName("name");
student.setAge(18);
return student;
}
注意:
-
id:是Bean的唯一标识,要求在整个配置文件中要唯一,也可使用name属性,bean标签里面的id和name属性都可以用来标识这个配置的对象,但是id会帮我们检查给对象起的名字是否规范(名字不能重复、不能用数字开头、不能有空格等等),如果检查出来了就会报错,而name属性不会检查这些东西;
XML配置
<bean name="t1 t2" class="com.it.bean.Teacher"></bean> <bean id="t1 t2" class="com.it.bean.Teacher"></bean>
java配置类
@Bean(name = {"stu", "student", "s"}) public Student student() { return new Student(); }
-
property:对于所有用set方式来注入的必须使用该标签。
-
value:基本数据类型,都用value(标签/属性)来注入,可以实现自动的数据类型转换。
2)对象类型的装配
-
引用对象在当前配置文件中:
<property name="student"> <ref local="objName"/> </property>
-
引用对象不在当前配置文件中:
<property name="student"> <ref bean="objName"/> </property>
-
使用property的ref属性引用:
<property name="student" ref="objName"></property>
3)集合的装配
-
List集合:
<!-- 使用value注入 --> <property name="list"> <list> <value>hello1</value> <value>hello2</value> <value>hello3</value> </list> </property> <!-- 使用ref引用spring容器中的其他对象 --> <property name="list"> <list> <ref bean="t1"/> <ref bean="t2"/> </list> </property>
-
Set集合:
<property name="set"> <set> <value>world1</value> <value>world2</value> <value>world3</value> </set> </property>
-
Map集合:
<property name="map"> <map> <entry key="tom1" value="23"></entry> <entry key="tom2" value="24"></entry> <entry key="tom3" value="25"></entry> </map> </property>
-
Propertis:
<property name="prop"> <props> <prop key="a">山西</prop> <prop key="b">北京</prop> <prop key="c">上海</prop> </props> </property>
2. 构造器注入
配置 <constructor-arg>
元素,在Bean中不用写set方法,但是要有相应的构造器。
1)根据参数类型
<bean name="student1" class="com.it.bean.Student">
<constructor-arg type="long" value="2020001"></constructor-arg>
<constructor-arg type="String" value="tom"></constructor-arg>
<constructor-arg type="int" value="23"></constructor-arg>
</bean>
2)根据参数下标位置
<bean name="student2" class="com.it.bean.Student">
<constructor-arg index="0" value="2020002"></constructor-arg>
<constructor-arg index="1" value="lisa"></constructor-arg>
<constructor-arg index="2" value="24"></constructor-arg>
</bean>
3. 自动注入
注意:自动装配只对【对象类型】起作用,对基本类型不起作用。
1)配置默认装载方式
default-autowire
,该属性可以指定值为:default、no、byName、byType、constructor。
在【根元素】beans中加入这个属性,那么下面所有的bean都会使用byName的方式进行自动注入,如果在下面的某一个bean里面想使用其他的方式进行注入,可以用 autowire="***"
属性进行注入。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd"
default-autowire="byName">
<bean name="teacher" class="com.it.bean.Teacher">
<property name="id" value="1001"/>
<property name="name" value="李四"/>
</bean>
</beans>
2)手动指定装置方式
在bean标签中指定配置方式:
<bean name="teacher" class="com.it.bean.Teacher" autowire="byType">
<property name="id" value="1001"/>
<property name="name" value="李四"/>
</bean>
<bean name="teacher" class="com.it.bean.Teacher" autowire="byName">
<property name="id" value="1001"/>
<property name="name" value="李四"/>
</bean>
3)继承
配置文件中,一个bean的配置可以继承另一个bean的配置。bean标签中的俩个属性:
- abstract属性:
<bean name=".." class=".." abstract="true">
。bean中一旦定义为了abstract="true"
,则说明该bean是用来被继承的,不能通过该bean获得对象了。 - parent属性:
<bean name=".." parent="另一个bean的名字">
,指定继承哪个bean的配置,配置和配置的继承像java中的类和类直接的继承一样,子类会把父类中的对象继承过来。当然在子配置里面依然是可以覆盖父配置中已经写的配置信息。
<bean name="teacher" class="com.briup.bean.Teacher" abstract="true">
<property name="student" ref="student"></property>
</bean>
<bean name="t" parent="teacher">
<property name="id">
<value>11</value>
</property>
<property name="name">
<value>TeacherWang</value>
</property>
</bean>
四、Bean对象的生命周期
1. 单例Bean
1)五步
- 第一步:实例化Bean;
- 第二步:Bean属性赋值;
- 第三步:初始化Bean;
- 第四步:使用Bean;
- 第五步:销毁Bean;
2)七步
- 第一步:实例化Bean;
- 第二步:Bean属性赋值;
- 第三步:执行Bean后置处理器的before方法;
- 第四步:初始化Bean;
- 第五步:执行Bean后置处理器的after方法;
- 第六步:使用Bean;
- 第七步:销毁Bean;
3)十步
- 第一步:实例化Bean;
- 第二步:Bean属性赋值;
- 第三步:如果实现了Aware相关接口,则调用这些接口中的方法;
- 第四步:执行Bean后置处理器的before方法;
- 第五步:如果实现了InitializingBean相关接口,则调用这些接口中的方法;
- 第六步:初始化Bean;
- 第七步:执行Bean后置处理器的after方法;
- 第八步:使用Bean;
- 第九步:如果Bean实现了DisposableBean接口,将执行destory方法;
- 第十步:销毁Bean;
2. 原型Bean
- 第一步:使用这个对象的时候,spring容器会创建这个对象;
- 第二步:Bean属性赋值;
- 第三步:调用
init-method=".."
,如果有该配置的话; - 第四步:使用Bean;
五、配置导入
如果我们在spring框架中配置了多个文件,我们可以在读取配置文件的时候把其他文件全都读取,也可以只读一个总的文件,在这个总的文件中把其他的导入进来。
XML配置
<import resource="student.xml"/>
java配置类
@Import(OtherConfig.class) // @Import用于导入spring扫描不到的java配置类
@ImportResource("classpath:student.xml") // @ImportResource用于导入XML或groovy文件
@Configuration
public class SpringConfig {
}
六、创建Bean对象的方式
1. 常规方式
xml文件中有bean的配置,而且这个bean所对应的java类中存在一个无参构造器,那么这个时候spring容器就可以使用反射调用无参构造器来创建实例了。
2. 使用工厂类
工厂类交给spring管理,希望通过配置工厂类,可以直接获得工厂类产生的对象,而不是工厂类对象本身,所以需要告诉spring:
- 谁是工厂类;
- 工厂类中创建对象的方法是哪个
怎么告诉:(3种方式)
-
实现FactoryBean接口:
-
工厂类:
@Data public class ConnectionFactory implements FactoryBean<Connection> { private String driver; private String url; private String username; private String password; @Override public Connection getObject() throws Exception { Class.forName(driver); return DriverManager.getConnection(url, username, password); } @Override public boolean isSingleton() { return false; } @Override public Class<Connection> getObjectType() { return Connection.class; } }
-
配置类:
@Bean("connection") public ConnectionFactory connectionFactory() { ConnectionFactory factory = new ConnectionFactory(); factory.setUrl("url"); factory.setDriver("driver"); factory.setUsername("username"); factory.setPassword("password"); return factory; }
-
XML配置:
<bean name="conn" class="com.briup.ioc.factory.ConnectionFactory"> <property name="driver" value="driver"/> <property name="url" value="url"/> <property name="username" value="username"/> <property name="password" value="password"/> </bean>
-
获取对象:
// 获取工厂类,工厂类在spring容器中以&开头 Object factory = SpringUtil.getBean("&connection"); // 获取connection对象,运行时自动执行工厂类的getObject方法 Object connection = SpringUtil.getBean("connection");
-
-
实例工厂:在xml文件中告诉spring容器哪个是工厂类,以及哪个是工厂方法;
-
工厂类:
@Data public class ConnectionFactory { private String driver; private String url; private String username; private String password; public Object getConnection() throws Exception { Class.forName(driver); return DriverManager.getConnection(url,username,password); } }
-
配置类:
@Bean("connection") public Connection connectionFactory() throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setUrl("url"); factory.setDriver("driver"); factory.setUsername("username"); factory.setPassword("password"); // 这里调用工厂方法 return factory.getConnection(); }
-
XML配置:
<bean name="factory" class="com.bean.ConnectionFactory"> <property name="driver" value="driver"/> <property name="url" value="url"/> <property name="username" value="username"/> <property name="password" value="password"/> </bean> <bean name="connection" factory-bean="factory" factory-method="getConnection"/>
-
-
静态工厂:
-
工厂类:
@Data public class ConnectionFactory { private static String driver = "driver"; private static String url = "url"; private static String username = "username"; private static String password = "password"; public static Connection getConnection() throws Exception { return new MockConnection(); } }
-
配置类:
@Bean("connection") public Connection connectionFactory() throws Exception { return ConnectionFactory.getConnection(); }
-
XML配置:
<bean name="connection" class="com.bean.ConnectionFactory" factory-method="getConnection"/>
-
七、IOC中的常用注解
1. 组件定义注解
- @Component:这是一个通用的组件注解,用于将类标记为Spring容器中的一个组件。它可以被@Controller、@Service、@Repository等注解所替代,这些注解提供了更具体的用途描述。
- @Controller:用于标注在MVC架构中的控制器类上。
- @Service:通常用于标注服务层组件。
- @Repository:用于标注数据访问层组件,主要用于访问数据库。
2. 自动装配注解
- @Autowired:自动装配Bean,可以作用于构造器、字段或setter方法上。默认情况下,按照类型装配,如果需要按名称装配,可以结合@Qualifier注解使用。
- @Qualifier:与@Autowired配合使用,指定要注入Bean的名称。
- @Resource:与@Autowired类似,但@Resource默认按照名称装配,如果找不到名称匹配的Bean,则按照类型装配。
- @Inject:是JSR-250规范提供的注解,功能与@Autowired相似,但它是Java标准的一部分,而不是Spring特有的。
3. 配置类注解
- @Configuration:表明该类是一个配置类,用于定义Bean。配置类中的@Bean注解方法将返回的对象注册为Spring容器中的Bean。
- @Bean:用于在配置类中声明一个Bean,并指定其初始化方法。可以通过name属性指定Bean的名称。
4. 条件注解
- @Conditional:根据指定的条件决定是否创建Bean。这通常与实现了Condition接口的类一起使用,以提供具体的条件逻辑。
5. 扫描注解
- @ComponentScan:用于指定Spring在初始化时需要扫描的包路径,以便发现注解了@Component、@Service、@Repository、@Controller等注解的类,并将它们注册为Bean。可以通过excludeFilters和includeFilters属性来定义扫描的过滤规则。
6. 作用域注解
- @Scope:用于指定Bean的作用域。常见的值有"singleton"(单例,默认值)、“prototype”(原型,每次请求都会创建一个新的Bean实例)、“request”(Web环境中,每个HTTP请求都会创建一个新的Bean实例)、“session”(Web环境中,每个HTTP会话都会创建一个新的Bean实例)等。
7. 懒加载注解
- @Lazy:用于指定Bean的懒加载模式。默认情况下,Spring容器在启动时就会创建并初始化所有单例Bean。使用@Lazy注解后,Bean将在第一次被使用时才进行创建和初始化。
8. 生命周期注解
- @PostConstruct:用于标注在方法上,表示该方法是在Bean的依赖注入完成后执行的初始化方法。
- @PreDestroy:用于标注在方法上,表示该方法是在Bean销毁之前执行的清理方法。
9. 导入注解
- @Import:用于快速导入其他配置类、组件或ImportSelector/ImportBeanDefinitionRegistrar实现,以便将它们注册到Spring容器中。
八、自定义属性编辑器
Spring中我们可以使用属性编辑器来将特定的字符串转换为对象:String --> 转换 --> object
java.beans.PropertyEditor(JDK中的接口)用于将xml文件中字符串转换为特定的类型,同时JDK为我们提供一个实现类 java.beans.PropertyEditorSupport,将来我们自定义的属性编辑器类可以继承该实现类。
Spring在注入时,如果遇到类型不一致(例如需要Address类型,但是用户传了个String),则会去调用相应的属性编辑器进行转换。
Spring会调用属性编辑器的 setAsText(String str)
进行处理用户传的字符串,并调用 getValue()
方法获取处理后得到的对象。
注意:在代码中处理完后需要调用 setValue
方法,要不然spring调用 getValue
方法拿不到处理后转换成的对象。
下面是一个示例:
-
Address类:
@Data @AllArgsConstructor public class Address { private String city; private String province; private String country; }
-
Student类:
@Data public class Student { private String name; private int age; private Address address; }
-
PropertyEditorSupport类:
public class AddressEditor extends PropertyEditorSupport { @Override public void setAsText(String text) throws IllegalArgumentException { String[] str = text.split(","); String city = str[0]; String province = str[1]; String country = str[2]; Address add = new Address(city, province, country); setValue(add); } }
-
XML配置:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="customEditors"> <map> <entry key="com.bean.Address" value="com.bean.editor.AddressEditor"/> </map> </property> </bean>
-
配置类:
@Bean public CustomEditorConfigurer editorConfigurer() { CustomEditorConfigurer configurer = new CustomEditorConfigurer(); Map<Class<?>, Class<? extends PropertyEditor>> editors = new HashMap<>(); editors.put(Address.class, AddressEditor.class); configurer.setCustomEditors(editors); return configurer; }
-
这样Address即可支持字符串的赋值:
<bean id="student" class="com.bean.Student"> <property name="address"> <value>changzhi,shanxi,China</value> </property> </bean>
注意:按照JavaBeans的规范,JavaBeans的基础设施会在JavaBean相同类包下查找是否存在 JavaBeanEditor
的类,如果存在,自动使用 JavaBeanEditor
作为该JavaBean的PropertyEditor。
如:UserEditor会自动成为User对应的PropertyEditor。Spring也支持这个规范,也即如果采用这种规约命令PropertyEditor,就无须显式在CustomEditorConfigurer中配置了,Spring将自动查找并注册这PropertyEditor。