Spring框架中都用到了那些设计模式
-
工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例。
-
单例模式:Bean默认为单例模式。
-
代理模式:SpringAOP功能用到了JDK的动态代理和CGLIB字节码生成技术。
-
模板方法:用来解决代码重复的问题,比如:RestTemplate, JmsTemplate, JpaTemplate、refresh。
-
观察者模式:定义对象键是一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到被制动更新,如Spring中listener的实现–ApplicationListener。
Spring IOC
==========
IOC是什么?
IOC(Inversion of Control)即控制反转,简单说就是把原来代码需要实现的对象创建、依赖反转给容器来帮忙实现,需要创建一个容器并且需要一种描述让容器指导要创建对象之间的关系,在Spring中管理对象及其依赖关系是通过Spring的IOC容器实现的。
IOC的实现方式有依赖注入和依赖查找是,由于依赖查找用的很少,因此IOC也叫做依赖注入。依赖注入指对象被动地接受依赖类而不用自己主动去找,对象不是从容器中查找它依赖的类,而是在容器实例化对象时,主动将它所依赖的类注入给它。
依赖注入更加准确地描述了IOC的设计理念,所谓依赖注入(Dependency Injection),即组件之间的依赖关系有容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用程序中的各个关联的组件之中,组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。
举例:一个Car类需要一个Engine的对象,那么一般需要手动new一个Engine,利用IOC就只需要定义一个私有的Engine类型的成员变量,容器会在运行时创建一个Engine的实例对象并将引用自动注入给成员变量。
控制反转是:关于一个对象如何获取他所依赖的对象的引用,这个责任的反转。
IOC容器初始化的过程
基于xml的容器初始化
当创建一个
ClassPathXmlApplicationContext时,构造方法做了两件事
-
调用父容器的构造方法为容器设置好Bean资源资源加载器
-
调用父类的setConfigLocations方法设置Bean信息的定位路径
ClassPathXmlApplicationContext通过调用父类AbstractApplicationContext的refresh方法启动整个IOC容器对Bean定义的载入过程,refresh是一个模板方法,规定了IOC容器的启动流程,在创建IOC容器前如果已有容器存在,需要把已有的容器销毁,保证在refresh方法后使用的是新创建的IOC容器
容器创建后通过loadBeanDefinitions方法加载Bean配置资源,该方法做两件事:
调用资源加载器的方法获取需要加载的资源;
真正执行加载功能,由子类XmlBeanDefinitionReader实现。加载资源时:
-
首先解析配置文件路径,读取配置文件内容,然后通过xml解析器将Bean配置信息转换成文档对象;
-
之后按照Spring Bean定义规则对文档对象进行解析。
Spring IOC容器中注解解析的Bean信息放在一个HashMap集合中,key是字符串,值是BeanDefinition,注册过程中需要synchronized保证线程安全,当配置文件的Bean被解析且被注册到IOC容器中后,初始化就算真正完成了,Bean 定义信息已经可以使用且可被检索。Spring IoC 容器的作用就是对这些注册的 Bean 定义信息进行处理和维护,注册的 Bean 定义信息是控制反转和依赖注入的基础。
测试类
基于注解的容器初始化
分为两种:
-
直接将注解Bean注解到容器中,可以在初始化容器时注册,也可以在容器创建之后手动注册,然后刷新容器使其对注册的注解Bean进行处理。
-
通过扫描指定包及其子包的所有类处理,在初始化注解容器时指定要自动扫描的路径。
或者
在xml中开启注解装配
context:annotation-config</context:annotation-config>
然后使用
ClassPathXmlApplicationContext加载xml配置文件
依赖注入的实现方法
=========
构造方法注入
IOC Service Provider会检查被注入对象的构造方法取得它所需要的依赖对象列表,进而为其注入相应的对象。这种方法的优点是在对象构造完成后就处于就绪状态,可以马上使用。缺点是当依赖对象较多时,构造方法的参数列表会比较长,构造方法无法被继承,无法设置默认值。对于非必需的依赖处理可能需要引入多个构造方法,参数数量的变动可能会造成维护的困难。
setter方法注入
当前对象要只需要为其依赖对象对应的属性添加setter方法,就可以通过setter方法将依赖对象注入中。setter 方法注入在描述性上要比构造方法注入强,并且可以被继承,允许设置默认值。缺点是无法在对象构造完成后马上进入就绪状态。
构造器依赖注入和Setter方法注入的区别
两种依赖方式都可以使用,构造器注入和Setter方法注入。最好的解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖。
接口注入
必须实现某个接口,接口提供方法来为其注入依赖对象。使用少,因为它强制要求被注入对象实现不必要接口,侵入性强,已在Spring4中废除。
依赖注入的过程?
getBean方法获取Bean实例,该方法调用doGetBean,doGetBean真正实现从IOC容器获取Bean的功能,也是触发依赖注入的地方。
具体创建Bean对象的过程是由ObjectFactory的createBean完成的,该方法主要通过createBeanInstance方法生成Bean包含的Java对象实例和populateBean方法对Bean属性的依赖注入进行处理。
在populateBean方法中,注入过程主要分为两种情况:
1.属性值类型不需要强制转换时,不需要强制类型转换时,不需要解析属性值,直接进行依赖注入。
2.属性类型需要强制转换时。
-
首先解析属性值;
-
然后对解析后的属性值进行依赖注入。
-
依赖注入的过程就是将Bean对象实例设置到它所依赖的Bean对象的属性上。
-
真正的依赖注入是通过setPropertyValues方法实现的,该方法使用了委派模式。
BeanWrapperImpl类负责对完成初始化的Bean对象进行依赖注入
-
对于非集合类型属性,使用JDK反射,通过属性的setter方法为属性设置注入后的值;
-
对于集合类型的属性,将属性值解析为目标类型的集合直接赋值给属性。
当容器对Bean的定位、载入、解析和依赖注入完成后就不再需要手动创建对象,IOC容器会自动为我们创建对象并且依赖注入。
Bean的生命周期
=========
-
Spring容器读取XML配置文件中bean定义并实例化Bean;
-
Spring根据Bean的定义设置属性值;
-
如果该Bean实现了BeanNameAware接口,Spring将Bean的id传递给setBeanName()方法;
-
如果该Bean实现了BeanFactoryAware接口,Spring将BeanFactory传递给setBeanFactory()方法;
-
如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;
-
如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessBeforeInitialization()方法;
-
如果bean实现了InitializingBean接口,Spring将调用它们的after-PropertiesSet()方法。类似地,如果bean使用initmethod声明了初始化方法,该方法也会被调用。
此时Bean就已经准备就绪了,可以被应用程序使用了,他们一直驻留在容器中,直到容器被销毁。
- 果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用。
自己定制初始化和注销方法
XML 方式通过配置 bean 标签中的 init-Method 和 destory-Method 指定自定义初始化和销毁方法。
注解方式通过 @PreConstruct 和 @PostConstruct 注解指定自定义初始化和销毁方法。
Bean的作用范围
通过scope属性指定bean的作用范围,包括:
-
singleton:单例模式,是默认作用域,不管收到多少Bean请求每个容器中只有一个唯一的Bean实例
-
prototype:原型模式,和singleton相反,每次Bean请求都会创建一个新的实例
-
request:每次HTTP请求都会创建一个新的Bean并把它放到request域中,在请求完成之后Bean会失效并被垃圾收集器回收
session:和request类似,确保每个session中有一个Bean实例,session过期后Bean会随之失效
- global session:当应用部署在Portlet容器时,如果想让Portlet公用全局存储变量,那么该变量需要存储在global session中
Bean线程安全性
Spring的单例Bean不是线程安全的
Spring中的Bean默认是单例模式,Spring框架中没有对单例Bean进行多线程的封装处理。
实际上大部分时候 spring bean 无状态的(比如 dao 类),所有某种程度上来说 bean 也是安全的,但如果 bean 有状态的话(比如 view model 对象),那就要开发者自己去保证线程安全了,最简单的就是改变 bean 的作用域,把“singleton”变更为“prototype”,这样请求 bean 相当于 new Bean()了,所以就可以保证线程安全了。
-
有状态:有数据存储功能
-
无状态:不会保存数据
Spring如何处理线程并发问题
只有无状态的Bean才会在多线程下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。
ThreadLocal为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据访问的冲突,因为每一个线程都拥有自己的变量副本,从而就没有必要对该变量进行同步,ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
扩展问题
====
创建Bean的方式
xml
-
默认无参构造方法,只需要指明 bean 标签中的 id 和 class 属性,如果没有无参构造方***报错。
-
静态工厂方法,通过 bean 标签中的 class 属性指明静态工厂,factory-method 属性指明静态工厂方法。
-
实例工厂方法,通过 ean 标签中的 factory-bean 属性指明实例工厂,factory-method 属性指明实例工厂方法。
注解
@Component 把当前类对象存入 Spring 容器中,相当于在 xml 中配置一个 bean 标签。value 属性指定 bean 的 id,默认使用当前类的首字母小写的类名。
@Controller,@Service,@Repository 三个注解都是 @Component 的衍生注解,作用及属性都是一模一样的。只是提供了更加明确语义,@Controller 用于表现层,@Service用于业务层,@Repository用于持久层。如果注解中有且只有一个 value 属性要赋值时可以省略 value。
如果想将第三方的类变成组件又没有源代码,也就没办法使用 @Component 进行自动配置,这种时候就要使用 @Bean 注解。被 @Bean 注解的方法返回值是一个对象,将会实例化,配置和初始化一个新对象并返回,这个对象由 Spring 的 IoC 容器管理。
name 属性用于给当前 @Bean 注解方法创建的对象指定一个名称,即 bean 的 id。当使用注解配置方法时,如果方法有参数,Spring 会去容器查找是否有可用 bean对象,查找方式和 @Autowired 一样。
BeanFactory、FactoryBean和ApplicationContext的区别?
==============================================
BeanFactory(”低级容器")
BeanFactory是一个Bean工厂,使用简单工厂模式,是Spring IOC容器的顶级接口,可以理解为含有Bean集合的工厂,作用是管理Bean,包括实例化、定位、配置对象及建立这些对象间的依赖。BeanFactory 实例化后并不会自动实例化 Bean,只有当 Bean 被使用时才实例化与装配依赖关系,属于延迟加载,适合多例模式。
FactoryBean
FactoryBean是一个工厂Bean,使用了工厂方法模式,作用是生产其他Bean实例,可以通过实现该接口,提供一个工厂方法来自定义实例化Bean的逻辑,FactoryBean接口有BeanFactory中的配置实现,,这些对象本身就是用于创建对象的工厂,如果一个 Bean 实现了这个接口,那么它就是创建对象的工厂 Bean,而不是 Bean 实例本身。
ApplicationContext(”高级容器“)
ApplicationContext是BeanFactory的子接口,扩展了BeanFactory的功能,提供了支持国际化的文本消息,统一的资源文件读取方式,事件传播以及应用层的特别配置等。该接口定义了一个refresh方法,用于刷新整个容器,即重新加载/刷新所有的bean。容器会在初始化时对配置的 Bean 进行预实例化,Bean 的依赖注入在容器初始化时就已经完成,属于立即加载,适合单例模式,一般推荐使用。
实现类:
-
ClassPathApplicationContext:只能读取路径下的配置文件(推荐使用)。
-
FileSystemXmlApplicationContext:可以读取任意硬盘路径的配置文件,你可以点击根目录下的文件的属性查看他在本地硬盘的位置。
-
AnnotationConfigApplicationContext:读取注解配置类。
Spring AOP
==========
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
最后
98664876)]
[外链图片转存中…(img-WLTDgoBw-1711698664877)]
[外链图片转存中…(img-FdeN5Yhc-1711698664877)]
[外链图片转存中…(img-cSMw6ET2-1711698664878)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-w8llIhPg-1711698664878)]
最后
[外链图片转存中…(img-jZKshp9z-1711698664879)]
[外链图片转存中…(img-CMkObMsy-1711698664879)]