Spring优点
1) 轻量级开源的javaEE框架,简化企业应用开发复杂性;
2) IoC容器创建管理对象及对象间的依赖关系,降低耦合,方便解耦,简化开发;
3) AOP编程支持,将业务代码和非业务部分隔离开;方便程序测试,方便和其他框架整合;
4)Java源码经典学习范例;
IoC容器
作用:
1) 控制反转:把对象创建和对象之间的调用过程交给Spring管理。
2) 耦合度降低了
Spring Bean
在Spring中,构成应用程序主干并由Spring IoC容器创建管理的对象称为Bean。
Bean是一个由Sping IoC容器实例化、组装和管理的对象。
定义Spring Bean的方式
1) 基于XML的方式
<beans>
<!--导入其他配置文件Bean的定义-->
<import resource=“resource1.xml” />
<import resource=“resource2.xml” />
<bean id="userService" class="com.spring.UserService" initmethod="init" destroymethod="destroy"></bean>
<bean id="message" class="java.lang.String">
<constructor-arg index="0" value="test"></constructor-arg>
</bean>
</beans>
2) 基于注解扫描
这种配置方式主要适用于:在开发中需要引用的类,如Controller、Service、Dao等。
<context:component-scan base-package="com.spring.demo">
<context:include-filter type="regex" expression="com.spring.demo.*"/>//包含的目标类
<context:exclude-filter type="aspectj" expression="com.spring.demo..*Controller+"/>//排除的目标类
</context:componen-tscan>
3) 基于Java配置
可以通过代码控制对象创建,实现零配置,消除XML配置文件。
@Configuration
public class BeansConfiguration {
@Bean
public Student student(){
Student student = new Student();
student.setName("张三");
student.setTeacher(teacher());
return student;
}
@Bean
public Teacher teacher(){
Teacher teacher = new Teacher();
teacher.setName("Tom");
return teacher;
}
}
上面配置含义如下:
a. 在类上配置@Configuration注解,表示此类将定义Bean的元数据
b. 在方法上使用@Bean注解,方法名默认就是Bean的名称,方法返回值就是Bean的实例
c. 通过AnnotationConfigApplicationContext或子类来启动Spring容器,从而加载这些已经声明好的注解配置
Spring容器如何加载Bean?
首先,Spring解析Bean配置信息,然后将配置信息转化为BeanDefinition对象,BeanDefinition保存了配置的所有内容,再将BeanDefinition存入BeanDefinitionMap中,这个Map以beanName作为key,以BeanDefinition作为value。之后每次获取bean对象,Spring容器就会根据beanName去Map中取对应的BeanDefinition对象。
Spring Bean的定义包含哪些内容?
1) Spring Bean声明式配置内容
Spring Bean的配置内容有很多,主要有下面九个关键配置属性:class,scope,lazy-init,depends-on,bean-name,constructor-arg,properties,init-method,destroy-method。
这些属性都是在Spring配置文件中声明的内容,在Spring容器启动后,这些配置内容都将映射为BeanDefinition对象,然后将BeanDefinition对象保存到BeanDefinitionMap容器中,这个Map容器以beanName作为key,以BeanDefinition作为value。这样Spring容器创建对象时,就不需要再读取和解析配置文件,直接根据beanName就可以拿到对应的BeanDefinition对象,然后根据配置信息进行对象的实例化。
2) 配置文件与BeanDefinition映射关系
配置文件九个关键配置属性:class,scope,lazy-init,depends-on,bean-name,constructor-arg,properties,init-method,destroy-method,从下图可以看出,配置文件配置属性与BeanDefinition对象属性一一对应。
lazy-init:用于指定Bean实例是否延时加载,默认false,也就是说容器启动时就会创建Bean的实例,如果设置为true,那么只有在首次获取Bean时才会创建。
depends-on:用于定义Bean实例化的依赖关系,所依赖的Bean需要提前实例化;这里定义的数组类型,所以可以同时定义多个依赖对象,并且可以定义依赖顺序。
factoryBeanName:beanName,Bean的唯一标识,且不能以大写字母开头;如果没有设置,Spring默认使用类名首字母小写作为唯一标识。
3) Spring如何解析配置文件
Spring容器启动后,会调用BeanDefinitionReader工具类的loadBeanDefinitions()方法,对配置文件进行加载和解析。BeanDefinitionReader的主要作用是读取Spring配置文件中的内容,将其转换为BeanDefinition对象。BeanDefinitionReader有多个实现类,分别对应不同类型的配置,如:读取XML文件的XmlBeanDefinitionReader;读取属性文件的PropertiesBeanDefinitionReader;读取Groovy语言定义的GroovyBeanDefinitionReader。
为什么Spring中每个Bean都要定义作用域?
1) Spring Bean的作用域有哪些
在Spring配置中,通过Definition对象中的scope属性来定义Bean的作用域,有5个作用域:
a、singleton(默认):定义一个Bean为单例,在Spring IoC容器中只有唯一的实例,作用域范围是ApplicationContext容器
b、prototype:定义一个Bean为多例,即每次请求获取Bean都会重新创建实例,作用域是getBean方法直至获取对象
c、request:定义Bean的作用范围仅在request中,即每次Http请求会创建一个实例,该实例只在当前request中有效,作用域范围是每次发起HTTP请求直至拿到结果
d、session:定义Bean的作用范围仅在session中,即每次Http请求会创建一个实例,该实例在当前会话session中有效,作用域范围是浏览器首次访问直至浏览器关闭
e、globalsession:全局,该实例仅存于WebApplicationContext中,作用域范围是WebApplicationContext容器
2) Spring为什么定义作用域?
定义Bean的作用域,那么用户可以通过配置的方式设置Bean何时被创建,以及Bean的作用范围,从而起到保护Bean的作用。
Spring中的Bean是线程安全的吗?
1) Spring中的Bean从何而来?
除了Spring框架内置Bean之外,其他Bean都是由我们自己通过Spring配置来声明的,Spring根据我们声明的配置信息(如:class,scope,lazy-init,depends-on,bean-name,constructor-arg,properties,init-method,destroy-method)进行解析转换成BeanDefinition,最终创建实例,由Spring IoC容器管理。所以说,Bean是否线程安全和Spirng容器无关,而是取决于我们声明的配置(如scope作用域)。
2) Spring中什么样的Bean存在线程安全问题?
线程安全问题和我们声明的Bean的作用域有直接关系,prototype多例Bean是每次请求都会创建新的实例,所以互不影响,不存在线程安全问题。
而singleton单例Bean是容器内唯一实例,所有线程共享,则可能存在线程安全问题,这取决于Bean是否有状态,如果是有状态的Bean,则会存在线程安全问题,无状态的Bean不存在线程安全问题。
有状态的Bean:在多线程操作中如果需要对Bean中的成员变量进行数据更新,这样的Bean是有状态的。
无状态的Bean:在多线程操作中,只会对Bean的成员变量进行查询操作,不修改成员变量的值,这样的Bean是无状态的。
3) 如何处理Spring Bean的线程安全问题?
a、多例Bean没有线程安全问题,单例Bean可能存在线程安全问题,那么将单例Bean改成多例Bean即可
b、避免将和会话有关的可变变量声明在单例Bean中,从而让单例Bean成为无状态的Bean
c、如果单例Bean一定要有会话相关的可变变量,那么可以定义为ThreadLocal类型,将会话相关的可变变量保存在ThreadLocal中,ThreadLocal中有一个单独的空间ThreadLocalMap,是线程之间隔离的,每个线程只能操作自己空间
d、在单例Bean中针对可变变量添加DCL双重检查锁,当一个会话需要修改值时,先抢占锁再做修改
Spring Bean的生命周期全过程
1) 创建前准备阶段
Bean加载之前,从Spring上下文和配置中获取Bean配置信息;如init-method(容器在初始化bean时调用的方法),destroy-method(容器在销毁实例时调用的方法)。还有实例化BeanFacoryPostProcesser和BeanPostProcessor,在bean加载过程中的前置和后置处理。这些类是Spring提供给开发者用于做扩展的,很多和Spring集成的中间件都会用到。
2) 创建实例阶段
通过反射来创建Bean的实例,并扫描和解析Bean的属性。
3) 依赖注入阶段
如果Bean存在其他依赖(depends-on配置有依赖关系),就需要对这些被依赖的Bean进行注入。比如通过@Autowired,@Resource等依赖注入的注解配置。注入过程中会调用BeanNameAware的setBeanName()方法给Bean设置name,调用BeanFactoryAware的setBeanFactory()方法创建Bean的Facotory,然后通过BeanFactory创建Bean实例。
此阶段还会触发一些扩展方法的调用,比如BeanPostProcessor,InitializingBean的afterPropertiesSet()方法给属性赋值,还有BeanFactoryAware等。
4) 容器缓存阶段
这个阶段把Bean保存到IoC容器中缓存起来,此时的Bean可以被使用了。此时Bean属性配置的init-method方法会被调用,BeanPostProcessor的初始化后后置处理器方法postProcessorAfterInitialization()会被调用。AOP正是通过此后置处理器实现。
5) 销毁实例阶段
将销毁Spring上下文的所有Bean,Bean中配置的destroy-method方法会被调用。
Spring为什么需要三级缓存解决循环依赖,而不是二级缓存?
循环依赖就是两个或多个对象之间互相持有对方的引用,因为 Spring 加入了依赖注入机制,自动给属性赋值,所以在Spring给属性赋值时会出现死循环,一般有三种循环依赖:自我依赖,相互依赖,循环依赖。
针对循环依赖,spring设计了一级二级三级缓存,三级缓存用来存储代理Bean或者FactoryBean,当调用getBean()时,如果一级二级缓存没有,会通过三级缓存拿到代理工厂去创建bean,然后将创建的实例进行属性赋值,依赖注入,最终保存到一级缓存中。
但是针对构造器注入的bean,实例是非单例的bean,无法解决循环依赖问题。
Spring AOP
Spring AOP原理
大体来说,AOP的底层实现是通过动态代理。接下来分四个阶段详细介绍:
1) 创建代理对象阶段
在Spring中创建bean实例都是从getBean()开始的,在创建bean实例之后,Spring容器根据AOP配置去匹配目标类的类名,如果目标类满足切面规则,就会调用ProxyFactory创建代理bean缓存到IoC容器中。根据目标类的配置选择不同的代理策略,如果目标类实现了接口,Spring会默认选择JDK Proxy,否则调用Cglib Proxy,当然也可以通过配置强制使用Cglib Proxy。
2) 拦截目标对象阶段
当用户调用目标对象的某个方法时,会被AopProxy拦截,Spring将所有调用策略封装到这个对象中,它默认实现了InvocationHandler接口,也就是调用代理对象的外层拦截器。在这个接口中的invoke()中,会触发MethodInvocation的proceed()方法,在这个方法中会按照顺序执行符合AOP拦截规则的拦截器链。
3) 调用代理对象阶段
Spring AOP拦截器链中的每个元素被命名为MethodInterceptor,其实就是切面配置中的Advice通知,被织入的代码会在这个阶段执行。
4) 调用目标对象阶段
MethodInterceptor接口中也有一个invoke(),在这个invoke()中会反射调用目标对象方法。
总结:
代理对象:由Spring代理策略生成的对象。
目标对象:原对象,封装业务代码的对象。
织入代码:在业务代码之外,需要前后加入的其他非业务代码。
切面通知:封装织入代码片段的回调方法。
MethodInvocation:负责执行拦截器链,在proceed()中执行。
MethodInterceptor:负责执行织入的代码片段,在invoke()中执行。
Spring MVC
Spring MVC执行流程
1) 配置阶段
完成对xml和注解的配置。首先从web.xml开始,配置DispatcherServlet的url匹配规则和Spring配置文件的加载路径。然后配置注解,通过@Controller、@Service、@Repository、@Autowired、@RequestMapping等注解完成用户请求和对应方法的匹配处理。
2) 初始化阶段
加载并解析配置信息以及IoC容器、DI操作和HandlerMapping的初始化。具体步骤如下:
首先,web容器启动以后,会自动调用DispatcherServlet的init()方法。
然后,在init()中,会初始化IoC容器,其实就是个Map。
紧接着,根据配置扫描包路径,扫描出相关的类,通过反射创建实例存入IoC容器中。
再接着,Spring迭代扫描IoC容器中的实例,给需要自动赋值的属性自动赋值,即加了@Autowired或@Resource的属性。
最后,读取@RequestMapping注解,将url和方法的映射关系缓存起来,可以简单看成是一个Map,key是url,value是对应的方法。
3) 运行阶段
用户发送请求,web容器的DispatcherServlet会拦截请求,并调用doGet()或doPost(),从方法中会有两个对象request和response,request对象中携带有用户的请求信息,从request中拿到url,通过HandlerMapping找到对应的处理方法Method,然后利用反射调用方法,获得返回结果,并通过response将结果返回给用户。