控制反转、依赖注入,相当于咱不需要自己创建对象,不需要关注对象创建的过程了,要啥对象直接向Spring容器索要即可(Spring 通过反射创建),而我们只需要告诉它需要什么样的对象(bean,bean定义,xml或者注解配置)
控制反转,把对象创建和对象之间的调用过程交给Spring管理,为了降低耦合度。
控制反转和依赖注入降低了应用的代码量。
松耦合,便于维护。
支持加载服务时的饿汉式初始化和懒加载。
单例模式可以分为懒汉式和饿汉式。
懒汉式就是创建对象时比较懒,先不急着创建对象,在需要加载配置文件的时候再去创建。
饿汉式就是在系统初始化的时候我们已经把对象创建好了,需要用的时候直接拿过来用就好了。惰性加载机制(或懒加载、延时加载),也就是说只有当使用到这个实例的时候才会创建这个实例
原始方式:耦合度非常高
IOC思想基于IOC容器完成,IOC容器底层就是对象工厂。
Spring提供两种实现IOC容器的方式:
BeanFactory:IOC容器的基本实现,Spring内部的接口,一般不提供开发人员使用。
ApplicationContext:BeanFactory的子接口,提供更多强大功能,一般开发人员使用。
Spring 创建对象
创建普通类,类里面创建普通方法
public class User{
public void add(){
system.out.println("add....");
}
}
创建Spring配置文件,在配置文件中创建对象,新建base.xml配置文件
<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">
<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方法
<bean id="book" class="com.spring5.Book">
<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>
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名称空间在配置文件中。
<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>
字面量
字面量为等号右侧的值,字面量是由字符串,数字等构成的字符串或数值,是固定的
null值:通过标签实现
包含特殊符号的属性值,要么用转义字符,要么使用CDATA格式
//方法一:转义字符
<property name="address" value="<北京&dt;">property>
//方法二:CDATA
<property name="address">
<value>
]]>
value>
property>
外部bean
通俗理解就是一个类中需要用到了另外一个类的对象,就是给一个bean注入一个外部bean,就需要用到ref属性。
<bean id="userService" class="com.spring5.service.UserService"> //对象一
<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>
这三种赋值方式都有些什么区别?
<bean id="stu" class="com.spring5.collectionytpe.Stu">
<property name="courses">
<array>
<value>Java课程value>
<value>数据库课程value>
array>
property>
<property name="list">
<list>
<value>张三value>
<value>小三value>
list>
//如果集合里的存储的是其它bean对象,就需要引用,同时也需要配置引用的bean
<list>
<ref bean="course1">ref>
<ref bean="course2">ref>
list>
property>
<property name="maps">
<map>
<entry key="Java" value="java">entry>
<entry key="PHP" value="php">entry>
map>
property>
<property name="sets">
<set>
<value>Mysqlvalue>
<value>Redisvalue>
set>
property>
bean>
<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
<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集合提取
<util:list id="bookList">
<value>三国演义value>
<value>水浒传value>
<value>西游记value>
<value>红楼梦value>
util:list>
<bean id="book" class="com.spring5.collectionytpe.Book">
<property name="list" ref="bookList">property>
bean>
初始化过程:BeanDefinition的资源定位、解析、注册。
单例Bean的初始化以及依赖注入一般都在容器的初始化阶段进行,除非设置了懒加载 lazy-inint为true的单例bean就是在第一次调用getBean() 方法的会后进行初始化和依赖注入。
多例Bean在容器启动时不实例化,必须要在getBean调用的时候才实例化。
loadBeanDefinitions 采⽤了模板模式,具体加载 BeanDefinition 的逻辑由各个⼦类完成。
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的生命周期主要包含四个方面:
仔细细化来看,首先来看实例化对象部分
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属性;
在bean对象内添加@PostConstruct 注解后即可实现初始化的功能,被@PostConstruct修饰的方法会在构造函数之后,init()方法之前运行。 有多个则会执行多次;
spring 初始化方法之一,作用是在BeanFactory完成属性设置之后,执行自定义的初始化行为。
执行顺序:在initMethod之前执行,在@PostConstruct之后执行
bean 配置文件属性 init-method 用于在bean初始化时指定执行方法,用来替代继承 InitializingBean接口,
注意的一点是只有一个类完整的实例被创建出来后,才能走初始化方法。
到这一步,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垃圾处理器会自动将无用的对象进行回收操作;
根据指定装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入
<bean id="emp" class="com.spring5.autowire.Emp" autowire="byName">
bean>
<bean id="dept" class="com.spring5.autowire.Dept">bean>
数据库信息配置
直接配置
<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>
Spring针对Bean管理中创建对象提供注解
<!-- 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();
}
开启组件扫描细节配置
<context:component-scan base-package="com.spring5" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
context:component-scan>
<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
该类是实现了 BeanDefinitionReader 和 EnvironmentCapable 接口的抽象类,提供常见属性:工作的 bean 工厂、资源加载器、用于加载 bean 类的类加载器、环境等。
核心方法:loadBeanDefinitions
当传入的参数为资源位置数组时,进入上述方法,如果为字符串数组,则挨个遍历调用。根据资源加载器的不同,来处理资源路径,从而返回多个或一个资源,然后再将资源作为参数传递给 loadBeanDefinitions(resources)
方法。在该类中存在一个 loadBeanDefinitions(Resource... resources)
方法,该方法用于处理多个资源,归根结底,最后还是调用 loadBeanDefinitions((Resource)resource)
方法。
XmlBeanDefinitionReader
这里也体现了,是通过将配置文件转换成流,再通过sax或者dom4j等解析方式,转换成document加载注册。
这个类的具体使用如下:
可以判断出IOC的使用过程,首先获取资源,创建bean工厂,通过工厂创建reader对象,再通过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也就是三级缓存中,分别存储的什么对象?
三个map缓存的查找顺序是什么样的?
如果只有一个map缓存,能不能解决循环依赖的问题?
如果只有两个map缓存,能否解决循环依赖问题?
为什么三级缓存能够解决循环依赖中包含代理对象的问题?
创建代理对象的时候是否需要创建原始对象?
同一个容器中能否出现同名的两个不同的对象。
如果一个对象被代理,那么代理对象跟原始对象应该如何去存储。
在某个步骤中,出现了覆盖?
在对象对外暴露的时候,如何准确的给出原始对象或者代理对象,因为正常创建代理对象的地方在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对象,循环往复,因此这里用了三级缓存做处理。
循环依赖总结
两个问题:
因为,Spring的设计原则在于,AOP代理对象的生成一般是在初始化阶段的后置处理器中实现,而如果这样做,在循环依赖发生的情况下,最后经过完整生命周期的对象是该对象的代理对象,但是,在属性注入的时候,仍然注入的是原始对象,就会出现矛盾。
合理,因为后面所有步骤操作的A对象都是注入那个A对象的引用,都指向同一个地址,后面A完成生命周期的同时,B中的A也会一起完成,成为一个完整的A对象。