个人博客网:https://wushaopei.github.io/ (你想要这里多有)
一、Spring概念与IOC
JavaWeb发展史:
第一阶段:JavaBean + Servlet +JSP逐步发展
第二阶段:面对EJB重量级框架带来的种种麻烦
第三阶段:SpringMVC/Struts + Spring + Hibernate/myBatis
第四阶段:享受SpringBoot "约定大于配置"的种种乐趣
第五阶段:以Dubbo为代表的SOA微服务架构体系
第六阶段:SpringCloud微服务架构技术生态圈
1、Spring概述
简单概述:Spring框架是一个开放源代码的J2EE应用程序框架,由Rod Johnson发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。 Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用,并且可以与 Swing等桌面应用程序AP组合。
2、IoC概念介绍
- IoC: Inversion of Control,控制反转、依赖注入
注意:不只是Spring的ioc,其他的框架也具有相同的IOC思想及应用
【1】控制什么?
控制对象的创建及销毁(生命周期)
【2】反转什么?
将对象的控制权交给IoC容器
【3】为什么要使用IOC ?
模拟场景解读:回家
①我 -----> 走路 ----> 回家
②我------> 坐车 ------->回家
①、②之间的区别在于,我自己走路回家,从出发到到达目的地都是自己在执行;而②中是将自己要回家的任务交由车辆执行,自己只需要依赖于车辆(坐在车上)就可以,等车辆到达目的地,下车就可以。
由此引申出,默认情况下,我们调用一个实例时,大部分情况由自己手动创建,然后才能去达到目的;使用IOC后,只需要掌握主要的方向,将实际的操作交由容器去处理,在需要的时候调用容器返回的结果,如@Autowire注解的实例(这是基于多态的实现),便可以达到调用bean实例的目的;
不过,IOC真正的意义在于可以降低代码的耦合度;当目标人物只有一辆车时,使用IOC进行依赖注入效果并不明显,但是,如果目标人物有多辆车呢?
按照传统的做法,每一辆车都创建一个实例,奥迪 是 new Audi(),别克是 new Buick();......
这就显得很繁琐,代码也会很冗余,每一次替换车辆都会造成代码大幅度的改动,增加了工作量。
而IOC则能很好的解决这个问题,由前文中所描述的,IOC的底层依赖注入使用了类似 多态的结构,这也就意味着可以通过接口的实现或类的继承的方式来通过进行动态调用 车辆的实例,而不需要修改整行的实例对象代码。
例如:奥迪和别克都实现了车的接口,那么就可以通过多态的形式,将车类的共性方法或属性放在父类中,独有的各自声明,当需要调用时,由容器返回父类的实例,再通过泛型的方式声明当前调用的是哪一种车?然后返回实例。
注意:
多态中,返回的 "audi" 虽然是父类变量的引用,但是指向的是子类对象。
可以用它来调子类对象的方法或属性
泛型方式:
这便是底层的一部分操作的解读
【4】手动创建实例的 问题——高耦合性
- ①张三所有的行为都需要自己主动创建并销毁一辆车
- ②更换车辆的代价是巨大的
【5】改进原始的 购车计划:
将方法域中的 Audi 实例提到 成员属性所在的域中,使用 Buick 替代 Audi,这样就不需要修改 方法内的代码
进一步分析:
【1】张三需要的是一辆奥迪?一辆别克?或者就是一辆车?
张三只是需要一辆车
【2】张三会智造(创建)车么?
车辆不应该由张三来创建
那么,重点来了:Car 的 创建者是谁呢?
那就是IOC 容器,车本身不会自己创建自己,而张三这个使用者又不会创建,所以实际的 Bean 的创建由 容器本身来创建
在Audi和 Buick 实现Car 的接口后,在 ZhangSan中声明 Car 的实例,在这里,Car 的实例不是有张三所创建的,将其放到构造方法中,
注意: 从代码中可知,车辆的创建和销毁已经不是 ZhangSan 所 执行的,那么也就意味着ZhangSan 失去了对对象的控制权;
那么,对象的可控制权是交给谁呢?
答案就是 IOC 容器;而这也是 IOC 中的 “控制反转”中“反转” 的真正体现
3、实现一个自己的IoC
【1】实现一个自己的 IOC , 场景设计
【2】简化 IOC 业务逻辑的 三个约定
- 所有Bean的生命周期交由IoC容器管理
- 所有被依赖的Bean通过构造方法执行注入
- 被依赖的Bean需要优先创建
第一步:声明一个 Humen 人类接口
第二步 : 创建一个 HumenWithCar 类 ,关联人与车;将该类声明为 抽象类,类中的 Car 实例通过构造方法获取
第三步:创建 ZhangSan ,继承 HumenWithCar 类,并重写 hoHome 方法
第四步:创建LiSi,继承HumenWithCar类,并重写 hoHome 方法
第五步:创建 IOC 容器 IoCContainer 类,声明一个Map 类型的 beans
并创建 GET/SET 两个方法用于对Bean实例进行赋值与获取值
getBean() 用于根据beanId 获取实例Bean 并 返回 Bean:
setBean()用于委托ioc 容器创建一个bean,具体的创建Bean的过程是通过反射实现的
第六步:测试类:
在测试类中,创建 自定义 IOC 容器 的实例,
使用@Before 管理依赖关系, 使用 自定义IOC 的 实例对象 调用setBean ()传入 类的class 和 要调用的构造器的 beanId
总结:
1、依赖关系使用 @Before进行管理
2、每个类各自的业务逻辑不同,不再受实例化的限制而要修改实现业务的代码
3、依赖 的关系可以在 @Before 中的构造参数中修改即可,如要实例化的 bean 、bean的id 、参数id 等,降低了代码的耦合度
4、Spring IoC入门
使用Spring提供的IOC 容器:
【1】创建Bean类
【2】引入Spring 的依赖, 包括 spring-core、spring-context
【3】创建 spring.xml 配置文件
【4】创建test测试类,通过读取配置文件,从服务上下午获取 bean 对象
总结:
二、Bean的实例化
使用Spring 实例化 Bean的4中方式:
- 通过构造方法实例化Bean;
- 通过静态方法示例化Bean;
- 通过实例方法实例化Bean;
- Bean的别名。
1、通过构造方法实例化Bean;
【1】创建Bean1
【2】创建对应的Bean1配置:
【3】测试:
2、通过静态方法示例化Bean
【1】创建Bean2
【2】创建Bean2的静态工厂类,并创建对应的getBean2()方法去实例化一个Bean2
创建与Bean2 对应的 <bean> 配置
test测试创建 Bean2 的实例,由静态工厂方法创建实例并返回
总结: 由静态工厂方法创建 bean 实例的方法有两种,
第一种,直接使用 Bean2Factory.getBean2()获取 Bean2的实例;
第二种,配置spring.xml 中的 <bean> ,通过读取配置上下文的方式生成 Bean2的实例
3、通过实例方法实例化Bean;
【1】创建Bean3和Bean3Factory:
【2】Bean3的配置注入管理:
【3】使用Bean3Factory的实例化对象,调用方法来创建Bean3的实例化:
4、Bean的别名
通过Bean的别名创建对象实例有多种方法:
第一种:实例化Bean 对象时 , 别名的使用:
配置<bean>文件 ,name = "bean1_1,bean1_2", id = "bean1"
由测试结果可知,使用别名创建 Bean 实例的时候,其实创建的是同一个实例对象的Bean ,指向同一个地址值:
三、注入Bean的方式:
1、通过构造方法注入Bean
【1】创建两个 类 ,分别为 Bean 和 AnotherBean 类,在Bean 类 中声明两个变量 , 分别为 AnotherBean 和 string . 并创建相应的构造方法
【2】配置 Bean 类 和 AnotherBean 的实例化配置文件,
在Bean 中声明 <constructor-arg> 用于映射Bean实例中的构造方法,
index 代表是当前构造器中的第几个变量,从0开始;
name 代表当前变量的变量名;
type 代表当前变量的类型;
value 代表当前变量的值或者要赋予什么值,如 value = "aaa" ,将 string 赋值为 string ;
ref 代表引用对象,可通过 其他的 <Bean>的id 做为被引用的值
【3】@Test测试类,获取Bean对象实例
2、通过set方法注入Bean
【1】在Bean类中创建 类型为 AnotherBean 的变量 anotherBean ;以及 类型为String 的 变量string1;并创建对应的SET/GET 方法
【2】启动 Test测试类,通过打印的结果可以看到,当Bean被实例化时,所配置的 bean1 和anthorBean1 的值也被打印出来 了
3、集合类Bean的注入
【1】分别创建包括: List 、Map、Set、Properties 的四中属性的变量或对象,并声明相应的 SET/GET 方法
【2】配置spring.xml 配置文件配置,使用 <property/> 来配置 List、Map、Set、 Properties 的bean实例
【3】@Test中测试执行 Bean 的实例化
4、null值注入
【1】创建 AnotherBean 类型的变量 anotherBena2 ,并声明 GET/SET 方法
【2】配置 spring.xml 的配置数据
【3】Test测试
5、注入时创建内部Bean
使用 <property>中包含<bean> 标签,由<bean>标签返回 bean实例的方式实现内部包含 bean 对象
四、Bean作用域
1、singleton、prototype
Singleton 作用域:当 Bean 实例被使用 singleton 进行单例设置后,生成的Bean 实例无论被调用多少次,都是同一个Bean实例,指向同一个内存地址
Prototype 作用域:每次向Spring上下文请求Bean 都会 new 一个 新的实例
【1】创建 Bean1 和 Bean2 ,在Bean1 中创建Bean2 类型的对象变量,并声明SET/GET 方法
【2】配置spring.xml 配置文件,声明Bean2的Scope 为 singleton ,并引入到 Bean1 中
【3】@Test中进行测试,由结果可知,当Bean2被Singleton 模式化后,生成的 Bean2实例指向的是同一个地址;又由于Bean1 默认情况下是 singleton 的模式,所以Bean1 的多个实例对象指向的是同一个地址值
单例模式: 每次想Spring 上下文请求一个实例时,Spring 都会给你返回同一个实例。
在Spring的整个生命周期中,只存在一个实例
注意:当Bean2 被声明为 singleton ,而 Bean1被声明为 Prototype 时, Bean2 在Spring 上下文中只实例化一次,而Bean1 属于多例,此模式下默认返回的是不同的Bean2 ,返回的Bean2 的实例地址是不同的;而由于Bean1 是 Prototype 模式的,Spring进行了对此的实例化,所以 bean1_1 == bean1_2 的比较结果是false,意味着每一次的Bean1 都进行了新的实例化
【4】当Bean2 为 Prototype 时,而Bean1为singleton时,Spring只对Bean1做了一次实例化,故只有一个地址,而由于Bean1 需要一个Bean2 的实例,所以Spring需要对Bean2 进行实例化,但是由于Bean1只实例化一次,所以Propotype模式下的Bean2 实际上只被Spring 进行了一次实例化
2、web相关作用域
【1】创建一个 WebServlet 工程,使用Maven管理
【2】配置webServlet 的web.xml 前端控制器
【3】创建 ApplicationController 、RequestController、 SessionController三个类
【4】将每个Controller的 bean 实例交由 spring容器来管理,在spring.xml 中添加相应配置
Request作用域实例:每次刷新都会 生成新的实例
Session作用域实例:每次重新打开浏览器才会生成新的实例,同一个浏览器如果没有关闭页面,每次刷新都是相同的实例。
Application作用域实例:只要浏览器的页面不关闭,serveltContext上下文就不会变更,实例不变
【6】使用tomcat 作为容器来配置 Controller请求,可知:
- RequestScope 中,每一次发送请求,spring都会创建一个新的实例;
- RequestScope 中,每一次发送请求,spring都只有一个相同的实例;
request:每个request请求都会创建一个单独的实例
session:每个session都会创建一个单独的实例
application: 每个servletContext都会创建一个单独的实例
3、自定义作用域
自定义双例作用域:
【1】创建MyScope.java类,并实现Scope.java,并重写方法
【2】创建两个变量:
【3】完善get emove方法:
【4】配置Bean注入到spring中:
【5】测试:
结果:
五、Bean的懒加载与生命周期
1、Bean的懒加载
Spring懒加载:Spring容器会在创建容器时提前初始化Singleton作用域的bean。但是如果Bean被标注了lazy-init="true",则该Bean只有在其被需要的时候才会被初始化。
Bean的懒加载只有在标注为 Singleton 作用域时才有效
【1】关于 Bean 的配置实现方法
对单个bean配置懒加载时,只需要将lazy-init属性配置为true,
在beans标签加上default-lazy-init属性配置为true,则该文件下的所有bean都是懒加载
【2】Bean的懒加载的 适用场景
如果某个Bean在程序整个运行周期都可能不会被使用,那么可以考虑设定该Bean为懒加载。
优点:尽可能的节省资源
缺点:可能会导致某个操作响应时间增加
可以设置单个Bean的加载方式为lazy,也可以在配置文件头部的Beans标签中,统一设置default-lazy-init,这样它包含的所有Bean都是懒加载方式
2、Bean初始化及销毁逻辑处理
【1】bean初始化的方法
xml里配置init-method
如果所有的Bean对象都有一样的初始化方法,可以在beans中定义defult-init-method
bean类继承InitializingBean接口
【2】Bean 的声明周期 之 Bean 的销毁:
Bean的销毁,如果需要在Bean销毁之前进行一些逻辑,有两种方法:
1、使用destroy-method
2、让 Bean实现DisposableBean接口
【3】实现一次Bean的声明周期:
①创建一个Bean类并创建 onInit 和 onDestroy 两个方法,分别表示 Bean的初始化和销毁,并将 Bean 的实例通过spring.xml注入到容器中
②执行 Bean的生命周期 测试,
③使用 AbstractApplicationContext 可以调用 Spring 上下文的销毁方法
④使用 init-method 定义实例化后的逻辑
⑤使用 destory-method 定义实例销毁前的逻辑
⑥为所有的Bean 设定默认的初始化方法和销毁方法
3、Bean属性继承
【1】一个父bean的属性,被两个子bean继承
【2】场景2,也是继承,但是父bean中的属性不能被继承,在子bean中需要分开单独写属性值
【3】Bean 属性 继承 所需要 的 Spring 配置
【4】Bean 属性 继承 实例:
第一步:创建 ParentClass 类,并创建 attribute1、attribute2、attribute3 三个变量
第二步:创建 Class1 类,并创建 attribute4、attribute5 两个变量
第四步: 在 spring.xml 配置 Bean 到容器中,并将 ParentClass 和 Class1、Class2 关联起来
第五步: @Test 单元测试结果
代码分析:
继承机制下的的Bean属性继承场景,适用 abstract =“true" 告诉 spring 这个 bean 是不需要实例化的,再在 Class1 、Class2 中进行改造,让它们继承指向parentClass 的bean
六、注解的使用
1、注解的基本使用介绍
SpringIOC 注解的使用,要比使用配置便捷的多。
【1】对比一下通过配置文件来配置的方式:
上图是配置 spring.xml 装配一个 bean 所需要的配置,相对比较繁琐。
为了简化代码和流程,加快开发的效率,使用约定大于配置的方式。
【2】@Bean 和 @Conguration 注解的使用:
第一步:创建一个Bean1类和一个MyConfiguration 类,在MyConfiguration 类中创建 bean1 方法,返回值为 Bean1 的匿名对象;并在MyConfiguration 上添加 @Configuration 注解,和在bean1 方法上添加 @Bean 注解;
@Configuration标签所标定的class会被作为AnnotationConfigApplicationContext()的参数,起到传入一个xml文件给ApplicationContext类似的效果,使spring在创建上下文时,就将此类里面的有@Bean标签的方法都执行一遍,并产生相应的Bean,后面,用getBean获取就可以了。
第二步:使用 AnnotationConfigurationApplicationContext(MyConfiguration.class)创建一个Spring 上下文的对象,并使用context.getBean(“bean1”,Bean1.class);返回 Bean1 的实例
说明:由以上 Spring 上下文的生成可以知道, 被添加 @Configuration 的类可以被AnnotationConfigurationApplicationContext()所扫描到并添加到Spring中从而创建一个Spring的上下文;
而 @Bean 则类似于 spring.xml 中的 <bean/> 的作用,可以将Bean1 装配到容器中,从而创建一个 Bean1的实例,即 bean1。 而被添加@Bean 注解的方法名就是 类似于<bean/> 中的 id=“bean1”了。
【3】简化springioc的注解操作逻辑
第一步:使用注解: @ComponentScan(value="类的上一级包路径名")
@ComponentScan(value="xxxx.xxx")注解会告诉spring容器去扫描value指定的包结构下的所有class ,当有一个class被标注了 @Component时,该类就会被交由Spring来管理,并且由Spring来进行实例化
第二步:使用注解: @Component,标注到要被实例化的类上,如Bean1
@component告知这个类时需要交由spring来管理的,并可执行该bean的id。且还有另外三个注解和component类似,但是所在层次不同。
Controller,Service,Repository
@Controller : 被标注在Controller层
@Service : 被标注在Service层
@Repository : 被标注在Dao层
@component : 通用性注解
另一种简化注解的操作:
在spring.xml配置文件添加如下:
【4】Bean别名
给Bean取别名,但是在@Component时候就不可以,因为它是String类型,而不是String数组类型
小结:
- 通过注解减少配置 @configuration 相当于spring.xml整个xml的配置 @bean 相当于bean标签 另外可以让spring自动扫描注解,配置@componentScan,效果同component-scan标签
- bean的别名配置 @bean的value可以配一个数组
2、通过注解注入Bean
通过注解注入Bean 的 技术体系
Spring通过注解注入Bean
@Autowired:通过该注解实现构造方法注入和set方法注入,可以标识在有参构造方法上、set方法上、属性上。
【1】通过方法注入Bean
①通过有参构造方法注入
步骤1:创建扫描配置类,并添加注解@Configuration、@ComponentScan(value=“路径”)
步骤2:创建要实例化的Bean,并提供有参的构造方法,并在构造方法上添加注解@Autowired在类上添加@Component。
测试代码:
结果:
MyBean [anotherBean=springzhuru.AnotherBean@27b47740]
②通过set方法注入
步骤1:创建扫描配置类,并添加注解@Configuration、@ComponentScan(value=“路径”)
步骤2:创建要实例化的Bean,并提供set方法,并在set方法上添加注解@Autowired在类上添加@Component。
结果:anotherBean和anotherBean1相等,@Component是默认单例模式,同一spring上下文中只会创建一个AnotherBean的对象。
MyBean [anotherBean=springzhuru.AnotherBean@3c8587f, anotherBean1=springzhuru.AnotherBean@3c8587f]
【2】集合类型Bean的注入
①List集合类型Bean的注入
步骤1:创建注入Bean的类(包括创建集合类型的属性,基本类型的作为Bean的属性),并提供set方法,添加@Resource注解。
步骤2:扫描配置类,提供List<String>类型的实例的方法,并添加@Bean注解,告知spring由spring管理的方法。
测试:
通过注解注入List的第二种方式:在扫描配置类中添加几个返回类型为字符串类型的方法,返回的字符串都会被注入到Bean的集合属性中。
测试:
MyBean [anotherBean=springzhuru.AnotherBean@7ea7476f, anotherBean1=springzhuru.AnotherBean@7ea7476f, anotherBean2=springzhuru.AnotherBean@7ea7476f, stringList=[222, 111]]
222
111
List注入方式:如果一个Bean有一个List类型的属性需要注入,spring会到上下文中(扫描注解类)查找所有该List中定义泛型的所有实例(带有@Bean),然后将所有实例注入到List里面。
@Qualifier("stringList")指定Id,而且在集合属性的set方法上的@Qualifier(“stringList”)指定Id。
拓展:@Order(数值),来控制实例化Bean的顺序,小的先注入。前提:Spring4.2版本以后该注解才起作用,可以通过它实现注入集合中数据的顺序。
②Map注入
步骤1:创建Map类型的集合,并提供set方法
步骤2:扫描配置文件中提供返回map集合的方法。
测试:
Map注入的第二种方式:同List相同,创建多个方法返回Integer类型参数。
扫描配置类代码:
结果:还可以通过给@Bean(name="名称")来给实例取名。
springzhuru.MyBean@11de0733
integerMap1=444
integerMap2=555
【4】String、Integer等类型直接赋值:
步骤1:创建简单数据类型的变量,并提供set方法,并在set方法上添加@value(“值”)注解。
测试:
结果:MyBean [string=2222]
【5】SpringIoC容器内置接口实例注入:
- 可直接将ApplicationContext注入进来,也可以注入BeanFactory、Environment、ResourceLoader、ApplicationEventPublisher、MessageSource及其实现类!