Spring核心编程思想学习 |
您所在的位置:网站首页 › ioc的作用是什么 › Spring核心编程思想学习 |
学spring就是学它的各种后置处理器和扩展接口,生命周期的各个执行时间点,各个接口一看你就知道它在什么时候执行,这个东西学会了,你可以在spring中为所欲为。ioc,aop都是小儿科。
对于Ioc的理解? IOC说起来,其实是一种编程思想或原则。相对于传统的编程方式(如从前文到后文这样串行顺序的方式),比如用传统方式,当我依赖一个对象,我要去创建它,对它进行属性配置,然后我才能使用这个对象。 但是对于IOC这种方式来说,它使对象或者组件的创建更为透明,不需要过多的关注细节(如创建对象、给对象设置属性,这些ioc容器都给我们设置好了),以达到解耦的目的。控制反转,简单来理解其实就是把获取依赖动象的方式,交给IOC容器来实现,由主动拉取,变为被动获取。 并不是说传统模式不好,IoC 主要是让流程反转,更多的组件创建流程更透明,不需要过多的关注细节,让你更专注于做你自己的事情! 实际上,IoC 是为了屏蔽构造细节,new 出来的对象,任何生命周期的细节使用端都是知道的,如果在没有容器的前提下,IoC 是没有存在的必要,不过由于复杂的系统,应用更关注地是对象的运用,而非它的构造和初始化细节。
ioc和DI的区别 DI是ioc的一种实现方式而已 IOC编程思路实现有多种: 其中 1.依赖查找 就是应用程序里面还是要调用容器的bean查找接口查找bean实例 缺点:还是有侵入性,性能低。 2.依赖注入 直接在容器启动时通过构造器,参数,getter setter,接口等形式注入依赖。 优点:性能高,侵入小 3.上下文依赖查询 4.模板方法设计模式 JdbcTemplate的使用,不需要关心具体callback来源 传统方式,需要自己调用 5.策略模式
ioc设计目标: 实现bean的管理,让应用程序不用过度关注bean的生命周期及依赖。 ioc包含了: bean生命周期管理,依赖的处理:依赖查找及依赖注入 配置管理:资源文件,spring容器的管理等
javaBeans也是ioc容器? 这节没听太懂,不知道有什么用
构造器注入与setter注入 构造器注入是绝对不允许循环依赖存在的,因为ta要求被注入的bean都是成熟态,而字段注入与setter注入没有这样的要求,同时springboot in action和我还有老师的观点一致,推荐构造注入,因为如果出现循环依赖或者多个依赖代码不够优雅的情况那都是自身的设计有问题应该好好反省反省自己代码结构了
面试题: 1. 什么是ioc IOC:就是一个控制反转的思想,具体有两种形式去实现:依赖查找和依赖注入 依赖查找就是需要一个容器API在业务代码去调用获取对应的依赖,缺点很明显需要依赖容器API,代码侵入会大于后面讲的依赖注入。 依赖注入则是自动将依赖注入到bean的依赖属性上,在运行时无法再调用容器API去获取依赖属性了,直接像用普通java属性一样即可。 具体实现有spring的ioc(依赖注入),servlet里面用jndi去加载资源(依赖查找),更广义的消息异步机制也是IOC的一种机制(消息发送者不需要同步去调用结果了,由消息接收者通知消息发送者结果) spring IOC的优势:实现了依赖查找及依赖注入两种形式,配置少易使用。其次按需配套了spring事务,aop等其它第三方整合(生态)。
application.getbean(xx,class),这属于依赖查找吗? 作者回复: 是的,等同于BeanFactory#getBean
有个问题想问一下,我理解的延迟查找应该是获取了ObjectFactory后user并没有被初始化,而是在ObjectFactory获取对象的时候User才会被加载,所以我想通过在User的无参构造里打印一下无参构造的调用时间来证明我的想法,但是在获取beanfactory的时候无参构造就被调用了,不是说beanFactory在getbean之前是不实例化bean的吗? 小马哥讲的是延迟查找,你测试的是延迟加载。ObjectFactory的getObject方法是从beanFactory中获取实例(new ClassPathXmlApplicationContext时会执行refresh方法,作用简单来说就是先自己创建一个类型为ListableBeanFactory的beanFactory,然后将user实例化之后放到这个beanFactory),这个user是你在new ClassPathXmlApplicationContext时就已经实例化好了的。所以从实例化的层面看,先获取objectFactory实例再调用getObject方法获取user,就跟调用beanFactory的getBean方法获取user是一样的;但是从依赖查找的方向看,延迟查找是直接让objectFactory根据targetName关联对应的实例也就是你的user(相当于给beanFactory指定了getBean传入的id或name),后面再调用getObject时就可以直接获取到实例了,这是延迟查找。懒加载的实现是在xml配置文件中对应的bean标签里设置lazy-init="true",DefaultListableBeanFactory会根据bean的懒加载的情况判断是否需要立刻实例化bean,然后在getBean的时候才会真正地实例化bean 总结: ObjectFactory 仅是一个操作对象,当依赖注入或依赖查找该 ObjectFactory 对象时,实际内嵌的 Bean 需要 getObject() 方法来获取。
ObjectFactoryCreatingFactoryBean ob = (ObjectFactoryCreatingFactoryBean) beanFactory.getBean("objectFactoryCreatingFactoryBean"); 这种根据名称查找编译直接报错,说类型转换错误。而根据类型查找确可以正确编译ObjectFactoryCreatingFactoryBean ob = (ObjectFactoryCreatingFactoryBean) beanFactory.getBean(ObjectFactoryCreatingFactoryBean.class); 这一点没搞懂,希望老师指点一下 作者回复: 这个是 BeanFactory 实现决定,当 name 关联的 Bean 为 FactoryBean,实际返回的对象是 FactoryBean#getObject(),请参考 org.springframework.beans.factory.support.AbstractBeanFactory#getObjectForBeanInstance 方法。
ObjectFactory 通常是针对单类 Bean 做延迟获取的,BeanFactory 则是全局 Bean 管理的容器。
spring ioc提供了那些依赖注入模式
依赖查找和依赖注入依赖不同源 依赖注入的方式: 名称, 类型 ,容器内建依赖对象:如beanFactory, 非 bean对象(最后这个有点疑问) 手动配置是一种硬编码的方式,使用自动装配实现依赖注入
延迟注入:使用ObjectFactory ,userRepository.getObjectFactory().getObject() == applicationContext != userRepository.getBeanFactory() applicationcontext不等于beanfactory
spring ioc依赖的三个来源 1. 自定义Bean(自己用xml配置或注解配置的bean) 2. 内部容器依赖的Bean(非自己定义的Bean,spring容器初始化的Bean) 3.内部容器所构建的依赖(非Bean,不可通过获取依赖查找Bean的方法来获取(getBean(XXX)))
自定义的bean和内建的bean能够通过依赖查找查询到,但是内建依赖是不能通过依赖查找查询到,因为他们的来源不 内建依赖和自定义Bean的区别 1.内建依赖指的是DefaultListableBeanFactory属性resolvableDependencies这个map里面保存的bean,自定义bean指的是通过DefaultSingletonBeanRegistry#registerSingleton手动注册的bean。它们都在BeanFactory里面; 2.依赖注入的时候比如@AutoWired(AutowiredAnnotationBeanPostProcessor处理)会调用DefaultListableBeanFactory#resolveDependency方法去resolvableDependencies里面找,而依赖查找BeanFactory.getBean(xxx)是不会去resolvableDependencies这个map里面找的。 所以视频里面依赖查找内建依赖报错了
spring中通过依赖注入可以获取到代理对象,而同一个类中方法之间的直接调用用的是原对象,我应该怎么理解单例bean中的代理对象和原对象呢 作者回复: 这个要看,如果是 @Configuration 标准的 Class 所对应的 Bean 是会被 CGLib 提升的,而 @Bean 则则不会,这是由于技术限制所致。所以通常需要 org.springframework.aop.support.AopUtils#getTargetClass 方法获取真实的 Class
BeabFactory和ApplicationContext的关系 BeanFactory和ApplicationContext有何区别? ● ApplicationContext是BeanFactory的子接口 ● BeanFactory是一个底层的IOC容器,提供了IOC容器的基本实现,而ApplicationContext则是BeanFactory的超集提供了丰富的企业级特性。 ● ApplicationContext是委托DefaultListableBeanFactory来实现Bean的依赖查找和依赖注入。 BeanFactory和ApplicationContext的关系,从设计模式上讲,应该是代理模式吧? 作者回复: 是的,ApplicationContext 是使用了代理模式,也使用了面门模式。 userRepository.getBeanFactory() == applicationContext // false userRepository.getBeanFactory() == applicationContext.getAutowireCapableBeanFactory() // true 注入userRepository的beanFactory,其实是ClassPathXmlApplicationContext的父类 AbstractRefreshableApplicationContext的成员变量 DefaultListableBeanFactory beanFactory 1.文中xml中自动注入的BeanFactory为DefaultListableBeanFactory,是因为在AbstractApplicationContext#prepareBeanFactory方法中beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory)中指定了接口的实现为自己bean对象也就是DefaultListableBeanFactory(最后是放在一个map中) 2.我们通过classPathXmlApplicationContext创建的bean对象为ApplicationContext类型,虽然继承了接口BeanFactory但是依赖注入的并不是它所以不相等 3.ApplicationContext中每次getBean()都是通过DefaultListableBeanFactory来查找bean对象的 了解了ApplicationContext和BeanFactory之间的关系,也知道了DefaultListableBeanFactory被组合到了AbstractRefreshableApplicationContext,但好像视频并没有回答一个问题:UserRepository中的BeanFactory beanFactory属性为什么通过autowired=“byType”,注入的是DefaultListableBeanFactory,而不是ClassPathXmlApplicationContext?既然他们两者属于BeanFactory接口,按type注入,为什么不是ClassPathXmlApplicationContext?是框架哪里设置的吗? ApplicationContext是BeanFactory的子接口,那UserRepository的beanFactory自动装配的时候不就有多个类型为BeanFactory的bean了吗,一个是我们创建的ApplicationContext,另一个是它内部的DefaultListableBeanFactory,为什么没有报错,而是直接装配了内部的DefaultListableBeanFactory作者回复: 参考一下我给 aksonic 回复的内容,里面已经提到了,它是一种特殊的依赖注入。 答案在 org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory 方法中,其中: beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory); beanFactory.registerResolvableDependency(ResourceLoader.class, this); beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this); beanFactory.registerResolvableDependency(ApplicationContext.class, this); 以上代码明确地指定了当需要被注入的字段类型是 BeanFactory 类型时,那么最终被注入的对象是 ApplicationContext#getBeanFactory() 方法的返回的内容,而非ApplicationContext自身。
在UserRepository中注入的beanFactory是DefaultListableBeanFactory,若我将代码private BeanFactory beanFactory;改为private ClassPathXmlApplicationContext classPathXmlApplicationContext;然后给出get、set后,userRepository.getClassPathXmlApplicationContext()==beanFactory则为true,那么此时我是不是就拿到了比DefaultListableBeanFactory还要牛的classPathXmlApplicationContext,然后可以为所欲为了? 作者回复: 能不能为所欲为不确定,但是可以干很多“坏“事。
applicationContext除了ioc容器的角色,还有哪些功能
我们常说spring的核心就是IOC和AOP,但是BeanFactory和ApplicationContext才是真正的IOC容器,ApplicationContext又提供了AOP的功能,那之前的说法是不是有点问题? 作者回复: 并不矛盾,BeanFactory 是 Bean 容器,它不提供企业特性,比如 AOP、事务以及 事件等,这些都被 ApplicationContext 支持 依赖注入是(BeanFactory) spring-beans 提供的 但是需要(@Autowired @Bean) spring-context 提供驱动 理解: 业务应用不直接接触 BeanFactory 而是通过 @Autowired @Bean 这种方式获取依赖注入的能力。
beanFactory 运用的什么设计模式 我看到了模板 适配器 其他看不出来 可以说下嘛 作者回复: DefaultListableBeanFactory 实现的设计模式有: 抽象工厂(BeanFactory 接口实现) 组合模式(组合 AutowireCandidateResolver 等实例) 单例模式(Bean Scope) 原型模式(Bean Scope) 模板模式(模板方法定义:AbstractBeanFactory) 适配器模式(适配 BeanDefinitionRegistry 接口) 策略模式(Bean 实例化) 代理模式(ObjectProvider 代理依赖查找)
spring IoC容器启动(主要阶段): 1.前期准备工作(记录IoC容器启动时间,校验必要的属性值...) 2.创建一个BeanFactory,注册一些内建的Bean对象或者Bean依赖,和内建的非Bean的依赖 3.对BeanFactory进行扩展,通过BeanFactoryPostProcessors 进行操作 4.对Bean的进行扩展,通过BeanPostProcessors 进行操作 5.做一些国际化的资源设置 6.完成一个IoC容器启动事件的广播 applicationcontext.refresh()用到了模板模式,因为obtailFreshFactory()是需要子类自己去实现,但是整个刷新启动过程是固定的。 IoC容器的停止(主要阶段): 1.销毁容器里面的所有Bean对象 2.销毁BeanFactory
application相关的context需要调用refresh开启上下文,但是beanfactory是不需要调用的吧?我理解的对么。。。 作者回复: 你的理解是正确的,BeanFactory 实际上没有很细粒度的生命周期,通常是由 ApplicationContext 来驱动。
面试题 Spring Bean有两种实现,普通Bean,和工厂Bean(FactoryBean) 实现工厂Bean的方法就是pojo继承FactoryBean,并实现他的方法,当容器通过getBean()获取bean时,返回的是实现的getObject()方法所返回的对象 这个factorybean在看源码的时候也非常的不解,对于我们使用创建复杂的bean来说是不是用@Bean这个注解就可以完全代替factorybean呢,是不是factorybean的用意本身只是更好的去帮助Spring的一些内键Bean,而不是对我们用来扩展的,如果用来扩展Bean的话,@Bean是不是更方便 简洁一些。 作者回复: @Bean 确实可以做到,不过 FactoryBean 很早的接口,当时还不支持 Annotation
springIOC容器为什么一定要把xml(或者注解类)解析成BeanDefinition,而不是在解析的时候直接实例化,为啥要多加这么一层? 作者回复: BeanDefinition 是 Bean 的配置,里面有不少的元信息,包括延迟加载、scope、以及属性配置等,这样的话,可以有更多扩展空间。
小马哥,我这里有个问题困扰我很久了,您课件上也说了BeamDefinition的class只能是具体类,不能是抽象类或者接口, 但是在mybatis的源码中 是通过spring中的那个扫描器,根据@mapper去扫描有这个注解的接口,并且返回了BeanDefinition 为什么这里可以为接口生成BeanDefinition呢? 作者回复: 你可以忽视了一个细节,@Mapper 标注的接口实际的对象是动态代理,BeanDefinition 可以关联具体实例,也可以动态代理产生的实例。
Bean 名称生成器(BeanNameGenerator) //Spring Framework 2.0.3 引入,框架內建两种实现 //1.DefaultBeanNameGenerator:默认通用 BeanNameGenerator 实现 /* generatedBeanName 如果全路径类名为空 -父Bean名字存在,假设为Parent,generatedBeanName = "Parent$child" -工厂类Bean存在,假设为FactoryBean,generatedBeanName = "FactoryBean$created" 接着, -为内嵌Bean,id = generatedBeanName#随机数 -非内嵌Bean,id = generatedBeanName#递增数 */ //2.AnnotationBeanNameGenerator:基于注解扫描的 BeanNameGenerator 实现,起始于Spring Framework 2.5 /* 判断是为注解-@Component等,且指定value值,直接返回value值 相反,根据spring规则生成, -如果发现类的前两个字符都是大写,则直接返回类名; MYService -将类名的第一个字母转成小写,然后返回 MyService -> myService
AnnotatedBeanDefinitionReader的方式我记得在dubbo里用过,咋不讲下 作者回复: 因为我已经讲过了,请参考小马哥技术周报 第二十七期 - Apache Dubbo 设计与实现系列之注解驱动,Github 地址:https://github.com/mercyblitz/tech-weekly
spring bean实例化的方式 ● 构造器方式初始化 ● 静态工厂方法初始化 ○ 比如定义一个User类并在其内部定义一个createUser()的静态方法,然后在XML中bean标签的factory-method属性指定该createUser()静态工厂方法,从而让ioc容器按照咱们自己在createUser()中定制化的实例化细节,进行User对象的实例化 ● 工厂方法方式初始化 ○ 比如定义一个UserFactory类和内部的一个createUser()非静态方法;然后在XML中定义一个bean标签并指定其属性factory-bean和factory-method分别为前工厂的对应类和方法名。 ● FactoryBean方式初始化 ○ 定义一个UserFactoryBean并实现FactoryBean接口并实现其getObject()方法和getObjectType()方法。然后直接再XML中定义bean标签其class属性就是UserFactoryBean全限定名称即可。 ● JDK的ServiceLoader方式初始化(SPI) ○ 在classpath的META-INF/servives下创建一个文件,名称是某接口的全限定名称,文件无后缀。文件中直接写上接口实现类的全限定名。然后使用ServiceLoader相关的api即可实现接口实现类的实例化。 ● ServiceLoaderFactoryBean获取ServiceLoader方式初始化,类似上一个。 ● AutowireCapableBeanFactory的createBean(Class class)方法初始化 ○ 注意class参数,不能是接口或者抽象类,否则实例化会报错
初始化方式 见名知意 postconstruct 构造后置 afterpropertiesset 属性填充后 initmethod bean初始化(可以认为是功能初始化) 问一下, //1.通过BeanDefinitionBuilder 构建 BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(User.class); //设置初始化方法 beanDefinitionBuilder.setInitMethodName("ztTest"); //获取BeanDefinition实例 BeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition(); System.out.println(beanDefinition.getInitMethodName()); 这边怎么只能得到这个初始化方法的名字,为什么这个自定义方法不执行? public void ztTest(){ System.out.println("多帅啊!"); } 作者回复: BeanDefinition 仅是定义 Bean,当 BeanDefinition 变为Bean 后,容器将会调用 "zkTest" 方法。
小马哥我换个问法,使用@Configuration 在spring boot2中是不是有使用applicationContext.register(BeanInitializationDemo.class)的效果? 作者回复: 实际上上面两种用法是一样的,因为它们都是 Spring ApplicationContext 注册特性。
延迟初始化 请问小马哥 为什么在循环依赖的时候加上@Lazy注解 就可以解决掉呢? 是因为在初始化的时候大家一起互相注入会冲突.等到用到的时候再去初始化就不会冲突了么?原理是什么 可以解答一下嘛? 作者回复: @Lazy 注解加到@Bean注解修饰的返回Bean对象的方法后,该方法返回的对象实际上是一个代理(也就是一个占位符) ,当某上层对象,用该方法返回的Bean对象,完成依赖注入时,实际的该方法返回的 Bean 还没有完成初始化,只有首次调用该方法返回的Bean的方法或字段时,才开始进行该Bean的初始化
小马哥,我在用spring security的时候,要注入AuthenticationManager,然后它是通过覆盖WebSecurityConfigurerAdapter的方法注入的,这样初始化的: @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } 我在其他类中要使用到这个注入的对象,然后在另一个Bean中通过 @Autowired private AuthenticationManager authenticationManager; 进行注入,有时项目在启动的过程中会出现循环依赖的问题,后面加入@Lazy解决了,我想知道具体的原因 作者回复: 主要原因在于类扫描过程中的不确定性,因为 @Configuration class 注册时是按照扫描顺序,当顺序不确定时,Bean 在生命周期过程中,将会按照BeanDefinition 的注册顺序逐一实例化和初始化,所以可能导致循环依赖等问题。
疑问: 延迟初始化,有什么应用场景? lazy注解到底是,延迟初始化,还是延迟实例化?
作者回复: 这是因为 @PreDestroy 需要 JDK 5 支持,并且在 JDK 6 开始成为 JDK 的一部分,所以这是高版本的支持,那么在这之前,Spring 是兼容 JDK 5 之前的版本,故使用接口来实现
上面案例,就是演示的如何使用外部单体对象,注册成为一个spring bean
依赖查找是基础,依赖注入是在依赖查找的基础上,框架层面辅助绑定的方法
依赖查找的三种类型 可以看到spring 也是实现了这三种依赖查找,或者说借鉴了上面的三种方式,不过spring 实现得更优雅一些
可以了解到,BeanFactory这个最基础的接口,就是用来进行“单一类型依赖查找”的,后来的集合类型查找等功能,有更多的BeanFactory接口的子接口来承接!
通过ObjectProvider实现延迟查找
小马哥你好,如果此时容器内有两个String类型的Bean,通过ObjectProvider#getObject()获取时就会提示expected single matching bean but found 2异常,那么ObjectProvide针对这种情况是应该如何处理呢 作者回复: ObjectProvider允许使用泛型来查找集合类型,比如 ObjectProvider
咨询一下,这里的延迟查找和非延迟查找的区别是什么以及对应的应用场景有哪些? 作者回复: 延迟查找:主要用于暂时性地获取某个 Bean Holder 对象,如果过早的加载,可能会引起未知的状态,比如,当 A bean 依赖 B bean 时,如果过早地初始化 A,那么 B bean 里面的状态可能是中间状态,这样容易导致一些错误
通常在表达单一与复杂时,可以使用继承 比如这里,在表达单一类型依赖查询,用了Beanfactory接口,表达复杂的集合类型依赖查找时,使用了继承下来的ListableBeanFactory接口。 对于集合类型依赖查找,通过BeanFactory#getBeanNamesForType 和BeanFactory#getBeansForType,两个依赖查找方法,前者不会强制bean的初始化,而是通过BeanDefinition和FactoryBean的getClassType进行判断;后者会强制Bean的初始化。 还有理解了为什么FactoryBean里面要有getObjectType() 方法,它是用于根据类型的依赖查找 最后的结论,ListableBeanFactory可以通过某个类型去查找一个集合的列表,集合列表可能有两种情况,一种是查询Bean的名称、还有一种是查询Bean的实例。推荐使用Bean的名称来判断Bean是否存在,这种方式可以避免提早初始化Bean,产生一些不确定的因素。
层次性依赖查找
Configurable就是可配置的,可修改的,可写的意思。 HierarchicalBeanFactory提供了一个getParentBeanFactory()的读接口,ConfigurableBeanFactory提供了setParentBeanFactory()这样一个写接口
这里就是用了组合模式,把层次性依赖查找和集合类型查找,组合在了一个接口中!
设置双亲BeanFactory
基本都是使用单一查找,依赖查找可以在LB这种场景应用,但这个分层查找有啥用? 比如 Spring MVC 中,Biz 组件放在 Root ApplicationContext,而 Web 组件放在 DispatcherServlet 的 ApplicationContext,后者是前者的子 ApplicationContext,所以子 ApplicationContext 能读取父ApplicationContext
设置父容器的方法是setParentBeanFactory(BeanFactory parentBeanFactory)。要注意这个方法是在ConfigurableBeanFactory中定义的,只有其继承者才有这个方法来设置父容器。 但是ApplicationContext没有继承ConfigurableBeanFactory这个接口,似乎ApplicationContext及其实现类都不能设置父容器。但是ApplicationContext是继承了HierarchicalBeanFactory的,它应该是具有层次性的。事实上ApplicationContext也确实可以设置父容器。 其中的奥秘是这样的,表面上看,ApplicationContext是继承BeanFactory,但实际上两者的关系是代理模式。 ApplicationContext需要实现容器的基本功能时,就会拉出其内部持有的BeanFactory类型的成员变量,让这个beanFactory来实现那些基本的容器功能,然后ApplicationContext再稍微润色一下。 真正具有容器层次性的是BeanFactory而不是ApplicationContext。当我们说给ApplicationContext设置父容器时,是指给ApplicationContext内部持有的BeanFactory成员变量设置父容器。 老师,我这样的理解对吗? 作者回复: 理解没错,不过推荐你看一下 ConfigurableApplicationContext
加入了java8的函数式接口参数,表示当延迟查找没有找到User对象时,就调用函数式接口,直接创建一个User并返回
延迟查找的几种方式
延迟查找,我个人认为主要是给架构开发者使用的。非常典型的一个使用场景,就是SpringBoot里的自动配置,可以看看LettuceConnectionConfiguration这个类,这个类用于创建RedisClient。显然,RedisClient的创建是要根据用户的需求来创建的。 有些属性是必须的,可以在配置文件里配置,比如ip,port这种。Lettuce在创建RedisClient的时候,会从配置文件里读取这些数据来创建RedisClient。 但有些属性是非必须的,而且不能在配置文件里配置,比如开启读写分离,RedisClient在Lettuce内部,是通过一个Builder来创建的,如果要开启读写分离,这需要你在这个Builder在执行build的过程中,额外加一行:clientConfigurationBuilder.readFrom(ReadFrom.REPLICA); 问题就在这里,怎么让业务开发人员把这行代码加入到其内部的build流程中?这个问题,一种比较常见的思路,是使用模板方法,写一个抽象方法,调用它。具体的实现交给开发人员。 所以Lettuce设计了一个LettuceClientConfigurationBuilderCustomizer的类,他有一个customize方法,并且把上面提到的Builder作为这个方法的参数传递进来。开发人员,如果能去配置LettuceClientConfigurationBuilderCustomizer这样一个类,就能达到上述的目的。 但问题是,如果是开发人员去配置这样一个类,说明LettuceClientConfigurationBuilderCustomizer这个类还没有被实例化。但根据模板模式,流程中必须调用LettuceClientConfigurationBuilderCustomizer这个类的抽象方法,才能达到目的。 这个时候延迟加载,ObjectProvider的作用就体现出来了。他可以规定,他产生的是一个LettuceClientConfigurationBuilderCustomizer的对象,并且指定这个对象产生以后,做什么事情。比如调用customize方法。 如果用户配置了LettuceClientConfigurationBuilderCustomizer对象。那么在创建RedisClient的流程中,ObjectProvider就能拿到该对象,然后按照预先指定的动作执行,比如执行customize方法。 如果用户没配置,那么拿不到Bean对象,就什么都不做。 所以这个场景,我认为是延迟查找的一个典型实现 小结: 整体,上面的没太看明白
ObjectProvider 是间接的依赖查找,ObjectProvider 相当于一个代理,实际依赖查找发生在ObjectProvider#get 等方法时 延迟查找并非是Bean的延迟加载,跟@Lazy是两码事,ObjectProvider#getxxx 方法 底层还是通过BeanFactory来进行依赖查找的,但是在进行依赖查找前,可以制定以下规则,比如Bean找到后,再设置额外的属性,完成一些用户的自定义需求;Bean没有找到,该如何处理 使用ObjectProvider既可以实现单一类型查找也可以实现集合类型查找? 且都是延迟查找 作者回复: objectProvider 获取后,不会马上去创建或者查找 Bean 对象,相当于延迟操作
安全查找 不安全的查找,就比如要查找的bean不存在时,会抛出NoSuchBeanDefinitionException
注意看,spring默认的BeanFactory是,单一查找,集合查找,层次查找都具备的。
马哥您好,为什么说安全的依赖查找比较好呢?我理解不安全的,会抛出异常,可以告诉程序员这里出现了问题,而安全依赖查找,感觉像是做了生吞异常的处理,不是很理解,忘马哥解答! 作者回复: 因为 ObjectProvider 依赖查找 Bean 的方式允许 Bean 不存在,也就是说,它具备容错性,比如第三方框架需要判断应用是否有自定义组件存在,通常用 ObjectProvider 查找更好,建议你可以参考一下 MyBatis 的 MyBatisAutoConfiguration 的构造器,里面就使用了注入 ObjectProvider 的方式,查找相关的 Bean。
内建依赖 依赖注入分为两种,一种就是@Autowired注解负责的外部Bean的注入,另一种就是@Value注解负责的外部属性配置的注入
老马哥 什么时候基于 Apollo或者nacos 详细讲讲 是怎样的思路在Sping 中做扩展组件的呀 十分期待 作者回复: 这块我已经讲过了,感兴趣的话,可以参考我的技术周报:https://github.com/mercyblitz/tech-weekly
可能出现,父类异常吞掉子类异常的情况
beanDefinitionMap本身是ConcurrentHashMap,线程安全是因为,对beanDefinitionMap的多步骤存或取操作外,加了synchronized。
依赖注入
一般开发多用手动模式,且一般只用手动模式中的第一和第二种,第三种一般给spring 的扩展开发人员使用。但是,其实底层第一种和第二种都是使用的第三种方式来实现的。
自动绑定的最大缺点,就是带来了不确定性。我们写程序,还是要万无一失,所以一般就只使用手动模式。 视频里面说的Autowiring使用不多指的是AbstractBeanDefinition的autowireMode这个属性默认是no,意思是一个spring bean里面的属性spring不自动注入,必须自己加注解(@Autowired)等方式注入。如果不为no,即使不使用@Autowired注解也能将属性注入。因为autowireMode为no,所以要使用@Autowired注解手动注入,这两个根本不是一个东西。
Setter注入
手动模式下的,java注解方式的构造器注入
手动模式下的,java注解方式的setter注入
手动模式下,spring api的setter注入
构造器注入
setter注入调用的顺序是不确定的,构造器注入,是按照顺序进行创建的 作者回复: 主要是因为 Java 反射 API 获取 Method 列表是不确定的 而构造器注入时,构造器的参数列表顺序是固定的
1,配置类本身,在ioc容器中也是一个bean 2,字段注入,主要指的是实例字段,静态字段是不能使用@Autowried注入的。
方法注入
构造器注入是没有办法解决循环依赖的,循环依赖就应该避免。
Spring 是如何创建一个bean对象的
什么是单例池,作用是什么 单例池也就是这个ConcurrentHashmap,用来保存单例的bean对象,如果是多例的bean对象,那么创建bean的步骤中,就不会有下面的放入map这一步
PostConstruct注解是如何工作的 我们现在的需求是,想让UserService中的User属性,能在UserService初始化前被赋值,且赋的值是从数据库中读取到的 实现原理,就是在初始化UserService对象的过程中,遍历UserService中所有带有PostConstruct注解的方法,然后通过反射的方式,来调用这些方法
Bean的初始化是如何做的 上面的给user属性赋值的需求,也可以通过实现innitianlalizBean接口来实现
Bean的实例化:通过无参构造方法得到一个对象 Bean的初始化:就是调用上面构造出来的对象中的某个方法(afterProperteirSet方法)
初始化后 最后,就把代理对象,放入单例池中,这样我们在使用时,直接用代理对象,就能用到用户定义的aop的逻辑
推断构造方法 Spring 在调用构造方法实例化对象时,会先判断调用哪个构造方法,在程序员没有显示写构造方法时,也默认调用无参构造方法,当程序员显示写有参构造方法时, 但没写无参构造方法时,无参构造方法就没有了 我们可以给构造方法加上面的注解,来明确的告诉spring 调用哪个构造方法 什么是推断构造方法中的先byType再byName
单例bean,是指在spring 容器中,同一个beanName只对应一个bean实例,不代表说,一个类只有一个实例,可能一个类有多个实例在容器中,分别叫不同的beanName 依赖注入时,先判断容器中是否已经有OrderService类型实例,如果没有则先创建再注入给UserService,如果有,则先通过byType的方式去匹配,同一个Type有多个实例,则再通过byName的方式匹配,如果再通过name的方式匹配时,匹配不到相同的name也会报错
spring aop是怎么工作的 注意到这里,代理对象从父类继承过来的orderService属性是没有值的,因为spring 只有针对普通对象的依赖注入逻辑,并没有针对代理对象的依赖注入逻辑 所以,如果这里直接用super.test 调用test方法,而test方法中依赖了还为空的orderService属性,所以肯定会报错 上面直接用super调用父类的test方法,是不行的 必须,在代理类中整一个target引用,把已经完成依赖注入的普通对象引用过来,然后调用普通对象的test方法 如果直接用super调用父类中的test方法
Spring 事务是怎么工作的 Spring 事务是通过aop来实现的, Configuration注解不加,事务是无法使用的 上面就往spring 容器中注入了一个事务管理器 Spring 事务,是通过spring 自己的事务管理器来建立连接,然后再这同一个连接中开始一个事务,在同一个事务中执行多条sql,这多条sql中有一条报错,因为在同一个事务中,所以才能执行回滚。 如果像上面这样,jdbcTemplate.execute时才建立连接,那多条sql就有多个事务,当然也就无法达到事务真正的目的了
Spring 事务失效的根本原因 Spring 事务,就是给加了Transactional注解的方法所在的对象,对这个对象生成一个代理对象 上面这种情况,UserService的代理对象所引用的UserService普通对象,在直接调用a方法, 当然a方法上的事务注解,不会生效 自己注入自己,也能使事务生效 总结: 思考某个方法上的Transactional注解,会不会生效,就看当前是普通对象还是代理对象在调用这个方法。只有调用代理对象的方法时,事务才能生效
Configuration注解的作用是什么 不加configuration注解时,事务好像就失效了一样, 所以,spring 的事务,多个线程中的sql就没办法公用同一个事务,就是因为拿不到同一个连接,因为连接是存在ThreadLocal当中的。 因为,spring 要考虑,一个线程中调用多个方法,不同方法调用的是不同的数据源,也就是为了支持多数据源,所以ThreadLocal的存储格式是上面这样。 此时,jdbcTemplate执行sql是,就需要拿到连接,拿事务管理器中存放的连接,怎么拿,就通过上面的ThreadLocal去自身的线程对象中拿, 首先会拿到一个Map,然后jdbcTemplate就以和自己绑定的dataSource为key去Map中拿连接。 JdbcTemplate需要一个dataSource,事务管理器也需要一个dataSource,站在java的角度,当没有加configuration注解时,上面就是两次方法调用,两次方法调用就会返回两个dataSource对象 因为,没加configuration注解,所以有两个dataSource,所以上面jdbcTemplate以自己的dataSource为kex去Map中拿连接,肯定拿不到事务管理器中的对应连接,所以它会自己另外创建一个连接,然后这个连接不是事务管理器管理的连接,autoCommit默认就不是false(此时,每天sql执行完,立马就会提交),从而就导致了spring事务的失效。 加了configuration注解后,AppConfig对象就变成了代理对象,代理对象中的代理对象逻辑是,先判断dateSource()是否已经被调用过,如果自己调用过,那么ioc容器中就有了一个数据源对象,那么另外的地方再调用dataSource()方法时,就不会再重新创建数据源对象,而是直接去容器中取出已经创建好的数据源对象返回, 上面这种情况,当项目中配置了多数据源,并且加了configuration注解,但是事务仍然会失效,因为事务管理器和jdbcTemplate引用的不是同一个数据源 @Bean注解和@Conponent注解 相同点:都可以给ioc容器注册bean对象 不同点:@Bean需要与@Configuration配合,保证bean对象的唯一性。 @Bean更灵活,比如第三方类库,如果需要注册到ioc容器中,就没办法使用Conponent 注解,只能使用@Bean注解
Spring 用三级缓存来解决循环依赖问题 本身加一个zhouyuMap,就能解决普通的循环依赖问题,如上图 但是,上面在有aop的情况下,就会有问题 有aop时,放入b对象a属性的是a的普通对象,但是,最终放入单例池的是a的代理对象 针对上面的问题,我们就需要解决方案:需要考虑,什么时候需要对a提前进行aop生成a的代理对象 当a出现循环依赖时,就可以考虑对a提前进行aop 二级缓存 为了防止提前AOP生成两个A的代理对象,所以要搞一个二级缓存,让A的代理对象,生成一次后就存起来 C在需要A对象时,就先去二级缓存中查了 二级缓存的目的:当创建b的过程中,发现a出现了循环依赖,要对a提前进行aop生成代理对象,为了保证后续创建c时同样发现a出现循环依赖,不再次重复生成a的代理对象 earlySingletonObjects专门用来存临时的没有经过完整生命周期的单例bean对象/单例bean代理对象,
创建a的普通对象后,往三级缓存中,放入一个lambda表达式 (此时,表达式还没有执行) 流程: 创建b时,先去一级缓存池中找a,没找到则再去二级缓存池中找a,如果还没找到,则从三级缓存中把lambda表达式中把a对应的表达式找出来,执行该表达式,如果a身上有aop那么该表达式执行后,就会生成a的代理对象,否则得到的就是a的普通对象。
不管怎样,得到对象后,还是会最后将生成的对象,放入二级缓存中。此时因为对象,还没有经过完整的bean生命周期,所以就放入二级缓存中。 总结: 真正打破循环的,就是第三级缓存 5.5还会把代理对象,从二级缓存中取出来,放入一级缓存单例池中 这里,是判断第五步还需不需要进行aop,如果第二步提前进行了aop,那么第五步就不需要再进行aop了 @Async注解为什么会导致循环依赖解决不了? 如果test方法上,既有aop拦截, 又有Async注解,那么就会各自生成一个代理对象,aop生成一个,async注解要生效也是通过生成代理对象,这是两个代理对象,流程走不下去就报错了 构造方法导致循环依赖报错 报错原因: 卡死在第1步,创建普通对象失败,因为构造a的普通对象时需要传入b,但是没有b,构造b时又需要传入a,所以就直接报错
Spring MVC
Springboot选择tomcat还是jetty 就看pom文件中,引入的是tomcat还是jetty(如果都引入了,就会报错) ConditionOnClass注解也就是表示,括号内所包含的所有类,都在classpath中出现,这个注解修饰的类,才生效 。这个生效的类中注入的bean,才会真正进入ioc容器中。
Spring事务怎么实现的 Transactional注解,为什么会失效 Spring 中后置处理器的作用 Spring aop怎么实现的
Spring的单例bean不等于单例模式 如何理解spring boot的starter机制
如何实现一个ioc
Ioc实现机制 简单工厂+反射技术 把创建对象,交给工厂来统一管理 上面是简单工厂 上面是通过反射,传入类全路径,通过简单工厂的方式,来返回对象 ioc和DI的区别 ioc是用来解决耦合的一种设计思想,它来集中管理所有对象,而DI是实现ioc中的重要一环,来注入对象。 松耦合和紧耦合 单一职责 接口分离原则,接口不动,实现换掉就可以了 依赖倒置 BeanFactory作用 BeanFactory也是容器,spring 容器管理着bean的生命周期 BeanDefinition 通过Class.forName得到Class对象,然后通过反射newInstance就可以得到真正的bean对象 BeanFactory和ApplicationContext上下文区别 共同点:都可以作为容器,都可以管理bean的生命周期 不同点:上下文不直接生产bean,而是通过门面模式,依赖beanFactory来getBean,上下文是更多的和用户打交道的, 上面,写错了是BeanFactory
被FactoryBean修饰的bean获取,就变成懒加载了,用到时才生成
Spring ioc的加载过程,重点 先找到invokeBeanFactoryPostProcessor方法(这个方法,就会把类,转成beanDefinition ),找到componentScan注解包路径,扫描类有没有component注解,有这个注解,就把当前这个类,注册为beanDefinition 然后就是生产bean,交给BeanFactory,通过简单工厂模式,调用getBean方法进行生产,
Spring 提供了哪些扩展点 什么是扩展点:spring 对外提供的很多接口或者说钩子方法,当我们实现了这些接口,它就会在特定的点,帮我们执行这些钩子方法,从而我们就可以来对底层进行扩展 也就是,整个bean加载的各个阶段,都有扩展点 1 bean的注册阶段 2 bean的生产阶段,9次 3 bean的初始化阶段,各种Aware接口 4 初始化阶段,各种生命周期的回调方法(也就是,初始化和销毁阶段的各种回调)
被spring 容器管理的,从ioc容器中获取的就是spring bean Java bean 就是自己new出来的 单例bean的好处 Spring 线程安全问题
自动装配的几种方式
生命周期的几种回调方式 Spring bean在加载过程中,有几种形态 解释bean的生命周期 Spring 如何避免在并发的情况下,获取到不完整的bean 因为加了双重检查锁 线程2走过一级缓存获取后,发现后续的流程被线程1锁住了,线程2就只能等,等线程1走完后,线程2去二三级缓存中找不到想要的对象,但这是线程2并不会直接开始自己动手创建,而是再次调用getSingleton方法,从一级缓存中获取对象,因为线程1走完流程后,已经把自己创建的对象放入了一级缓存,所以线程2这次就可以过去到对象了,就避免了一级创建 双重检查锁, 为什么不把锁直接加到一级缓存处,因为性能
描述beanDefinition加载过程 Bean工厂的后置处理器接口,就可以完成在所有beanDefinition注册完后的扩展 改成多例bean 如何在spring 所有bean创建完后做扩展 方式二,监听refresh 事件, spring在refresh方法执行完,也就是说所有bean创建完了,会发布一个refresh event事件 Ioc容器加载就是在refresh 方法中完成的
javaConfig是如何替代xml的 两个容器,都有共同点父类AbstractApplicationContext
重点: 在new spring上下文的过程中,在上下文的构造函数中,会注册很多支撑ioc容器整个加载过程的,一些内置的bean处理器 比如上面,解析@Configuration和解析@Autowired注解的处理器 ConfigurationClassPostprosessor实现BeanDefinitionRegistryPostProcessor的原因,就是为了让自己具有动态地注册BeanDefinition的能力,因为它需要解析@Bean等注解,然后把他们注册成为BeanDefinition Spring事务的传播行为是怎么实现的 上面的倒数第二行,在内嵌事务函数 最后,会判断内嵌事务的newTransaction属性是否为true,因为此时是融入的事务传播行为方式,所以此时内嵌事务函数,对应的代理对象,在最后就不会执行事务提交。 当带有@Transactional注解的函数A,调用带有@Transactional注解的函数B时,就相当于有两个代理对象,是A的代理对象,调用B的代理对象。 A的代理对象逻辑是创建连接,B的代理对象逻辑是,去ThreadLocal中发现有连接,就知道自己是一个内嵌事务,就去判断发现是融入的传播行为,则将newTransaction属性置为false,这样,B的代理对象逻辑,在最后检查newTransaction属性置为false,也B自己就不去提交事务了,而直接返回A中,A在代理逻辑的最后,统一的去提交事务。
Spring 如何管理mybatis的mapper接口的 因为,mybatis的mapper接口,是没有实现类的,只有接口,我们知道接口是无法实例化,也就无法直接被spring ioc容器进行管理的 通过上面,我们知道spring肯定是管理了UserMapper对应的bean的,不然是不可能通过Autowired注入进来的 上面,是普通类,被scanner扫描并进入spring管理过程的 可以看到,spring默认的ClasspathBeanDefinitiinScanner是不会把接口扫描并注册成BeanDefinition的,所以我们需要自己通过BeanDefinitionRegistryPostProcessor这个扩展接口,来把mybatis的UserMapper接口注册成为spring内的BeanDefinition。 又因为,spring默认的ClasspathBeanDefinitiinScanner在扫描.class文件并注册成BeanDefinition的的过程中,会把接口排除出去,所以我们需要自己写一个Scaner类继承ClasspathBeandefinitionScaner,并覆写它的扫描方法,从而不把接口排除在外 又因为,接口本身虽然已经扫描,并注册成为了BeanDefinition,但是它是接口,还是不能进行实例化的,所以需要将该BeanDefinition的beanClass属性,替换称为Jdk动态代理的实例
Spring mvc流程 视图解析器,就是两,字符串类型的视图名hello,返回hello.jsp这个视图View模版,然后最后用数据渲染这个视图模版,成为最后的响应内容
Spring boot整个启动过程,一共发布了9次事件,发布事件就是为了能让外部进行扩展,发布事件对内部来说就是实现解耦
Spring boot内嵌tomcat 启动原理
上面,就是通过ConditionOnClass……,来启用了tomcat(这个类是通过上面的上面那幅图中的类引过来的) TomcatServetWebServerFactory,这个工厂,就可以帮我们创建tomcat并且启动tomcat, 如上,这里就创建了tomcat,上面这个方法,是在spring boot启动的时候调用的 在自动配置类中启用了内嵌tomcat,并通过上面的@Bean注解,给ioc容器中注册了一个tomcat服务的工厂(tomcat server的工厂) 上面,表示spring boot应用开始启动时, 会先创建spring boot上下文容器, 接着调用spring boot容器类的refresh方法时,来加载真正的spring 容器 也就是真正会调用到AbstractApplicationContext的refresh方法, 然后通过533行的方法,会解析@bean等等注解,加载所有的自动配置类,这其中自然就包括web服务器的自动配置类, 因为系统中有Tomcat 相关的类,所以就启用tomcat 相关的自动配置,这样也就把TomcatServetWebServerFactory这个bean对应的BeanDefinition注册到了ioc容器中 紧接着 执行545行 会获取web server的工厂,也就是 获取到了前面注册进来的TomcatServetWebServerFactory, 执行到上面这个getWebServer方法 ,就会创建tomcat并启动tomcat,并让tomcat阻塞住,等待客户端用户发送请求 Spring mvc怎么集成进来的
Spring boot里面的spring mvc的核心类,DispatcherServlet也是通过自动配置类注入进来的,不是自己以前那样,在web.xml中自己配置的
Spring boot配置文件读取原理和读取顺序 Spring 启动时,会发布上面的事件 这个事件,有7个地方进行了监听,其中第一个就是配置文件监听器,这个监听器就会去加载配置文件,按照上面图中描述的读取顺序读取。
Spring boot默认使用cglib代理,上面就是切换回老的有接口就先使用jdk代理的方式
|
今日新闻 |
点击排行 |
|
推荐新闻 |
图片新闻 |
|
专题文章 |
CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭 |