一、初识Spring
1.1 Spring是什么
Spring是一个轻量级Java开发框架,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题。它是一个分层的开源框架,为开发Java应用程序提供全面的基础架构支持。Spring负责基础架构,Java开发者可以专注于应用程序的开发。
Spring最根本的使命是简化Java开发。
Spring的功能底层都依赖于它的两个核心特性,也就是依赖注入(DI)和面向切面编程(AOP)。
为了降低Java开发的复杂性,Spring采取了以下4种关键策略:
基于POJO的轻量级和最小侵入性编程;
通过依赖注入和面向接口实现松耦合;
基于切面和惯例进行声明式编程;
通过切面和模板减少样板式代码。
Spring框架的核心:IoC容器和AOP模块。通过IoC容器管理对象以及他们之间的耦合关系;通过AOP以动态非侵入的方式增强服务。
1.2 Spring的特点
1、轻量级
组件大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1M多的JAR文件中发布,并且Spring所需的处理开销也是微不足道的。此外,Spring是非侵入式,典型案例,Spring应用中的对象不依赖于Spring特定的类。
2、控制反转
Spring通过控制反转(IOC)技术实现解耦。一个对象依赖的其他对象会通过被动的方式传递进来,而不需要对象自己创建或者查找依赖。
3、面向切面
支持切面(AOP)编程,并且吧应用业务逻辑和系统服务区分开。
4、容器
Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器。可以配置每个bean如何被创建、销毁,bean的作用范围是单例还是每次都生成一个新的实例,以及他们是如何相互关联。
5、框架集合
将简单的组件配置,组合成为复杂的框架;应用对象被申明式组合;提供许多基础功能(事务管理、持久化框架继承),提供应用逻辑开发接口。
1.3 Spring的优缺点
1.3.1 优点
1、方便解耦,简化开发(IOC)
Spring就是一个工厂,可以将所有对象的创建和依赖关系的维护,交给Spring管理,实现了松散耦合。
2、AOP
Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能,把应用业务逻辑和系统服务分开。
3、声明式事务的支持(@Transactional)
只需要通过配置就可以完成对事务的管理,而无需手动编程。
4、方便程序的测试
Spring对Junit4支持,可以通过注解方便的测试Spring程序。
5、方便集成各种优秀框架
Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如MyBatis等)。
1.3.2 缺点
Spring依赖反射,反射影响性能。
1.4 Spring的模块组成
Spring5的模块结构图:
Spring的较核心模块:
1、Spring core(核心容器)
Beans:负责Bean工厂中Bean的装配,所谓Bean工厂即是创建对象的工厂,Bean的装配也就是对象的创建工作;
Core:这个模块即是负责IOC(控制反转)最基本的实现;
Context:Spring的IOC容器,因大量调用Spring Core中的函数,整合了Spring的大部分功能。Bean创建好对象后,由Context负责建立Bean与Bean之间的关系并维护。所以也可以把Context看成是Bean关系的集合;
SpEl:即Spring Expression Language(Spring表达式语言)。
2、Data Access/Integration(数据访问/集成)
JDBC:对JDBC的简单封装;
ORM:支持数据集成框架的封装(如Mybatis,Hibernate);
OXM:即Object XML Mapper,它的作用是在Java对象和XML文档之间来回转换;
JMS:生产者和消费者的消息功能的实现;
Transations:事务管理。
3、Web
WebSocket:提供Socket通信,web端的的推送功能;
Servlet:Spring MVC框架的实现;
Web:包含web应用开发用到Spring框架时所需的核心类,包括自动载入WebApplicationContext特性的类,Struts集成类、文件上传的支持类、Filter类和大量辅助工具类;
Portlet:实现web模块功能的聚合。
4、AOP
AOP把一个业务流程分成几部分,例如权限检查、业务处理、日志记录,每个部分单独处理,然后把它们组装成完整的业务流程。
AOP包括:aop、aspects、instrument共3个模块。
5、Aspects
提供了与AspectJ的集成功能,AspectJ是一个功能强大且成熟的AOP框架。
6、Instrumentation(设备)
相当于一个检测器,提供对JVM以及对Tomcat的检测。
7、Messaging(消息)
Spring提供的对消息处理的功能。
8、Test(测试)
在做单元测试时,Spring会帮初始化一些测试过程当中需要用到的资源对象。
Spring各个模块之间的依赖关系:
1.5 Spring主要Package
org.springframework.core:Spring的核心工具包,其他包依赖此包。
org.springframework.beans:所有应用都用得到,包含访问配置文件,创建和管理bean等。
org.springframework.aop:Spring的面向切面编程,提供AOP的实现。
org.springframework.context:提供在基础IOC功能上的扩展服务,此外还提供许多企业级服务的支持,有邮件服务、任务调度、JNDI定位,EBJ集成、远程访问、缓存以及多种视图层框架的支持。
org.springframework.web.mvc:包含SpringMVC应用开发所需的核心类。
org.springframework.transaction:为JDBC、HIBERNATE、JDO、JPA提供一致的声明式和编程式事务管理。
org.springframework.web:包含Web应用开发时,用到Spring框架时所需的核心类。
org.springframework.aspects:Spring提供的对AspectJ框架的整合。
org.springframework.test:对JUNIT等测试框架的简单封装。
org.springframework.asm:Spring3.0开始提供自己独立的asm jar包。
org.springframework.context.support:Spring context的扩展支持,用于MVC方面。
org.springframework.expression:Spring表达式语言。
org.springframework.instrument.tomcat:Spring对Tomcat连接池的集成。
org.springframework.instrument:Spring对服务器的代理接口。
org.springframework.jdbc:对JDBC的简单封装。
org.springframework.jms:为简化jms api的使用而做的简单封装。
org.springframework.orm:整合第三方的orm实现,如hibernate、ibatis、jpa等。
org.springframework.oxm:Spring对于Object/xml映射的支持,可以让Java与xml切换。
org.springframework.portlet:Spring MVC的增强。
org.springframework.servlet:对J2EE6.0 Servlet3.0的支持。
org.springframework.web.struts:整合对struts框架的支持,更方便更容易地集成Struts框架。
1.6 Spring5特性
1)基于JDK1.8。
2)支持HTTP/2。
3)Spring WebFlux响应式编程。
4)支持Kotlin函数式编程。
1.8 不同版本的Spring,有哪些主要功能?
版本 特性
Spring2.5 发布于 2007 年。这是第一个支持注解的版本。
Spring3.0 发布于 2009 年。它完全利用了Java5中的改进。
Spring4.0 发布于 2013 年。这是第一个完全支持 JAVA8 的版本。
Spring5.0 Spring Framework5.0的最大特点之一是响应式编程。
二、IOC简介
2.1 初识IOC
2.1.1 IOC是什么
IoC (控制反转),是技术思想,不是技术实现。IOC描述的事情:Java开发领域对象的创建,管理的问题。
IoC思想下开发方式:我们不用自己去new对象了,而是由IoC容器(Spring框架)去帮助我们实例化对象并且管理它,我们需要使用哪个对象,从IoC容器中取即可。
控制反转,它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。
Spring通过一个配置文件(xml,表示Bean关系方式的一种),描述Bean及Bean之间的依赖关系,利用反射功能实例化Bean并建立Bean之间的依赖关系。Spring的IoC容器在完成这些底层工作的基础上,还提供了Bean实例缓存、生命周期管理、 Bean实例代理、事件发布、资源装载等高级服务。
为什么叫做控制反转?
控制:指的是对象创建(实例化、管理)的权利。
反转:控制权交给外部环境了(spring框架、IoC容器)。
2.1.2 什么是Spring IOC容器
控制反转即IoC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。
Spring IOC 负责创建对象,管理对象,装配对象,配置对象,并且管理这些对象的整个生命周期。即:IoC容器就是具有依赖注入功能的容器,IoC容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。应用程序无需直接在代码中创建相关的对象,应用程序由IoC容器进行组装。
Spring IoC容器如何知道哪些是它管理的对象呢?这就需要配置文件,Spring IoC容器通过读取配置文件中的配置元数据,通过元数据对应用中的各个对象进行实例化及装配。由IoC容器管理的那些组成应用程序的对象我们就叫它Bean, Bean就是由Spring容器初始化、装配及管理的对象。
IOC容器的职责是:
实例化Bean;
把Bean关联在一起;
配置Bean;
管理Bean的整个生命周期。
Spring作为IOC容器(BeanFactory)的简单理解
Spring启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配好Bean之间的依赖关系,为上层应用提供准备就绪的运行环境。其中Bean缓存池为HashMap实现。图示:
2.1.3 控制反转有什么作用
1、管理对象的创建和依赖关系的维护;
2、解耦。
2.1.4 IoC和DI的区别
DI:Dependancy Injection(依赖注入)。IOC和DI描述的是同一件事情,只不过角度不一样。
IOC,控制反转, 就是将原本在程序中手动创建对象的控制权,交由Spring框架管理,简单说,就是创建对象控制权被反转到了Spring框架。
DI,依赖注入,在Spring框架负责创建Bean对象时,动态的将依赖对象注入到Bean组件。
IoC控制反转,指将对象的创建权,反转到Spring容器,DI依赖注入,指Spring创建对象的过程中,将对象依赖属性通过配置进行注入。
2.1.5 Spring IoC的实现原理
Spring IoC的实现原理就是工厂模式加反射机制。图示:
2.2 BeanFactory与ApplicationContext
BeanFactory是Spring框架中IoC容器的顶层接口,它只是用来定义那些基础功能,ApplicationContext是它的一个子接口。
在初始化BeanFactory时,必须为其提供一种日志框架,比如使用Log4J, 即在类路径下提供Log4J配置文件,这样启动Spring容器才不会报错。
通常,称BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的高级接口。ApplicationContext比BeanFactory要拥有更多的功能,比如说国际化支持和资源访问(xml,java配置类)等。
BeanFactory可以理解为就是个HashMap,Key是BeanName,Value是Bean实例。通常只提供注册(put),获取(get)这两个功能。可以称之为 “低级容器”。
ApplicationContext 可以称之为 “高级容器”,因为比 BeanFactory 多了更多的功能。
BeanFactory和ApplicationContext 接口及其子类图:
上图中一些类的关键作用:
ClassPathXmlApplicationContext:默认从类路径加载配置文件。
FileSystemXmlApplicationContext:默认从文件系统中装载配置文件。
ApplicationEventPublisher:让容器拥有发布应用上下文事件的功能,包括容器启动事件、关闭事件等。
ResourcePatternResolver : 所有ApplicationContext实现类都实现了类似于PathMatchingResourcePatternResolver的功能,可以通过带前缀的Ant风格的资源文件路径装载Spring的配置文件。
LifeCycle:该接口是Spring2.0加入的,该接口提供了start()和stop()两个方法,主要用于控制异步处理过程。在具体使用时,该接口同时被ApplicationContext实现及具体Bean实现, ApplicationContext会将start/stop的信息传递给容器中所有实现了该接口的Bean,以达到管理和控制JMX、任务调度等目的。
ConfigurableApplicationContext扩展于ApplicationContext,它新增加了两个主要的方法: refresh()和 close(),让ApplicationContext具有启动、刷新和关闭应用上下文的能力。在应用上下文关闭的情况下调用refresh()即可启动应用上下文,在已经启动的状态下,调用refresh()则清除缓存并重新装载配置信息,而调用close()则可关闭应用上下文。
2.2.1 BeanFactory和ApplicationContext的区别
1、依赖关系
BeanFactory:是Spring里面最顶级的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。
ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
继承MessageSource,因此支持国际化。
统一的资源文件访问方式。
提供在监听器中注册bean的事件。
同时加载多个配置文件。
载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
2、加载方式
BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
3、底层资源的访问
ApplicationContext扩展了ResourceLoader(资源加载器)接口,从而可以用来加载多个Resource,而BeanFactory是没有扩展ResourceLoader。
绝大多数情况下建议使用ApplicationContext,因为ApplicationContext包含BeanFactory的所有功能,因此通常建议优先于BeanFactory使用它。
4、创建方式
BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。
5、注册方式
BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。
2.2.2 ApplicationContext的实现类
ClassPathXmlApplicationContext和FileSystemXmlApplicationContext的路径加载区别:
ClassPathXmlApplicationContext默认文件路径是src下那一级;
FileSystemXmlApplicationContext 默认获取的是项目路径,默认文件路径是项目名下一级,与src同级。
三、通过XML方式创建Bean
3.1 在XML中通过构造器方式创建Bean的3种方式【使用较少】
Spring Bean是那些形成Spring应用的Java对象,它们被Spring IOC容器初始化、装配和管理。
3.1.1 构造器实例化Bean
假设有一个Person类,有name、age两个属性,通过构造器的方法的配置方式示例:
<bean id="person" class="com.spring.demo.Person">
<constructor-arg name="name" value="等风的草"/>
<constructor-arg name="age" value="21"/>
</bean>
3.1.2 静态工厂实例化Bean
当采用静态工厂方法创建bean时,除了需要指定class属性外,还需要通过factory-method属性来指定创建bean实例的工厂方法。
假设有这样的静态工厂PersonStaticFactory类:
public static Person createInstance() {
return new Person();
}
配置文件(class:指定静态工厂类;factory-method:指定哪个方法是工厂方法):
<bean id="person" class="com.spring.demo.PersonStaticFactory"
factory-method="createInstance">
<constructor-arg name="name" value="等风的草"/>
<constructor-arg name="age" value="21"/>
</bean>
这种方式获取Bean的方式也和通过构造器方法创建Bean的方式类似,示例:
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext(xmlPath);
System.out.println(applicationContext .getBean("person"));
3.1.3 实例工厂实例化bean
示例:
public class InstanceFactory {
public Person createInstance() {
return new Person();
}
}
配置文件示例(factory-bean:指定使用哪个工厂实例。factory-method:指定使用哪个工厂实例的方法):
<bean id="instancefactory" class="com.spring.demo.InstanceFactory"/>
<bean id="personInstance" factory-bean="instancefactory" factory-method="createInstance"/>
3.2 在XML中通过有参构造方法和无参构造方法创建Bean【使用较少】
构造方法分有参和无参2种,因此通过构造方法来创建Bean自然也分为2种。
3.2.1 通过无参构造方法来创建Bean
实体类:
public class Hello {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("Hello,"+ name );
}
}
Spring的配置文件,在src目录下,命名为beans.xml:
<?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.spring.test.Hello"></bean>
</beans>
测试类:
public class SpringTest {
public static void main(String[] args) {
test();
}
public static void test(){
//解析beans.xml文件 , 生成管理相应的Bean对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//getBean : 参数即为spring配置文件中bean的id
Hello hello = (Hello) context.getBean("hello");
//Hello,null
hello.show();
}
}
3.2.2 通过有参构造方法来创建Bean
通过有参构造方法来创建对象的方式有三种,示例:
<!-- 第一种根据index参数下标设置 -->
<bean id="user1" class="com.test.pojo.User">
<!-- index指构造方法 , 下标从0开始 -->
<constructor-arg index="0" value="zhangsan"/>
</bean>
<!-- 第二种根据参数名字设置 -->
<bean id="user2" class="com.test.pojo.User">
<!-- name指参数名 -->
<constructor-arg name="name" value="lisi"/>
</bean>
<!-- 第三种根据参数类型设置 -->
<bean id="user3" class="com.test.pojo.User">
<constructor-arg type="java.lang.String" value="wangwu"/>
</bean>
3.3 constructor-arg标签属性
在使用构造函数注入时,涉及的标签是<constructor-arg>,该标签有如下属性:
name:用于给构造函数中指定名称的参数赋值。
index:用于给构造函数中指定索引位置的参数赋值。
value:用于指定基本类型或者String类型的数据。
ref:用于指定其他Bean类型的数据。写的是其他bean的唯一标识。
3.4 在XML中通过set注入属性的方法来创建Bean【使用较少】
在上面的两种创建Bean的方式中,要求Bean有对象的构造方法,那么有没有其他比较灵活的注入方式呢?当然是有的,就是set方法的注入方式,这种方式要求Bean有对应的setXXX方法。
配置文件示例:
<bean id="hello" class="com.spring.test.Hello">
<property name="name" value="Spring"/>
</bean>
3.5 在XML中通过set方式注入不同类型数据【使用较少】
通过set的方式注入最普通的字符串,来创建对象。其实,除了字符串,还可以通过set的放入注入其他数据。3.5.1 注入其他对象
<bean id="addr" class="com.test.pojo.Address">
<property name="address" value="重庆"/>
</bean>
<bean id="student" class="com.test.pojo.Student">
<property name="name" value="小明"/>
<property name="address" ref="addr"/>
</bean>
3.5.2 注入数组
<bean id="student" class="com.test.pojo.Student">
<property name="name" value="小明"/>
<property name="address" ref="addr"/>
<property name="books">
<array>
<value>英雄志</value>
<value>沧浪之水</value>
<value>梦的解析</value>
</array>
</property>
</bean>
3.5.3 注入List
<!--注入字符串List-->
<property name="interest">
<list>
<value>听歌</value>
<value>看电影</value>
<value>爬山</value>
</list>
</property>
<!--注入对象List-->
<property name="empList">
<list>
<ref bean="emp1" />
<ref bean="emp2"/>
</list>
</property>
3.5.4 注入Map
<property name="story">
<map>
<entry key="余华" value="活着"/>
<entry key="东野圭吾" value="白夜行"/>
</map>
</property>
<property name="empMap">
<map>
<entry key="1" value-ref="emp1" />
<entry key="2" value-ref="emp2" />
</map>
</property>
3.5.5 注入Set
<property name="movie">
<set>
<value>兹山鱼谱</value>
<value>浪客剑心</value>
</set>
</property>
<property name="empSets">
<set>
<ref bean="emp1" />
<ref bean="emp2"/>
</set>
</property>
3.5.6 注入Null
在Spring中,不仅可以注入一个Null值,也可以注入空字符串。
<property name="wife">
<null/>
</property>
<property name="name">
<null></null>
</property>
3.5.7 注入属性
用于注入键值对,键和值都只能为String类型。
<property name="info">
<props>
<prop key="学号">20190604</prop>
<prop key="性别">男</prop>
<prop key="姓名">小明</prop>
</props>
</property>
3.6 property标签属性
在使用set方法注入时,需要使用<property>标签,该标签属性如下:
name:指定注入时调用的set方法名称。(注:不包含set这三个字母,druid连接池指定属性名称)
value:指定注入的数据。它支持基本类型和String类型。
ref:指定注入的数据。它支持其他bean类型。写的是其他bean的唯一标识。
3.7 构造器依赖注入和Setter方法注入的区别
1、Setter方法注入
1、设值注入需要该Bean包含这些属性的setter方法。
2、对于复杂的依赖关系,如果采用构造注入,会导致构造器过于臃肿。Spring在创建Bean实例时,需要同时实例化器依赖的全部实例,因而导致性能下降。而使用设值注入,则能避免这些问题。
3、尤其是在某些属性可选的情况下,多参数的构造器显得更加笨重。
2、构造注入
1、构造注入需要该Bean包含带有这些属性的构造器。
2、构造注入可以在构造器中决定依赖关系的注入顺序,优先依赖的优先注入。例如,组件中其他依赖关系的注入,常常要依赖于DataSrouce的注入。采用构造注入,可以在代码中清晰的决定注入顺序。
3、对于依赖关系无需变化的Bean,构造注入更有用处。因为没有Setter方法,所有的依赖关系全部在构造器内设定。因此,无需担心后续的代码对依赖关系产生破坏。
4、依赖关系只能在构造器中设定,则只有组件的创建者才能改变组件的依赖关系。对组件的调用者而言,组件内部的依赖关系完全透明,更符合高内聚的原则。
在两种方式的选择上:最好的解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖。
3.8 Bean标签属性
在基于XML的IoC配置中,bean标签是最基础的标签。它表示了IoC容器中的1个对象。换句话说,如果1个对象想让spring管理,在XML的配置中都需要使用此标签配置,Bean标签的属性如下:
id属性: 用于给Bean提供1个唯一标识。在1个标签内部,标识必须唯一。
class属性:用于指定创建Bean对象的全限定类名。
name属性:用于给Bean提供1个或多个名称。多个名称用空格分隔。
factory-bean属性:用于指定创建当前bean对象的工厂Bean的唯一标识。当指定了此属性之后,class属性失效。
factory-method属性:用于指定创建当前bean对象的工厂方法,如配合factory-bean属性使用,则class属性失效。如配合class属性使用,则方法必须是static的。
scope属性:用于指定bean对象的作用范围。通常情况下就是singleton。当要用到多例模式时,可以配置为prototype。
init-method属性:用于指定Bean对象的初始化方法,此用法会在Bean对象装配后调用。必须是1个无参方法。
destory-method属性:用于指定bean对象的销毁方法,此方法会在Bean对象销毁前执行。它只能为scope是singleton时起作用。
四、通过注解方式创建Bean
在实际开发中,纯XML管理Bean的方式已经很少用了,更多的时候用的是注解的方式。
xml中标签与注解的对应关系示例:
xml形式 对应的注解形式
<Bean> @Component(“accountDao”),注解加在类上Bean的id属性内容直接配置在注解后如果不配置,默认定义个这个bean的id为类的类名首字母小写;
另外,针对分层代码开发提供了@Componenet的三种别名@Controller、@Service、@Repository分别用于控制层类、服务层类、dao层类的bean定义,这四个注解的用法完全一样,只是为了更清晰的区分
Scope属性 @Scope(“prototype”),默认单例,注解加在类上
其他的一些较常用的注解:
@Configuratio:表明当前类是一个配置类。
@ComponentScan:替代 context:component-scan。
@Value:对变量赋值,可以直接赋值,也可以使用 ${} 读取资源配置文件中的信息。
@Bean:将方法返回对象加入Spring IOC容器。
@Component的简单使用
配置文件中需要添加context:component-scan配置,示例:
<context:component-scan base-package="com.spring.test"/>
@Component的使用简单示例:
@Component("user")
//相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
String name = "自己";
}
测试代码:
public static void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");
//自己
System.out.println(user.name);
}
@Value的简单使用
可以不用提供set方法,直接在直接名上添加@value(“值”),示例:
@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
@Value("自己")
// 相当于配置文件中 <property name="name" value="自己"/>
public String name;
}
如果提供了set方法,在set方法上添加@value(“值”),示例:
@Component("user")
public class User {
public String name;
@Value("自己")
public void setName(String name) {
this.name = name;
}
}
4.1 Bean自动装配的5种方式
Bean装配是指在Spring容器中,把Bean组装到一起,前提是容器需要知道Bean的依赖关系,如何通过依赖注入来把它们装配到一起。
Spring装配包括手动装配和自动装配,手动装配是有基于xml装配、构造方法、setter方法等。
自动装配有五种自动装配的方式,可以用来指导Spring容器用自动装配方式来进行依赖注入。
先准备几个实体类:
public class Cat {
public void shout() {
System.out.println("喵~");
}
}
public class Dog {
public void shout() {
System.out.println("汪~");
}
}
public class User {
private Cat cat;
private Dog dog;
private String str;
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
}
4.1.1 no
默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。示例:
<bean id="dog" class="com.spring.test.Dog"/>
<bean id="cat" class="com.spring.test.Cat"/>
<bean id="user" class="com.spring.test.User">
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
<property name="str" value="test"/>
</bean>
测试:
public static void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");
//喵~
user.getCat().shout();
//汪~
user.getDog().shout();
}
4.1.2 byName
通过Bean的名称进行自动装配,如果一个Bean的property与另一个Bean的name相同,就进行自动装配。
示例:
<bean id="user" class="com.spring.test.User" autowire="byName">
<property name="str" value="test"/>
</bean>
测试代码:
public static void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");
//test
System.out.println(user.getStr());
}
该模式表示根据Property的Name自动装配,如果一个Bean的name,和另一个Bean中的Property的name相同,则自动装配这个Bean到Property中。
也就是说,当一个Bean节点带有autowire="byName"的属性时,将查找其类中所有的set方法名,获得将set去掉并且首字母小写的字符串,然后去spring容器中寻找是否有此字符串名称id的对象。如果有,就取出注入;如果没有,就报空指针异常。
4.1.3 byType
通过参数的数据类型进行自动装配。使用autowire="byType"时,首先需要保证:同一类型的对象,在Spring容器中唯一。如果不唯一,会报不唯一的异常(NoUniqueBeanDefinitionException)。
如果照下面的配置,就会报异常,因为有两个同类型的Bean:
<bean id="dog" class="com.spring.test.Dog"/>
<bean id="cat" class="com.spring.test.Cat"/>
<bean id="cat2" class="com.spring.test.Cat"/>
<bean id="user" class="com.spring.test.User" autowire="byType">
<property name="str" value="test"/>
</bean>
删掉id为cat或cat2的Bean,则代码可以正常运行,示例:
public static void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");
//test
System.out.println(user.getStr());
}
4.1.4 constructor
利用构造函数进行装配,并且构造函数的参数通过byType进行装配。这个方式类似于byType,但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
先在User实体类中加入一个构造方法:
public User(Cat cat) {
this.cat = cat;
}
然后配置文件改为:
<bean id="cat" class="com.spring.test.Cat"/>
<bean id="user" class="com.spring.test.User" autowire="constructor"></bean>
测试:
public static void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");
//喵~
user.getCat().shout();
}
4.1.5 autodetect
该模式自动探测使用构造器自动装配或者byType自动装配。首先会尝试找合适的带参数的构造器,如果找到就是用构造器自动装配,如果在Bean内部没有找到相应的构造器或者是无参构造器,容器就会自动选择byTpe的自动装配方式。
4.2 开启自动装配的注解
Jdk1.5开始支持注解,Spring2.5开始全面支持注解。
要使用注解,需要在配置文件里加上<context:annotation-config/>,用来开启注解支持。
配置文件中关于Bean的关键配置示例:
<context:annotation-config/>
<bean id="cat" class="com.spring.test.Cat"/>
<bean id="user" class="com.spring.test.User"/>
此时就可以在代码中使用@Autowired注解自动获取到对应的对象了,这也是实际项目中常见的做法。示例:
public class User {
@Autowired
private Cat cat;
//其他代码
}
4.3 @Autowired和@Resource
4.3.1 @Autowired注解
@Autowired注解,用来自动装配指定的bean。
@Autowired注解原理:在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:
如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
如果查询的结果不止一个,那么@Autowired会根据名称来查找;
如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。 示例:@Autowired(required=false)。
@Autowired(required=false) 使用说明:当值为false时,对象可以为null;当值为true时,对象必须存对象,不能为null。
@Autowired可用于:构造函数、成员变量、Setter方法。@Autowired的作用是自动注入Bean。最常见的使用示例:
public class UserService {
//相当于直接给userDao变量实例化
@Autowired
private UserDao userDao;
}
如上代码所示,Spring容器会找到类型为UserDao的类,然后将其注入进来。这样会产生一个问题,当一个类型有多个Bean值的时候,会造成无法选择具体注入哪一个的情况,Spring容器在启动时也会抛出BeanCreationException。这个时候可以配合@Qualifier使用,@Qualifier告诉spring具体去装配哪个对象。
当使用@AutoWired注解的时候,自动装配的时候是根据类型(在上面的例子中,即类型为UserDao的类)来装配的:
如果只找到一个UserDao类型的类,则直接进行赋值;
如果没有找到UserDao类型的类,则直接抛出异常;
如果找到多个UserDao类型的类,那么会按照变量名(此处的变量名为类名首字母小写)作为id继续匹配:
1)匹配上直接进行装配;
2)如果匹配不上则直接报异常。
还可以不使用变量名,使用@Qualifier注解来指定id的名称,当使用@Qualifier注解的时候也会有两种情况:
1)找到,则直接装配;
2)找不到,就会报错。
@Qualifier和@Autowired结合使用的示例:
public class UserService {
@Autowired
@Qualifier(name="userDao1")
private UserDao userDao;
}
4.3.2 @Resource注解
@Resource注解也可以完成自动注入,其自动注入规律:
如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
如果指定了type,则从上下文中找到类似匹配的唯一Bean进行装配,找不到或是找到多个,都会抛出异常。
如果既没有指定name,也没有指定type,则自动按照byName方式进行装配。如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。
@Resource的使用示例:
public class User {
//优先注入name为"cat2"的Bean
@Resource(name = "cat2")
private Cat cat;
//其他代码
}
@Resource在Jdk11中已经移除,如果要使用,需要单独引入jar包。示例:
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
4.3.3 @Autowired和@Resource之间的区别
1、@Autowired
@Autowired为Spring提供的注解。
@Autowired默认按类型装配,默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如果想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用。示例:
public class TestServiceImpl {
@Autowired
@Qualifier("userDao")
private UserDao userDao;
}
2、@Resource
@Resourc是jdk提供的注解。
@Resource默认按照ByName自动注入。示例:
public class UserService {
@Resource
private UserDao userDao;
@Resource(name="studentDao")
private StudentDao studentDao;
@Resource(type="TeacherDao")
private TeacherDao teacherDao;
@Resource(name="manDao",type="ManDao")
private ManDao manDao;
}
3、总结:
@AutoWired @Resource
来源 spring Jdk
默认的Bean匹配方式 按类型 按名称
使用范围 只适合spring框架 扩展性更好
五、在Spring中创建Bean的一些相关问题
5.1 Bean的作用域
当定义一个Bean在Spring里,我们还能给这个bean声明一个作用域。它可以通过Bean定义中的scope属性来定义。Spring框架支持以下五种Bean的作用域:
类别 说明 注解
singleton 默认值 , 容器初始时创建 bean 实例, 在整个容器的生命周期内只创建这一个bean @Scope(“singleton”)
prototype 容器初始化时不创建bean的实例,而在每次请求时都创建一个新的Bean的实例 @Scope(“prototype”)
request 在每一次http请求时会创建一个实例,该实例仅在当前http request有效 @Scope(“request”)
session 在每一次http请求时会创建一个实例,该实例仅在当前http session有效 @Scope(“session”)
globalSession 全局Session,类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义。 @SessionScope
Spring4.x的版本中包含两种作用域:request和session作用域,不过这两种作用域几乎不用,因此在5版本的时候被淘汰了。
在这几类作用域中,最常用的是Singleton和Prototype。一般情况下,对有状态的bean使用prototype作用域,而对无状态的bean使用singleton作用域。
1、Singleton
当一个Bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的Bean实例,并且所有对Bean的请求,只要id与该Bean定义相匹配,则只会返回Bean的同一实例。
该模式在多线程下是不安全的。
当创建容器时,对象就被创建了。只要容器在,对象一直活着。当销毁容器时,对象就被销毁了。一句话总结:单例模式的Bean对象生命周期与容器相同。
示例:
<bean id="hello" class="com.spring.test.Hello" scope="singleton">
<property name="name" value="Spring"/>
</bean>
测试:
public static void test(){
//解析beans.xml文件 , 生成管理相应的Bean对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//getBean : 参数即为spring配置文件中bean的id
Hello hello1 = (Hello) context.getBean("hello");
Hello hello2 = (Hello) context.getBean("hello");
//true
System.out.println(hello1==hello2);
}
2、Prototype
当一个Bean的作用域为Prototype,表示一个Bean定义对应多个对象实例。Prototype作用域的Bean会导致在每次对该Bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的Bean实例。
当使用对象时,创建新的对象实例。只要对象在使用中,就一直活着。当对象长时间不用时,被Java的垃圾回收器回收了。一句话总结:多例模式的bean对象,spring框架只负责创建,不负责销毁。
一般来说,对有状态的bean使用prototype作用域,而对无状态的bean使用singleton作用域。
示例:
<bean id="hello" class="com.spring.test.Hello" scope="prototype">
<property name="name" value="Spring"/>
</bean>
测试:
public static void test(){
//解析beans.xml文件 , 生成管理相应的Bean对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//getBean : 参数即为spring配置文件中bean的id
Hello hello1 = (Hello) context.getBean("hello");
Hello hello2 = (Hello) context.getBean("hello");
//false
System.out.println(hello1==hello2);
}
3、Request
当一个Bean的作用域为Request,表示在一次HTTP请求中,一个Bean定义对应一个实例;即每个HTTP请求都会有各自的Bean实例,而且该bean仅在当前Http Request内有效,当前Http请求结束,该bean实例也将会被销毁。该作用域仅在基于Web的Spring ApplicationContext情形下有效。
示例:
<bean id="loginAction" class="cn.csdn.LoginAction" scope="request"/>
4、Session
当一个Bean的作用域为Session,表示在一个HTTP Session中,一个Bean定义对应一个实例。每一次Session请求创建新的实例,而不同的实例之间不共享属性,且实例仅在自己的Session请求内有效,请求结束,则实例将被销毁。该作用域仅在基于Web的Spring ApplicationContext情形下有效。示例:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
5、Global Session
当一个bean的作用域为Global Session,表示在一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于Web的Spring ApplicationContext情形下有效。
默认的Spring Bean 的作用域是Singleton。使用Prototype作用域时,频繁创建和销毁Bean会带来很大的性能开销。
5.2 实体类常用的注解
主要用到的插件是Lombok,使用Lombok注解,目的和作用就在于不用再去写经常反复去写的(如Getter,Setter,Constructor等)一些代码。常用的注解有以下几个:
@Data
使用这个注解,就不用再去手写Getter、Setter、equals、hasCode、toString等方法,注解后在编译时会自动加进去。
@AllArgsConstructor
使用后添加一个构造函数,该构造函数含有所有已声明字段属性参数
@NoArgsConstructor
使用后创建一个无参构造函数
@Builder
关于Builder较为复杂一些,Builder的作用之一是为了解决在某个类有很多构造函数的情况,也省去写很多构造函数的麻烦,在设计模式中的思想是:用一个内部类去实例化一个对象,避免一个类出现过多构造函数,
@Setter/@Getter
为相应的属性自动生成Getter/Setter方法。
@ToString
生成一个toString()方法,默认情况下,会输出类名、所有属性(会按照属性定义顺序),用逗号来分割。@ToString的使用效果示例:
@Override
public String toString() {
return "Square(super=" + super.toString() + ", width=" + this.width + ", height=" + this.height + ")";
}
}
@Data、@AllArgsConstructor、@NoArgsConstructor、@Builder使用示例:
@Data //生成getter,setter等函数
@AllArgsConstructor //生成全参数构造函数
@NoArgsConstructor //生成无参构造函数
@Builder
public class test1 {
String name;
String age;
String sex;
}
@Builder使用示例:
public static void main(String[] args) {
//使用@Builder注解后,可以直接通过Builder设置字段参数
test1 t1=new test1.test1Builder()
.name("wang")
.age("12")
.sex("man")
.build();
System.out.println("name is"+t1.getName()+'\n'+"age is :"+t1.getAge());
}
5.3 @Bean
该注释的属性的名称和语义类似于Spring XML模式中bean的元素的名称和语义。@Bean指示方法产生一个由Spring容器管理的bean。
使用示例:
@Configuration
public class DataBaseConfig {
@Bean("dataSource")
public DataSource getDataSource(){
DataSource dataSource = new DataSource();
dataSource.setUserId("jingsi");
dataSource.setPassword("123456");
dataSource.setUrl("www");
return dataSource;
}
}
@RestController
public class IndexController {
@Autowired
private User user;
@Autowired
private DataSource dataSource;
@GetMapping(value = "index")
public String index(){
return "hello world!"+"dataSource:"+dataSource;
}
}
5.4 Spring框架中的单例Bean是线程安全的吗
Spring框架中的单例Bean不是线程安全的。Spring中的Bean默认是单例模式,但并没有对单例Bean进行多线程的封装处理。
实际上,大部分的Spring Bean并没有可变的状态(比如Service类和DAO类),所以在某种程度上Spring的单例bean是线程安全的。
如果某个Bean有多种状态的话,就需要自行保证线程安全,最简单的解决办法就是将多态Bean的作用域设置为“prototype”。
如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Spring MVC的Controller、Service、Dao等,这些Bean大多是无状态的,只关注于方法本身。
对于有状态的Bean,Spring官方提供的Bean,一般提供了通过ThreadLocal去解决线程安全的方法,比如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等。
有状态对象
就是有实例变量的对象,可以保存数据,是非线程安全的。每个用户有自己特有的一个实例,在用户的生存期内,Bean保持了用户的信息,即“有状态”。一旦用户灭亡(调用结束或实例结束),Bean的生命期也告结束。
无状态对象
就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。Bean一旦实例化就被加进会话池中,各个用户都可以共用。即使用户已经消亡,Bean的生命期也不一定结束,它可能依然存在于会话池中,供其他用户调用。由于没有特定的用户,那么也就不能保持某一用户的状态,所以叫无状态Bean。
5.4 Spring如何处理线程并发问题
在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为Singleton作用域,Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。
ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
5.6 Spring实现单例用到了什么集合对象
Spring对Bean实例的创建是采用单例注册表的方式进行实现的,而这个注册表的缓存是ConcurrentHashMap 对象。
5.7 循环注入/循环依赖
什么是循环注入?举个列子有一个类A,A有一个构造器里面的参数是类B;类B里面有个构造器参数是类C,类C里面有个构造器参数是类A。其实引用循环了A里面有B的引用,B里面有C的引用,C里面又有A的引用。
当用Spring加载A的时候Spring的流程是这样的:
Spring创建类A的实例对象,首先去当前创建池中去查找当前类A的实例对象是否在创建,如果发明没有创建则准备其构造器需要的参数B,然后把创建A的标识放入当前创建池中。
Spring 创建B首先去当前创建池中去查找当前B是否在创建,如果发现没有创建则准备其构造器需要的参数C,然后把创建B的标识放入当前创建池中。
Spring 创建C首先去当前创建池中去查找当前C是否在创建,如果发现没有创建则准备其构造器需要的参数A,然后把创建C的标识放入当前创建池中。
Spring 创建C需要的A,这个时候会发现在当前创建池中已经有A的标识,A正在创建中则抛出 BeanCurrentlyInCreationException。
5.7.1 什么是循环依赖
循环依赖其实就是循环引用,也就是两个或者两个以上的 Bean 互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C依赖于A。
这里不是函数的循环盗用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。
Spring中循环依赖场景有:构造器的循环依赖(构造器注入)、Field属性的循环依赖(set注入)。
构造器的循环注入是没有办法解决的,所以只能避免。因此,不要使用基于构造函数的依赖注入,可以通过以下方式解决:
在字段上使用@Autowired注解,让Spring决定在合适的时机注入;
用基于setter方法的依赖注入。
在解决属性循环依赖时,Spring采用的是提前暴露对象的方法。
5.7.2 循环依赖处理机制
单例Bean构造器参数循环依赖(无法解决)
prototype原型Bean循环依赖(无法解决)
对于原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过setXxx方法产生循环依赖,Spring都会直接报错处理。
AbstractBeanFactory.doGetBean()方法:
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null &&
(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>)
curVal).contains(beanName))));
}
在获取bean之前如果这个原型bean正在被创建则直接抛出异常。原型bean在创建之前会进行标记这个beanName正在被创建,等创建结束之后会删除标记:
try {
//创建原型bean之前添加标记
beforePrototypeCreation(beanName);
//创建原型bean
prototypeInstance = createBean(beanName, mbd, args);
} finally {
//创建原型bean之后删除标记
afterPrototypeCreation(beanName);
}
总结:Spring不支持原型bean的循环依赖。
单例Bean通过setXxx或者@Autowired进行循环依赖
Spring的循环依赖的理论依据基于Java的引用传递,当获得对象的引用时,对象的属性是可以延后设置的,但是构造器必须是在获取引用之前。
Spring通过setXxx或者@Autowired方法解决循环依赖其实是通过提前暴露1个ObjectFactory对象来完成的,简单来说ClassA在调⽤构造器完成对象初始化之后,在调用ClassA的setClassB⽅法之前就把ClassA实例化的对象通过ObjectFactory提前暴露到Spring容器中。
1)Spring容器初始化ClassA通过构造器初始化对象后提前暴露到Spring容器。
2)ClassA调用setClassB方法,Spring先尝试从容器中获取ClassB,此时ClassB不存在Spring容器中。
3)Spring容器初始化ClassB,同时也会将ClassB提前暴露到Spring容器中。
4)ClassB调用setClassA方法,Spring从容器中获取ClassA ,因为第1步中已经提前暴露了ClassA,因此可以获取到ClassA实例。ClassA通过spring容器获取到ClassB,完成了对象初始化操作。
5)这样ClassA和ClassB都完成了对象初始化操作,解决了循环依赖问题。