一、什么是软件框架
- 它符合某种业界的规范和标准,springmvc 框架,严格按照 mvc 设计模式以及 javaWeb 的技术标准。
- 框架也是软件,它在项目的开发和运行、部署等环节都会起到作用。也可以理解为一个软件平台,使用框架相当于使用它的基础作用。所以,框架也可以按照层次分为基础功能(核心功能),可复用的组件,面向应用的功能。
- 现代的软件开发有充分的理论依据,它散发在开发的各个层面和角落,因此只要开发软件就离不开框架。
- 使用框架可以让软件的开发变得标准化,可以简化代码的设计,可以最大限度的对功能进行解耦,符合规范的项目对多人合作以及后期的维护都会有好处。
二、什么是 spring 框架
- 它是 java 技术领域开源的,具有首要地位的框架。
- 它的目标就是为了简化 java 企业级的开发。
- 它本身具有分层的结构,基础是 IOC 和 AOP。从组件上来看,又包括了 jdbc,web,date,security 等。从应用的角度看,它可以令 java 项目与多种第三方的中间件或工具结合在一起很好的工作(功能的整合)。
- spring 是一个大家族,提供了非常多的功能,这些与任何 java 项目都有着千丝万缕的关系。
三、Spring Framework(spring 框架)
- 学习的核心,第一是 IOC,也就是 DI,spring 框架整合其他的框架和中间件,底层的核心技术就是 DI。第二是 AOP,需要利用该技术,用更简单的方式去完成日志的记录,数据库的事务管理等任务。
四、创建 spring 项目
-
spring 框架可以用在 java 的任何项目中,jar,war,pom 等类型的项目都可以使用 spring。
-
创建项目的详细步骤
-
创建一个 maven 项目,在 pom 中引入 spring-context 的依赖。
-
再创建一个 module,继承以上的项目。把上面创建的项目的 package 设为 pom 类型,首次创建的项目就是一个聚合项目。
-
new module,让它继承 pom 项目,本 module 的 pom 文件中出现的内容。
-
<parent> <artifactId>springDemo</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent>
-
-
当前项目为 jar
-
父项目中的依赖对子项目是有效的。
-
创建 spring 容器的配置文件
-
可利用 spring config 的菜单项来创建。
-
<?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"> <bean id="hello" class="com.zhong.bean.Hello"> <property name="userName" value="spring"/> </bean> </beans>
-
-
创建一个 java 类,该类中需要使用另外的对象。
-
在 spring 的配置文件中,把上面创建的类作为一个 Bean 进行配置。
-
在本项目的 pom 文件中添加 junit 的依赖
-
创建 java 的单元测试类,在测试类中创建 spring 的容器对象,通过容器对象得到已配置的 bean 对象并使用。
-
创建一个 spring 的测试类,利用测试类的功能来完成对 bean 的测试。(需要添加依赖)
-
五、spring 的 IOC
-
容器的用法一:创建容器的对象并告诉它用指定的配置文件来完成容器及内容的初始化和创建。如果程序中需要使用容器中的 bean,直接从容器中获取并使用。
-
package SpringDemo; import com.zhong.bean.Hello; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author 华韵流风 * @ClassName Demo1 * @Description TODO * @Date 2021/6/4 20:29 * @packageName com.zhong.SpringDemo */ public class Demo1 { //按照传统的方式使用Hello类 @Test public void test1(){ Hello hello = new Hello(); hello.setUserName("spring"); System.out.println(hello); } //用spring要求的方式来使用Hello类 @Test public void test2(){ //创建spring的容器对象,有很多种容器对象,要注意它们的使用场合,当前的配置是写在xml中 ApplicationContext ac = new ClassPathXmlApplicationContext("applicationcontext.xml"); Hello hello = (Hello)ac.getBean("hello"); System.out.println(hello); } }
-
-
容器的用法二:
-
package SpringDemo; import com.zhong.bean.Hello; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import javax.annotation.Resource; /** * @author 华韵流风 * @ClassName Demo2 * @Description TODO * @Date 2021/6/4 20:40 * @packageName com.zhong.SpringDemo * 作为 spring 的测试类需要加上两个注解 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationcontext.xml") public class Demo2 { //可以自动注入的bean @Resource Hello hello; @Test public void test(){ System.out.println(hello); } }
-
通过 spring-test 组件,利用配置文件自动创建容器对象,在类中使用 @Resource 注解,按照变量名及 bean 的名称进行匹配,如果有同名的 bean,就把该 bean 的对象交给被注入的属性,此时该属性就引用容器中的这个 bean。在类中的任何方法中都可以使用该 bean。
-
-
联系和区别:
- 都是利用 spring 容器提供的 IOC 和 DI 来完成 bean 的创建和注入。
- 都会创建 spring 的容器,也都需要 spring 的配置文件。
- 用法一要求自己创建容器,用法二由 spring-test 来创建容器。
- 用法一需要自己从容器中去得到 bean 并使用,用法二由容器把 bean 注入到对象中,我们直接使用。
- 显然,用法二比用法一更加舒服和优雅。
-
什么是 IOC(控制反转)
- 传统的方式,一个对象需要使用其他的对象,自己负责创建该对象,IOC 的方式是一个对象要使用其他的对象不由自己创建,而是交由容器创建,在需要使用的时候,把对象注入进来。这种变化体现了一种动作的反转,把自己做转化为别人做。IOC 是现代编程思想的一个重要部分,它更多的体现出是一种编程思想而不是具体的实现。
-
什么是 DI(依赖注入)
- 一个对象要使用其他的对象,自己不创建对象,由容器来创建并提供对象。因此,它是 IOC 的一种具体的实现。
- 在 spring 框架中,是利用 DI 来实现 IOC 编程思想的。
六、spring 如何创建容器并初始化 bean
- BeanFactory 接口,它表示 spring 的容器,而且具有基础的地位,spring 本身就是用该接口来使用容器。
- ApplicationContext 接口,它表示 spring 容器,它继承自 BeanFactory 且功能更强,体现出了容器具有父子结构的特点;它主要应用在应用编程中。
- spring 提供的容器的具体实现
- spring 可以与任何类型的 java 程序一起工作,比如普通 java 项目,比如 web 项目,比如使用注解来进行配置的项目等,而不同种类的项目它们启动和管理容器的方式是不同的,因此要求针对它们有不同的容器的实现,就好比不同的数据库要有不同的驱动程序。
- 常用的容器实现类
- ClassPathXmlApplicationContext,按类路径搜索,用 xml 来配置的容器。
- FileSystemXmlApplicationContext,按文件系统的目录位置,用 xml 配置的容器。
- AnnotationConfigApplicationContext,用 java 注解作为配置的容器。
- StubWebApplicationContext,使用在 JavaWeb 应用程序中的容器,既可以用 xml,又可以用 java 的配置类。
- spring 容器的创建过程
- 容器的创建过程非常复杂,有兴趣可以单独了解。
- 容器工作过程中的要点
- 通过 spring 提供的一个字符输入流(Reader)得到配置文件的内容,对配置文件的内容进行详细的解析,也就是按照配置内容的基本结构把节点及属性及节点的子节点读到一个确定的数据结构中。XmlBeanDefinitionReader 就是以上提到的配置读取器,它在 XmlBeanFactory 类中创建。XmlBeanDefinitionReader 中有多个 loadBeanDefinitions 的重载方法,这些方法用来把 Bean 的定义内容封装到 BeanDefinition 对象中。
- 分析 Bean 属性并创建 Bean
- BeanDefinition 接口体现了 bean 的具体特征和属性。
- BeanDefinitionParserDelegate 类的 createBeanDefinition 方法创建 Bean 定义的对象。parseBeanDefinitionElement 用来解析 bean 的配置内容。parsePropertyValue 方法解析 Bean 的属性值。
- 注册 bean
- DefaultListableBeanFactory 类中有 Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap(256) 属性,该属性是一个线程安全的Map,所有创建完成的Bean对象,都会注册到该 map 中,所以程序中调用 getBean() 方法时,就是从该map中得到 Bean 的对象。
- 以上类中的 registerBeanDefinition 方法负责把创建完成的 Bean 注册到以上的 Map 中。
七、如何在配置文件中定义 Bean,也就是如何使用 DI(依赖注入)
-
定义 Bean 的基本属性
-
<bean id="hello" class="com.zhong.bean.Hello"></bean>
-
id 就是 Bean 的名称不能重复,但可以有多个。class 就是 bean 的类型,不要使用接口。name 属性与 id 的作用相同,所以用了 id 就不要使用 name。
-
-
依赖注入的方式有三种
-
属性注入,类的属性必须有 setXXX 方法,容器调用该方法来完成属性的注入。最常用的方式,在配置中需要使用 property 子节点来设置键值对。
-
<bean id="hello" class="com.zhong.bean.Hello"> <property name="userName" value="spring"/> </bean>
-
-
构造方法注入:
-
<bean id="hello1" class="com.zhong.bean.Hello"> <constructor-arg index="0" value="java"/> </bean>
-
-
工厂方法注入(基本不用)
-
-
依赖注入的属性值
-
值为直接量,配置时用 value 属性,如果是空值使用 </null>。
-
值为引用类型(当前属性引用另外一个 Bean)
- 可以先定义一个外部 Bean,再通过 property 子节点,使用 ref 属性来引用外部 bean。
- 也可以使用内部 bean(把 bean 定义在另一个 bean 的内部,不需要指定 id)
-
值为集合:
-
<util:list id="list"> <value>abc</value> <value>xyz</value> </util:list> <util:set id="set"> <value>aaa</value> <value>bbb</value> </util:set> <util:map id="map"> <entry key="k1" value="v1"/> <entry key="k2" value="v2"/> </util:map> <bean id="hello1" class="com.zhong.bean.Hello"> <property name="userName" value="spring"/> <property name="person"> <bean class="com.zhong.bean.Person"> <property name="name" value="李四"/> </bean> </property> <property name="list" ref="list"/> <property name="set" ref="set"/> <property name="map" ref="map"/> </bean>
-
List 集合
-
Set 集合
-
Map 集合
-
常用的集合就是以上三种,也包括 Properties 集合。
-
如果集合的元素是基本类型或字符串,使用 value 来写直接量的值,如果元素是引用类型使用是 ref 引用其他的 bean。在 bean 中引用集合类型要使用 ref 属性。
-
-
八、自动装配 Bean
- 手动注入情况下,如果值不存在就会出错。
- 自动装配就是在定义一个 Bean 时,针对需要注入的属性不直接指定要使用的值或引用的对象,而是让容器按照 spring 制定的一些规划来完成。
- 在 Bean 节点中提供了属性 autowire,该属性的取值有几个最重要的是两个分别是 “byType” 和 “byName”,但是 “byType” 又是最常用的,也是默认的。
- byType 表示按照类型来执行自动装配,此处的类型指的是 bean 中属性的类型。如果在容器中存在与属性的类型相同的 Bean 且只有一个,就可以把该 Bean 注入到同类型的属性上。如果同类型的 Bean 有多个会报错。
- 如果按类型匹配且存在多个同类型的外部 Bean,就可以采用 byName 进行自动装配,与属性名相同的外部 Bean 可以进行装配。
- 自动装配的方式并不是最好的方式,容易造成混乱。
-
<bean id="hello" class="com.zhong.bean.Hello" autowire="byType"> <property name="userName" value="spring"/> </bean> <!--外部Bean的类型与hello的person属性的类型相同,就可以把它自动注入到hello中--> <bean class="com.zhong.bean.Person"> <property name="name" value="张三"/> </bean>
九、Bean 的作用域
- 所谓作用域就是 Bean 在什么样的范围内有效。
- Bean 的一个 scope 的属性,有两个合法的取值,一个是 singleton,一个是 prototype。
- singleton:表示当前的 Bean 是单例的,也就是调用 getBean() 方法时,无论调用多少次在哪里调用得到的都是唯一的对象。这是默认值。在 jvm 中,如果一个对象会被多个线程使用,尽可能的让这些线程共用同一个对象,这样内存的消耗最少。如果对象中没有可供线程共享的数据就不会产生问题。因为线程在栈中有独立的工作区,它们会独自执行相同的方法而不会互相干扰。
- prototype:表示非单例,每次调用 getBean() 方法得到的都是不同的对象。该取值很少见。
十、Bean 的生命周期
- 容器中对的 Bean 也有从创建到消亡的过程,这整个的过程就是 Bean 的生命周期。
- Bean 的生命周期是比较复杂的,对于 spring 的 IOC 的实现是非常重要的。需要对流程和作用有一个基本认识。
- Bean 的 init-method=“” 属性用来指定 Bean 的初始化方法,如果指定了该方法,在 Bean 的生命周期中会被调用。
- Bean 的 destroy-method="" 属性用来指定 Bean 的销毁方法,如果指定了该方法,在 Bean 被清除或容器关闭时会被调用。
- 总结:以上两个方法属于 Bean 一级的生命周期方法。
- BeanNameAware 接口,BeanFactoryAware 接口,Aware 有可感知的含义,它们可以被当前的 Bean 所实现,从而去感知当前环境或 Bean 有关的一些事项,转而可以干一些事情。它们可以分别感知当前的 Bean 的名称及当前的容器对象。
- 总结:以上两个接口属于 Bean 一级的生命周期接口
- Bean 的后置处理器
- BeanPostProcessor 接口是后置处理器必须实现的接口。
- 后置处理器可以分为两大类:第一就是自定义的后置处理器;第二就是 spring 固有的后置处理器。
- 以上接口有两个方法分别是 postProcessBeforeInitialization(),postProcessAfterInitialization(),它们在生命周期中的执行时机有先后之分,先后的比较基准就是 Bean 的初始化方法的执行,before 和 after 分别在初始化方法的先后执行。它们的方法的参数相同,Object bean 表示当前的 bean 对象,beanName 就是它的名称,因此我们可以在初始化方法的前后通过当前的 bean 对象和名称来做一些我们想做的事情,比如检查 Bean 的属性的有效性等。
- 总结:后置处理器属于容器级的生命周期接口,它与容器中所有的 Bean 都有关系,如果在容器中注册了后置处理器,对于容器中的每个 bean 都会调用后置处理器的方法进行处理。
- spring 固有的后置处理器
- spring 框架为了增强容器的功能也提供了几个自己使用的后置处理器。
- 比如 ConfigurationClassPostProcessor 就是其中之一,这类后置处理器有专用的注册方式,也就是 spring 提供了一些专用的标签来注册这类处理器。
- 以上的处理器对于 Bean 的注解式自动装配以及通过 java 配置类来实现配置(springboot 框架)起到了决定性的作用。
十一、在容器中使用外部文件
- 容器中的 Bean 属性的取值,有时候会写在外部文件中,比如数据库连接池的配置参数。
-
如何引用外部文件:<context:property-placeholder location=""/>
-
怎样取到外部文件的值(类似于EL 表达式,也就是spEL)。
-
<bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="${jdbc.url}"/> </bean>
-
十二、基于注解实现 DI 的相关功能
-
实现 DI 的基本要素
- 目标 Bean,当前需要使用的或正在工作的对象。
- Bean 需要使用的对象(属性)。
- spring 容器的配置文件、
- 在配置文件中的 Bean 配置,指定当前的 Bean(被容器实例化并管理的对象),某个 Bean 所需要依赖的另外的 Bean(被注入的对象)。
- Bean 的配置内容中的一些属性及子节点等。
-
通过注解来实现 DI
- 依据以上的分析,实现 DI 可以分为两个主要部分,第一被注入的对象;第二需要注入的对象。
- 利用注解让 spring 容器能够创建 Bean 对象。我们可以在类的声明上添加相关的注解,这些类就会被 spring 容器创建为 spring 对象并管理。
- @Component 组件注解,凡是类上面添加了该注解,该类会被 spring 容器当做 Bean 来进行创建并使用,就好比该类在配置文件中被作为 Bean 进行配置。在配置文件中添加 component-scan 表示对包中添加了 @Component 注解的类进行特殊的处理(当做 Bean 进行处理,容器中就会有该类的对象)
- @Controller 控制器注解
- @Service 业务逻辑注解
- @Repository 数据访问注解
- 以上三个注解,本质上都具有 @Component 注解的功能,它们等同于 @Component。
- 按照 mvc 设计模式,需要把基本组件分为三种,就是控制器,业务层,dao层。
- 以上三个层面的对象都要被 spring 进行管理,都要作为 Bean 来使用。
- 如果采用注解的方式来使用它们,都需要添加 @Component 注解。
- 为了在形式上区分这三层的对象实例,所以给它们各自命名了不同的注解,虽然这些注解的作用是一样的。
- 以上四个注解都有 value 属性,也就是默认属性。属性值就是自定义的组件名称。
-
利用注解的方式来注入 Bean
- 在 mvc 设计模式下,控制器中需要使用业务逻辑,业务逻辑中需要使用 dao。
- 实现 bean 注入的相关注解
- Autowired
- 可以用在属性、set 方法、构造方法上面,起到的作用是一样的,都可以把 bean 注入到本类中。
- 它默认采用 byType 的注入方式,如果容器中只有一个该类型的 Bean,就注入该 Bean。
- 如果该类型的 Bean 不止一个,就按照 byName 的注入方式。
- 如果同名的 bean 有多个,就要配合 @Qualifier(“beanname”) 来指定唯一的 bean 的名称。
- 如果唯一类型或唯一名称的 bean 都不存在,可以添加一个属性 @Autowired(required=false),就不会报错。
- 几乎所有的 Bean 都是单例模式,所以依据类型进行注入的方式使用最多。
- @Resource
- 可以用在属性、set 方法、构造方法上面,起到的作用是一样的,都可以把 bean 注入到本类中。
- 它默认采用 byName的注入方式,如果容器中只有一个该名称的 Bean,就注入该 Bean。
- 如果该类型的 Bean 不止一个,就按照 byType的注入方式。
- 如果同类型的 bean 有多个,就要添加名称或者类型属性来进行区分。@Resource(name=“”,type=)
- 如果指定类型或指定名称的 bean 都不存在,就会报错。
- Bean 的 name 默认是类的名称首字母小写,但是在不同的包下有相同的类名会造成歧义,出错的可能性大一些。
- Autowired
- @Resource 注解肯定是结合 @Component 来使用的,@Component 及另外三个注解都需要明确的指定组件的名称,否则容易出问题。
- @Inject 注解,它与 @Autowired 的作用相似,也是依据类型来进行注入,区别在于 @Inject 注解遇到同一类型出现多个,需要配合 @Name 进行名称上的区分。
十三、基于 java 的配置
- 基于 xml 的配置出现于 spring1.0。配置的内容写在外部文件中,因此配置与代码是分开的,互不干扰,低耦合。但是书写的工作量比较大,会出现一些重复内容(标签的反复使用且属性名基本一致)。现在把项目中的类似于数据源的配置,以及一些常用属性的设置,这些属性项目中的基础配置,采用 xml 的配置方式。
- 基于注解的方式出现于 spring2.0.原因在于 jdk1.5 中出现了注解的新特性。这种配置最简单,但是配置的内容与代码耦合在一起,破坏了代码的结构。现在把项目中与业务相关的内容采用注解的配置,比如 service,dao 等。
- 基于 java 的配置方式出现于 spring3.0。此时 spring 新增了两个注解,分别是 @Configuration,@Bean。好处在于把配置与 java 的类整合在一起,另外开发平台对 java 类提供了很多很方便的操作,比如查错,自动编译,生成字节码等。
- 利用 java 的类作为配置的载体,也就是一个 java 类可以作为配置文件来使用,因此配置类上必须添加 @Configuration 注解。
- 利用 java 类中的方法作为一个 <bean> 的节点来使用,因此方法上必须添加 @Bean 注解。
- 作为 <bean> 节点的方法必须返回该 bean 所要求创建的对象。
- 在方法中一般会创建 bean 的对象并给它设置属性值。
- 在 java 配置类中可以指定要使用的外部文件。需要使用 @PropertySources。
- 在 java 配置类中可以指定需要扫描的包,这样在类中使用的与 DI 相关的注解就可以生效。因此 java 配置类与 DI 相关的注解就可以生效。因此 java 配置类与 DI 相关的注解可以在项目中同时使用。