Spring系列--IOC详解
目录
IOC
概念
控制反转、依赖注入,相当于咱不需要自己创建对象,不需要关注对象创建的过程了,要啥对象直接向Spring容器索要即可(Spring 通过反射创建),而我们只需要告诉它需要什么样的对象(bean,bean定义,xml或者注解配置)
控制反转,把对象创建和对象之间的调用过程交给Spring管理,为了降低耦合度。
优点
-
控制反转和依赖注入降低了应用的代码量。
-
松耦合,便于维护。
-
支持加载服务时的饿汉式初始化和懒加载。
单例模式可以分为懒汉式和饿汉式。
懒汉式就是创建对象时比较懒,先不急着创建对象,在需要加载配置文件的时候再去创建。
饿汉式就是在系统初始化的时候我们已经把对象创建好了,需要用的时候直接拿过来用就好了。惰性加载机制(或懒加载、延时加载),也就是说只有当使用到这个实例的时候才会创建这个实例
底层原理
- XML解析、工厂模式、反射。
原始方式:耦合度非常高
工厂模式
XML配置文件
IOC接口
-
IOC思想基于IOC容器完成,IOC容器底层就是对象工厂。
-
Spring提供两种实现IOC容器的方式:
-
BeanFactory:IOC容器的基本实现,Spring内部的接口,一般不提供开发人员使用。
- 特点:加载配置文件的时候并不会创建bean,在用到的时候才会创建。
-
ApplicationContext:BeanFactory的子接口,提供更多强大功能,一般开发人员使用。
- 特点:加载配置文件的时候同时创建bean。
- ApplicationContext接口实现类
- FileSystemXmlApplicationContext(“盘符路径(绝对路径)”)
- ClassPathXmlApplicationContext(“src目录下类路径”)
-
IOC操作
基于XML方式
-
Spring 创建对象
-
创建普通类,类里面创建普通方法
public class User{ public void add(){ system.out.println("add...."); } }
-
创建Spring配置文件,在配置文件中创建对象,新建base.xml配置文件
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--配置User对象创建--> <bean id="user" class="com.spring5.User"></bean> </beans>
-
在Bean标签有很多属性,常用的属性:id、class、name
-
创建对象的时候,默认也是执行无参数构造方法,若没有无参构造则报错。
-
Spring注入属性
-
DI 依赖注入,注入属性
第一种注入方式:使用set方法进行注入
原始方法一:创建类,定义属性和对应的set方法
public class Book {
private String bname;
private String bauthor;
public void setBname(String bname) {
this.bname = bname;
}
public void setBauthor(String bauthor) {
this.bauthor = bauthor;
}
public static void main(String[] args) {
Book book = new Book();
book.setBname("WeiSanJin");
}
**Spring:**在Spring配置文件配置对象创建,配置属性注入,仍需要有set方法
<!--set方法注入属性-->
<bean id="book" class="com.spring5.Book">
<!--使用property完成属性注入
name:类里面属性名称
value:向属性注入的值
-->
<property name="bname" value="WeiSanJin"></property>
<property name="bauthor" value="WeiSanJin"></property>
</bean>
第二种注入方式:使用有参数构造进行注入
原始方法二:创建类,定义属性,创建属性对应有参数构造方法
public class Orders {
private String oname;
private String address;
public Orders(String oname, String address) {
this.oname = oname;
this.address = address;
}
}
**Spring:**在spring 配置文件中进行配置,利用 ****标签
<bean id="orders" class="com.spring5.Orders">
<constructor-arg name="oname" value="WeiSanJin"></constructor-arg>
<constructor-arg name="address" value="WeiSanJin"></constructor-arg>
<!--可以使用 index代替name-->
</bean>
测试
@Test
public void TestOrder(){
//1.加载Spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("base1.xml");
//2.获取配置创建的对象
Orders orders = context.getBean("orders",Orders.class);
System.out.println(orders.toString());
}
P名称空间注入(了解)
-
使用P名称空间注入是为了简化XML的配置方式,直接把属性写在bean标签内即可,不用再写property标签。
-
第一步要添加P名称空间在配置文件中。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
-
属性注入,直接bean标签中操作。
<bean id="book" class="com.spring5.Book" p:bname="WeiSanJin" p:bauthor="WeiSanJin"></bean></bean>
其它类型属性注入(xml)
字面量
字面量为等号右侧的值,字面量是由字符串,数字等构成的字符串或数值,是固定的
-
null值:通过标签实现
-
包含特殊符号的属性值,要么用转义字符,要么使用CDATA格式
//方法一:转义字符 <property name="address" value="<北京&dt;"></property> //方法二:CDATA <property name="address"> <value> <![CDATA[<北京>]]> </value> </property>
外部bean
通俗理解就是一个类中需要用到了另外一个类的对象,就是给一个bean注入一个外部bean,就需要用到ref属性。
<bean id="userService" class="com.spring5.service.UserService"> //对象一
<!-- 注入UserDao对象
name属性:类里面属性名称
ref属性:创建userDao对象bean标签id值
-->
<property name="userDao" ref="userDaoImpl"></property>//注入对象二
</bean>
// 配置dao对象
<bean id="userDaoImpl" class="com.spring5.dao.UserDaoImpl"></bean> //对象二 也就是外部bean
内部bean
<bean id="emp" class="com.spring5.bean.Emp">
<!--设置两个普通属性-->
<property name="ename" value="WeiSanJin"></property>
<property name="genfer" value="WeiSanJin"></property>
<property name="dept">
<bean id="dept" class="com.spring5.bean.Dept">
<property name="dname" value="保安部"></property>
</bean>
</property>
</bean> //可以改用外部bean的方式来写 其它bean对象就不能调用。
级联赋值
<!--级联赋值-->
<bean id="emp" class="com.spring5.bean.Emp">
<!--设置两个普通属性-->
<property name="ename" value="WeiSanJin"></property>
<property name="genfer" value="WeiSanJin"></property>
<!--级联赋值-->
<property name="dept" ref="dept"></property>
</bean>
<bean id="dept" class="com.spring5.bean.Dept">
<property name="dname" value="财务部"></property>
</bean>
这三种赋值方式都有些什么区别?
集合类属性注入
<!--1. 集合类型属性注入-->
<bean id="stu" class="com.spring5.collectionytpe.Stu">
<!--数组类型属性注入 -->
<property name="courses">
<array>
<value>Java课程</value>
<value>数据库课程</value>
</array>
</property>
<!--list类型属性注入 -->
<property name="list">
<list>
<value>张三</value>
<value>小三</value>
</list>
//如果集合里的存储的是其它bean对象,就需要引用,同时也需要配置引用的bean
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
</list>
</property>
<!--map类型属性注入 -->
<property name="maps">
<map>
<entry key="Java" value="java"></entry>
<entry key="PHP" value="php"></entry>
</map>
</property>
<!--set类型属性注入 -->
<property name="sets">
<set>
<value>Mysql</value>
<value>Redis</value>
</set>
</property>
</bean>
<!-- 创建多个course对象-->
<bean id="course1" class="com.spring5.collectionytpe.Course">
<property name="cname" value="String"></property>
</bean>
<bean id="course2" class="com.spring5.collectionytpe.Course">
<property name="cname" value="String"></property>
</bean>
util命名空间
应为beans只能存在bean对象,不能有list等集合标签。而util相当于将bean中的集合属性property抽出来单独当做一个bean对象操作,通过它就可以生成集合对象供其它bean引用。
-
spring配置文件中引入名称空间util
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
-
使用util标签完成list集合提取
<!--1 提前list集合类型属性注入--> <util:list id="bookList"> <value>三国演义</value> <value>水浒传</value> <value>西游记</value> <value>红楼梦</value> </util:list> <!--2 提前list集合类型属性使用--> <bean id="book" class="com.spring5.collectionytpe.Book"> <property name="list" ref="bookList"></property> </bean>
-
IOC容器初始化
-
初始化过程:BeanDefinition的资源定位、解析、注册。
- 从XML读取配置文件。
- 解析成BeanDefinition,并注入到BeanDefinition实例中。
- 将BeanDefinition注册到容器BeanDefinitionMap中。
- BeanFactory根据BeanDefinition的定义信息创建实例化和初始化Bean。
-
单例Bean的初始化以及依赖注入一般都在容器的初始化阶段进行,除非设置了懒加载 lazy-inint为true的单例bean就是在第一次调用getBean() 方法的会后进行初始化和依赖注入。
-
多例Bean在容器启动时不实例化,必须要在getBean调用的时候才实例化。
-
loadBeanDefinitions 采⽤了模板模式,具体加载 BeanDefinition 的逻辑由各个⼦类完成。
FactoryBean
Spring 有两种类型bean,一种普通bean,另外一种工厂bean(FactoryBean)
(1)普通bean在配置文件中,定义bean类型就是返回类型
(2)工厂bean在配置文件中定义bean类型可以和返回类型不一样
-
第一步创建类,让这个类作为工厂Bean,实现接口FactoryBean
-
第二步实现接口里的方法,在实现方法中定义返回的bean类型
//实现接口类 public class MyBean implements FactoryBean<Course>{ @override public Course getObject() throws Exceptions{ //定义你要返回的类型 Course course = new Course(); course.setCourse("java"); return course; } @override public Class<?> getObjectType(){ return NULL; } @override public Boolean isSingleton(){ return false; } } //配置类 <bean id = "mybean" class="com.zhh.entity.MyBean"></bean> //测试类 @test public void test2(){ ApplicationContext application = new classPathXpthXmlApplicationContext("bean4.xml"); Course course = context.getBean("MyBean",Course.class); }
工厂bean的意义在哪?
首先它是一个Bean,但又不仅仅是一个Bean。它是一个能生产或修饰对象生成的工厂Bean。
-
一个Bean如果实现了FactoryBean接口,那么根据该Bean的名称获取到的实际上是getObject()返回的对象,而不是这个Bean自身实例,如果要获取这个Bean自身实例,那么需要在名称前面加上’&’符号。
-
通常是⽤来创建⽐较复杂的bean,⼀般的bean 直接⽤xml配置即可,但如果⼀个bean的创建过程中涉及到很多其他的bean 和复杂的逻辑,直接⽤xml配置⽐较⿇烦,这时可以考虑⽤FactoryBean,可以隐藏实例化复杂Bean的细节。
从源码分析作用:https://cloud.tencent.com/developer/article/1553476
Bean生命周期
简单来说Bean的生命周期主要包含四个方面:
- 实例化对象
- 初始化对象
- 使用对象
- 销毁对象
仔细细化来看,首先来看实例化对象部分
实例化
1、3、4中的方法均实现InstantiationAwareBeanPostProcessor
接口
package com.Spring.Boot.init;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.stereotype.Component;
@Component
public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
// 实例化前置
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
System.out.println("postProcessBeforeInstantiation被调用了----在对象实例化之前调用-----beanName:" + beanName);
// 默认什么都不做,返回null
return null;
}
// 实例化后置
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInstantiation被调用了---------beanName:" + beanName);
//默认返回true,什么也不做,继续下一步
return true;
}
// 属性修改
@Override
public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
System.out.println("postProcessPropertyValues被调用了---------beanName:"+beanName);
// 此方法可对bean中的属性值进行、添加、修改、删除操作;
// 对属性值进行修改,如果postProcessAfterInstantiation方法返回false,该方法可能不会被调用,
return pvs;
}
}
-
实例化前置
使用的是 InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation(Class<?> beanClass, String beanName) 方法,顾名思义,就是对在对象实例化之前对bean对象的class信息进行修改或者扩展,以达到我们想要的功能,它的底层是动态代理AOP技术实现的;且是bean生命周期中最先执行的方法;
默认是返回一个null值的,也可以返回其它类型的值,当返回非空时,就不会进行下一步实例对象了,当用到这个bean的时候,返回的就是设置的这个返回值了。
-
实例化对象
doCreateBean方法创建实例,用反射技术创建,相当于new了一个对象,实例化了但是属性还没设置。
-
实例化后置
InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation(Object bean, String beanName),该方法的返回值决定着要不要进行下一步属性修改,返回false就不会执行下一步了。在目标对象实例化之后调用,这个时候对象已经被实例化,但是该实例的属性还未被设置
-
属性修改
postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName)
此方法可对属性值进行修改,修改范围包括添加、修改、删除操作;
初始化
- 给用户属性赋值
用户属性指的是用spring 的人自定义的bean对象属性,像 User、Student、Teacher 、UserService、IndexService 这类的对象都是自定义bean对象,第5步主要给这类属性进行赋值操作,使用的是 AbstractAutowireCapableBeanFactory.populateBean() 方法进行赋值;
- 给容器属性赋值
容器自带的属性,这些属性都是spring本来就有的;可以肯定的是,它们都是 Aware 接口的实现类。
package com.Spring.Boot.init.aware;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.*;
import org.springframework.context.annotation.ImportAware;
import org.springframework.context.weaving.LoadTimeWeaverAware;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.instrument.classloading.LoadTimeWeaver;
import org.springframework.stereotype.Component;
import org.springframework.util.StringValueResolver;
import org.springframework.web.context.ServletContextAware;
import javax.servlet.ServletContext;
@Component
public class AllAwareInterface implements BeanNameAware, BeanClassLoaderAware,
BeanFactoryAware, EnvironmentAware, EmbeddedValueResolverAware,
ResourceLoaderAware, ApplicationEventPublisherAware, MessageSourceAware,
ApplicationContextAware, ServletContextAware, LoadTimeWeaverAware, ImportAware {
@Override
public void setBeanName(String name) {
// BeanNameAware作用:让Bean对Name有知觉
//这个方法只是简单的返回我们当前的beanName,听官方的意思是这个接口更多的使用在spring的框架代码中,实际开发环境应该不建议使用
System.out.println("1 我是 BeanNameAware 的 setBeanName 方法 ---参数:name,内容:"+ name);
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
//获取Bean的类装载器
System.out.println("2 我是 BeanClassLoaderAware 的 setBeanClassLoader 方法");
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
// 注意: 如果使用 @Configuration 注解的话,setBeanFactory方法会执行2次,
System.out.println("3 我是 BeanFactoryAware 的 setBeanFactory 方法");
}
@Override
public void setEnvironment(Environment environment) {
//在工程启动时可以获得application.properties 、xml、yml 的配置文件配置的属性值。
System.out.println("4 我是 EnvironmentAware 的 setEnvironment 方法");
}
@Override
public void setEmbeddedValueResolver(StringValueResolver stringValueResolver) {
System.out.println("5 我是 EmbeddedValueResolverAware 的 setEmbeddedValueResolver 方法");
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
//用来加载外部资源的;方法中有个参数:ResourceLoader ,这个参数其实就是ApplicationContext(spring 的上下文对象);
System.out.println("6 我是 ResourceLoaderAware 的 setResourceLoader 方法");
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
System.out.println("7 我是 ApplicationEventPublisherAware 的 setApplicationEventPublisher 方法");
}
@Override
public void setMessageSource(MessageSource messageSource) {
//国际化消息通知操作
System.out.println("8 我是 MessageSourceAware 的 setMessageSource 方法");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("9 我是 ApplicationContextAware 的 setApplicationContext 方法");
}
@Override
public void setServletContext(ServletContext servletContext) {
System.out.println("10 我是 ServletContextAware 的 setServletContext 方法");
}
@Override
public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) {
//LoadTimeWeaver 简称LTW,LTW是AOP的一种实现方式,此方法是为了获取Aop织入的对象,使用的织入方式是:类加载期织入,
// 一般的aop都是运行期织入,就是在运行的时候才进行织入切面方法,但是LTW是在类加载前就被织入了,也就是class文件在jvm加载之前进行织入切面方法
// 只有在使用 @EnableLoadTimeWeaving 或者存在 LoadTimeWeaver 实现的 Bean 时才会调用,顺序也很靠后
System.out.println("11 我是 LoadTimeWeaverAware 的 setLoadTimeWeaver 方法");
}
@Override
public void setImportMetadata(AnnotationMetadata annotationMetadata) {
//只有被其他配置类 @Import(XX.class) 时才会调用,这个调用对 XX.class 中的所有 @Bean 来说顺序是第 1 的。
System.out.println("12 我是 ImportAware 的 setImportMetadata 方法");
}
}
EmbeddedValueResolverAware.setEmbeddedValueResolver()
通常我们使用@Value注解来获取properties 和 yml 文件中的值,每个类中都要使用@Value也很繁琐,实现EmbeddedValueResolverAware接口后就方便多了。用法也跟@Value一样,需要用${}包裹住;
@Component
public class PropertiesUtil implements EmbeddedValueResolverAware {
@Override
public void setEmbeddedValueResolver(StringValueResolver stringValueResolver) {
System.out.println(stringValueResolver.resolveStringValue("${logging.file}"));
}
}
ApplicationEventPublisherAware.setApplicationEventPublisher()
ApplicationEventPublisherAware是一个事件发布器的接口,使用这个接口,我们自己的 Service 就拥有了发布事件的能力。用户注册后,不再是显示调用其他的业务 Service,而是发布一个用户注册事件。那么在这里是发布事件,那就肯定有监听事件的接口,这个接口叫做 ApplicationListener ,只要实现 ApplicationListener 接口就可以接受发布的事件了。
- 初始化前置
方法名称: BeanPostProcessor.postProcessBeforeInitialization()
在每一个 Bean 初始化之前执行的方法(有多少 Bean 调用多少次)
注意 : 启用该方法后,标注了@PostConstruct注解的方法会失效
- 初始化后置
方法名称: BeanPostProcessor.postProcessAfterInitialization()
在每一个 Bean 初始化之后执行的方法(有多少 Bean 调用多少次)
初始化前置和初始化后置的实现代码如下
- 初始化方法
初始化方法有三个,分别是 添加了@PostConstruct 注解的方法、实现InitializingBean接口、在@bean注解上添加 initMethod属性;
- @PostConstruct
在bean对象内添加@PostConstruct 注解后即可实现初始化的功能,被@PostConstruct修饰的方法会在构造函数之后,init()方法之前运行。 有多个则会执行多次;
- InitializingBean.afterPropertiesSet()
spring 初始化方法之一,作用是在BeanFactory完成属性设置之后,执行自定义的初始化行为。
执行顺序:在initMethod之前执行,在@PostConstruct之后执行
- init-method
bean 配置文件属性 init-method 用于在bean初始化时指定执行方法,用来替代继承 InitializingBean接口,
注意的一点是只有一个类完整的实例被创建出来后,才能走初始化方法。
<bean id="beanTest" class="com.BeanTest" init-method="init"></bean>
使用中
到这一步,bean对象就已经完全创建好了,是一个完整对象了,并且正在被其他对象使用了;
销毁
在这里需要先说一下,被spring容器管理的bean默认是单例的,默认在类上面有个 @Scope注解。
销毁和单例和多例有关,如果是单例模式,会先执行 DisposableBean.destroy()方法,然后在执行 destroy-Method 方法;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.stereotype.Component;
/**
* 销毁方法
*/
@Component
public class ExtDisposableBean implements DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("我被销毁了");
}
}
//销毁方法和init一样 需要创建类再配置
package com.Spring.Boot.init;
import com.Spring.Boot.init.bean.BeanTest;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component()
public class InitMethod {
// 在@Bean注解上添加initMethod属性,指向类中的 initMethod_1 执行初始化方法
// 在@Bean注解上添加destroyMethod属性,指向类中的 destroyMethod_1 执行销毁方法
@Bean(initMethod = "initMethod_1",destroyMethod = "destroyMethod_1")
public BeanTest getBeanTest(){
return new BeanTest();
}
}
BeanTest.java
package com.Spring.Boot.init.bean;
public class BeanTest {
// 将要执行的初始化方法
public void initMethod_1(){
System.out.println("我是beanTest的init方法");
}
// 将要执行的销毁方法
public void destroyMethod_1(){
System.out.println("我是beanTest的init方法");
}
}
<bean id="beanTest" class="com.BeanTest" destroy-method="destroyMethod_1"></bean>
因为多例模式下,spring无法进行管理,所以将生命周期交给用户控制,用户用完bean对象后,java垃圾处理器会自动将无用的对象进行回收操作;
Bean的作用域
- singleton:单例,Spring中的bean默认都是单例的。
- prototype:每次请求都会创建⼀个新的bean实例。
- request:每⼀次HTTP请求都会产⽣⼀个新的bean,该bean仅在当前HTTP request内有效。
- session:每⼀次HTTP请求都会产⽣⼀个新的bean,该bean仅在当前HTTP session内有效。
- global-session:全局session作⽤域。
xml自动装配
根据指定装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入
<!--实现自动装配
bean标签属性autowire,配置自动装配
autowire属性常用两个值:
byName根据属性名称注入,注入值bean的id值和类属性名称一样
byType根据属性类型注入
-->
<bean id="emp" class="com.spring5.autowire.Emp" autowire="byName">
</bean>
<bean id="dept" class="com.spring5.autowire.Dept"></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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 引入外部属性文件--> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <!-- 配置连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${prop.driverClass}"></property> <property name="url" value="${prop.url}"></property> <property name="username" value="${prop.userName}"></property> <property name="password" value="${prop.password}"></property> </bean>
基于注解方式
- 格式:@注解名称(属性名=属性值,属性名=属性值)
- 使用注解:注解作用在类(方法,属性)上
- 使用目的:简化xml配置
Spring针对Bean管理中创建对象提供注解
- @Component 普通用法
- @Service 用于service业务逻辑层
- @Controller 用于web层
- @Repository 用于DAO持久层
<!-- 1、引入依赖-->
spring-aop-5.2.6.RELEASE
<!-- 2、引入context名称空间-->
<!-- 3、开启组件扫描,多个包,使用逗号隔开,或者写共同的上层目录-->
<context:component-scan base-package="com.zhh.service,com.zhh.DAO"></context:component-sacn>
<!-- 开启组件扫描-->
<context:component-scan base-package="com.spring5"/>
<!-- 创建类并使用注解||value值默认值为首字母小写的类名-->
@Component(value = "User")
public class User {
public void add(){
System.out.println("service add......");
}
}
<!-- 测试方法-->
@Test
public void testService(){
//1.加载Spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("base9.xml");
//2.获取配置创建的对象
User user = context.getBean("User", User.class);
user.add();
}
开启组件扫描细节配置
<!--示例1
use-default-filters="false" 表示现在不使用默认filter,自己配置fillter
context:include-filter,设置扫描哪些内容
-->
<context:component-scan base-package="com.spring5" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--示例2
下面配置扫描包所有内容
context:exclude-filter:设置哪些内容不进行扫描
-->
<context:component-scan base-package="com.spring5">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
基于注解方式显示属性注入
**@AutoWired:**根据属性类型进行自动装配
@Service
public class StuService {
// 定义dao类型属性(不需要添加set方法)
// 添加注入属性组注解
@Autowired
private StuDao stuDao;
public void add(){
System.out.println("service add.....");
stuDao.add();
}
}
@Test
public void testService(){
//1.加载Spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("base9.xml");
//2.获取配置创建的对象
StuService stuService = context.getBean("stuService", StuService.class);
stuService.add();
}
**@Qualifier:**根据属性名称进行注入
@Repository(value = "StuDaoImpl1")
public class StuDaoImpl implements StuDao{
@Override
public void add() {
System.out.println("dao add ......");
}
}
@Service
public class StuService {
// 定义dao类型属性(不需要添加set方法)
// 添加注入属性组注解
@Autowired
@Qualifier(value = "StuDaoImpl1") // 根据名称注入
private StuDao stuDao;
public void add(){
System.out.println("service add.....");
stuDao.add();
}
}
@Resource
@Service
public class StuService {
// @Resource // 根据类型注入
@Resource(name = "StuDaoImpl1") // 根据名称注入
private StuDao stuDao;
public void add(){
System.out.println("service add.....");
stuDao.add();
}
}
@Resource和@Autowired都是做bean的注入时使用,其实@Resource并不是Spring的注解,它的包是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。
①如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
②如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
③如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
④如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。
@Resource的作用相当于@Autowired,只不过@Autowired按照byType自动注入。
@Value
@Value
注解时Spring框架中的一个常用功能,其作用是通过注解将常量、配置文件中的值、其他bean的属性值注入到变量中,作为变量的初始值。
@Service
public class StuService {
// @Resource // 根据类型注入
@Resource(name = "StuDaoImpl1")
private StuDao stuDao;
@Value(value = "WeiSanJin")
private String name;
public void add(){
System.out.println("service add....."+name);
stuDao.add();
}
完全注解开发
@Configuration
@ComponentScan(basePackages = {"com.spring5"})
public class SpringConfig {}
@Test
public void testService(){
//1.加载Spring配置文件
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
//2.获取配置创建的对象
StuService stuService = context.getBean("stuService", StuService.class);
stuService.add();
}
源码探究
BeanDefinitionReader
不同的配置文件,只需要设置对应的解析方式,最后都是加载到BD(BeanDefinition)中去,因此就有了BeanDefinitionReader接口,不同的配置文件对应不同的实现子类。(体现了框架的扩展性)
**补充基础:**抽象类和接口的区别?
语法上的区别:一个是interface一个是abstract
1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;
2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。
涉及层面的区别:接口是自上向下的,而抽象类是自下向上的。
- 接口更像是一种约束,它能约束实现类你必须拥有什么样的功能,实现什么样的方法,但不能约束实现类不做什么功能。
- 抽象类目的在于减 少代码复用,当多个类拥有共同的特性的时候,可以将这部分代码抽象出来,通过继承的方式让子类获得这部分代码的实现。
- AbstractBeanDefinitionReader:实现了 EnvironmentCapable,提供了获取/设置环境的方法。定义了一些通用方法,使用策略模式,将一些具体方法放到子类实现。
- XmlBeanDefinitionReader:读取 XML 文件定义的 BeanDefinition
- PropertiesBeanDefinitionReader:可以从属性文件,Resource,Property 对象等读取 BeanDefinition
- GroovyBeanDefinitionReader:可以读取 Groovy 语言定义的 Bean
AbstractBeanDefinitionReader
该类是实现了 BeanDefinitionReader 和 EnvironmentCapable 接口的抽象类,提供常见属性:工作的 bean 工厂、资源加载器、用于加载 bean 类的类加载器、环境等。
核心方法:loadBeanDefinitions
当传入的参数为资源位置数组时,进入上述方法,如果为字符串数组,则挨个遍历调用。根据资源加载器的不同,来处理资源路径,从而返回多个或一个资源,然后再将资源作为参数传递给 loadBeanDefinitions(resources)
方法。在该类中存在一个 loadBeanDefinitions(Resource... resources)
方法,该方法用于处理多个资源,归根结底,最后还是调用 loadBeanDefinitions((Resource)resource)
方法。
XmlBeanDefinitionReader
这里也体现了,是通过将配置文件转换成流,再通过sax或者dom4j等解析方式,转换成document加载注册。
这个类的具体使用如下:
可以判断出IOC的使用过程,首先获取资源,创建bean工厂,通过工厂创建reader对象,再通过reader加载配置文件。
- 获取资源
- 获取 BeanFactory
- 根据新建的 BeanFactory 创建一个BeanDefinitionReader对象,该Reader 对象为资源的解析器
- 装载资源 整个过程就分为三个步骤:资源定位、装载、注册,如下:
其中注册就是通过BeanDefinitionRegistry 接口来实现的。本质上是将解析得到的 BeanDefinition 注入到一个 HashMap 容器中,IoC 容器就是通过这个 HashMap HashMap 来维护这些 BeanDefinition 的。
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return this.loadBeanDefinitions(new EncodedResource(resource));
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (this.logger.isTraceEnabled()) {
this.logger.trace("Loading XML bean definitions from " + encodedResource);
}
//获取已经被加载的资源集合中的资源集合,如果为null,则开辟空间
Set<EncodedResource> currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
//判断currentResources中是否包含encodedResource,如果有则抛出异常,没有则加入
if (!((Set)currentResources).add(encodedResource)) {
throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
} else {
int var5;
try {
//获取Resource对应的字节流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
//使用字节流创建新的输入源
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
//设置编码
inputSource.setEncoding(encodedResource.getEncoding());
}
//该方法就是创建BeanDefinition的关键
var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
} finally {
inputStream.close();
}
} catch (IOException var15) {
throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var15);
} finally {
((Set)currentResources).remove(encodedResource);
if (((Set)currentResources).isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
return var5;
}
}
loadBeanDefinitions(resource)
是加载资源的真正实现,从指定的 XML 文件加载 Bean Definition,这里会对 Resource 封装成 EncodedResource,主要是为了对 Resource 进行编码,保证内容读取的正确性。封装成 EncodedResource 后,调用 loadBeanDefinitions(encodedResource)
,方法 doLoadBeanDefinitions()
为从 xml 文件中加载 Bean Definition 的真正逻辑。
postProcessor
循环依赖
核心方法:
getBean() -> dogetBean() -> createBean() -> docreateBean() -> createBeanInstance() -> docreateBeanInstance()
需要用两个map将半成品对象和成品对象分开,那为啥还要用到三级缓存呢?
-
三个map也就是三级缓存中,分别存储的什么对象?
- 一级缓存:成品对象
- 二级缓存:半成品对象
- 三级缓存:lambda表达式(getEarlyBeanReference())
-
三个map缓存的查找顺序是什么样的?
- 先查一级缓存找不到二级,再找不到三级。
-
如果只有一个map缓存,能不能解决循环依赖的问题?
- 不能,如果只有一个map,成品和半成品都要放在一个map中,而半成品对象是不能给外部使用的,所以必须做区分,否则就有可能暴露半成品。
-
如果只有两个map缓存,能否解决循环依赖问题?
- 能,但是有前提条件:循环依赖中不包含aop的处理逻辑
-
为什么三级缓存能够解决循环依赖中包含代理对象的问题?
-
创建代理对象的时候是否需要创建原始对象?
- 需要
-
同一个容器中能否出现同名的两个不同的对象。
- 不能
-
如果一个对象被代理,那么代理对象跟原始对象应该如何去存储。
- 如果需要代理对象,那么代理对象创建完成之后应该覆盖原始对象
-
在某个步骤中,出现了覆盖?
- 在getEarlyBeanReference中,会判断是否需要代理对象,如果有了,就要覆盖。
-
在对象对外暴露的时候,如何准确的给出原始对象或者代理对象,因为正常创建代理对象的地方在BPP的后置处理器中,而解决循环依赖问题的时候没执行到那里,因此就用了lambda表达式了,就是一种回调机制,在程序判断需要对外暴露的时候,就执行表达式。
-
依赖循环debug:
一路进入到Spring核心方法AbstractApplicationContext类中的refresh方法:
在走完this.prepareBeanFactory(beanFactory)方法时,Spring已经生成了一个beanFactory,并且通过watch可以看到,一级缓存中此时存放了一些容器属性。
再一直走,此时跳过一些不重要的逻辑处理,找到第一个getBean方法,此时的目的是创建A对象。
进入该方法,发现调用了doGetBean方法,继续进入
可以看到这里有个getSingleton方法,但此时容器中没有A对象的单例实现,还没有到创建对象的环节,所以为null,再往下走,又发现了一个getSingleton方法。
这里的跟上面的有一些不一样,这里传进来的除了bean名称之外还有一个lambda表达式,进入这个方法,此处singletonFactory就是传进来的表达式
而这里调用getObject方法相当于执行表达式内容。
至此,发现了一个关键方法createBean,创建对象刚刚开始,进入,同样也是doCreateBean方法。
继续进入,找到了一个createBeanInstance方法,这个方法就是来创建对象实例的,也就是用了反射
至此A对象就已经创建好了一个实例,并存了个lambda表达式到三级缓存,并且准备为这个A对象填充属性
拿到属性名b
这里拿到的是一个b的运行时引用类型对象,不是我们所需要的B类对象,此时就需要做处理了
这个处理的过程其实就是开始创建对象了,循环正式开始了,此时三级缓存中存的是A类对象的lambda。
容器中和缓存中依旧是没有b对象,因此开始重复上面步骤。一直到需要给b对象赋值,如果没有做出处理,就又需要创建A对象,循环往复,因此这里用了三级缓存做处理。
循环依赖总结
- A创建对象 -> 实例化得到原始对象-> 通过原始对象生成对象工厂存入三级缓存(提前暴露) -> 属性注入B对象
- B创建对象 -> 实例化得到B的原始对象 ->通过该原始对象生成对象工厂存入三级缓存(提前暴露) -> 需要注入A对象
- 从三级缓存中获取对象工厂,通过调用getObject方法进行生成A对象(或者是代理对象),将该对象存入二级缓存。
- B对象注入了未初始化的A对象,完成生命周期 ,返回该对象,完成A的属性注入,再经过生命周期完成创建。
两个问题:
- 三级缓存什么情况下才有用?需要有AOP的情况下才有用,没有AOP的循环依赖存入到三级缓存中的对象工厂其实什么事也没做,直接返回对象。
因为,Spring的设计原则在于,AOP代理对象的生成一般是在初始化阶段的后置处理器中实现,而如果这样做,在循环依赖发生的情况下,最后经过完整生命周期的对象是该对象的代理对象,但是,在属性注入的时候,仍然注入的是原始对象,就会出现矛盾。
- B对象中注入的是未初始化的A对象,这样做合理吗?
合理,因为后面所有步骤操作的A对象都是注入那个A对象的引用,都指向同一个地址,后面A完成生命周期的同时,B中的A也会一起完成,成为一个完整的A对象。