赞
踩
一句话,Spring是一个轻量级的控制反转(IOC)和面向切面的容器框架。从大小和开销两方面来看,Spring都是轻量级的。Spring通过IOC可以使各个类之间达到松耦合的目的。通过aop可以分离应用的业务逻辑和系统级服务,进行内聚性开发。Spring中还可以对Bean进行一系列的配置和对其生命周期的管理。最后,它还可以组合各种各样的组件,如mybatis,mysql等等,从某个意义来说,就是一种框架,往上面可以轻易地搭各种组件。
与上面bean的创建差不多,但有出入。
①我们通过启动类注解springbootApplication注解获取到需要注入的bean的name和class之后
②通过推断构造方法对某个类进行实例化
③进行属性的注入
④检查是否实现了BeanNameAware,BeanClassloaderAware,BeanFactoryAware接口,采用回调的方式对该bean对象的name,classloader和factory进行设置
⑤如果该容器注册了 BeanPostProcessor,则会调用 postProcessBeforeInitialization() 方法完成 bean 前置处理
⑥如果该 bean 实现了 InitializingBean 接口,则调用 afterPropertiesSet() 方法。或者在这个bean中配置了init_method方法,调用init_method方法。
⑦初始化完成后,如果该容器注册了 BeanPostProcessor 则会调用 postProcessAfterInitialization() 方法完成 bean 的后置处理。通常会在这个步骤进行aop的处理
⑧得到bean对象,放入单例池中,供程序使用
⑨在容器进行关闭之前,如果该 bean 实现了 DisposableBean 接口,则调用 destroy() 方法。或者在bean中配置了destroy_method方法,调用该方法。
⑩结束。
我们先来简单看看Bean生成的流程(与真实的有出入):
假如某个类需要在单例池(Spring容器)中生成对应的bean对象:Spring是先根据反射获取其构造方法构造出该类的一个实例对象,再通过依赖注入它所关联的一些属性。在注入属性之后,Spring会去检查所有方法中有没有@PostConstruct注解,如果有的话,先去执行这个方法(一般这个方法是用于某些没有用@Autowired注解进行自动注入的属性进行赋值),再进行初始化。初始化这个步骤实际上就算去检查该类有没有去实现InitializingBean这一个接口,如果有就会重写afterPropertiesSet()方法,去执行这个方法。然后到初始化后,一般就是去进行aop。检查该类有没有@Aspect切面这个注解,方法中有没有标注切点。如果有,则通过aop生成代理对象,最后把这个代理对象放入单例池中,最终就完成了整个Bean的创建过程。
从WebApplicationContext提取到需要生成Bean的类的名字,由类的名字去通过反射找到该类的构造器,那么构造器的选择就很讲究。当没有显示声明构造方法或者显示声明的构造方法中有无参构造,Spring会去找默认的无参构造方法;但是如果你有显示地声明了一个构造方法时,就用你声明的构造方法。而如果你显示地声明了多个构造方法(其中不包括无参构造),则Spring会因为不知道选用哪个而报初始化异常错误。而如果声明了多个构造方法,可以在你想用的某一个构造方法上加上@Autowired注解,Spirng就可以找到啦!
因为单例池里面是通过ConcurrentHashMap去储存bean的,以beanName为key,bean对象为value。@Autowired是Spring框架提供的注解,其去Spring容器中查找bean,先通过先byType,再byName寻找bean。也就是先通过类型查找bean,如果存在多个相同类型的bean,再根据注入对象的名字去找。如果寻找类型的时候不存在该类型的bean,则报错。
而@Resource是JDK提供的注解,可以通过其指定的name属性去单例池中寻找bean对象,找到即注入;找不到就抛异常。如果没有指定name属性,则按照注入对象的名字作为name去查找,如果没有则按照类型查找。简单理解为先ByName,再ByType。
①属性注入 : 通过set方法或者是注解,如@Autowired,@Resource的方式去对对象的某个属性进行赋值。属性注入要求bean提供一个默认的构造函数,并且得为需要注入的属性提供set方法。
②构造器注入 :多是通过编写web.xml的配置文件去进行注入。在Spring没有解决循环依赖的时候,推荐使用构造器注入。
③工厂方法注入 : 也是在web.xml配置文件处下手。
单例bean指的是通过相同的名字(beanName)去查找对应的同一个bean对象。而相同类型的bean是可以存在多个(可以通过Autowired注入多个不同名字同一类型的bean对象)的。即Spring是没有在类层面去做一个单例。
而我们常说的单例模式指的是一个类中只会创建同一个实例对象。
普通对象是通过构造方法构造出来的对象。
Bean对象是普通对象通过一系列初始化操作之后得到的对象。存放于单例池中。
如果是基于cglib动态代理的话,是基于父子类的。Spring如果要帮我们代理某个类,假设是UserService类,则会先帮我们新建一个代理类,继承UserService , 然后在这个代理类里面设置一个 属性储存UserService的普通对象,再重写被调用的方法。当程序调用该方法时,会去到代理类也就是子类里面调用被重写的方法(基础!)。该被重写的方法的实现是 先去切面里面调用@Before注解里面的方法,再调用普通对象的方法,再调用切面里面调用了@After注解的方法。
如果是JDK动态代理,是基于实现接口的。同理,实现接口就需要重写对应的方法,代理类里面同样是有普通对象。
如果要在Spring里面使用事务,首先得在主程序中加上@EnableTransactionManager和@Configuration两个注解。然后在需要使用事务的方法上加上@Transactional注解。同样,在使用事务的时候,会产生一个代理类,当检查到调用的方法有@Transactional注解的时候,Spring会新建一个数据库连接,以下的所有sql语句都在这个连接下进行。然后把数据库自动提交给关闭(set autocommit = false),然后执行所有sql语句,当中间出现异常的时候,进行回滚;没有异常则执行成功,执行commit方法。
什么叫同类方法的调用为什么会事务失效? 就是在一个标注了@Transactional方法里面,也就是一个事务里面,调用了同一个类的其他方法,其他方法里面出错不会影响到这个事务。
为什么? 这里有两个点。一个是事务里面的方法或者是被调用的方法出现了受检异常,而受检异常是一定要利用try catch机制进行捕获或者是throws Exception,导致异常无法从方法中抛出,Spring无法捕获该异常,事务就不会进行回滚。第二个点是当调用的其他方法上有出现某些注解的时候,由于执行的时候是普通对象在执行其他方法,所以是识别不了注解的,只有代理对象才能识别注解。
其实就是保证我jdbcTemplate和TransactionManager中的dataSource是同一个对象。因为之前事务那里说过,开启一个事务的时候,是回去新建一个数据库连接,且把数据库连接中的autocommit属性设为false,再去执行代码。在Spring中,是如何储存这个数据库连接的呢?在每个线程中,都会使用ThreadLocal对象进行储存,在里面储存一个map,key为datasource,value为数据库连接。所以,如果jdbcTemplate和TransactionManager中是同一个dataSource对象,则会取到同一个数据库连接。如果不是,则取不到,jdbcTemplate就会去新建一个数据库连接,且不会把autocommit属性设会false,则达不到事务原子性的特定。
循环依赖即我在通过构造方法构造某一个类(A类)的普通对象的时候,需要依赖另一个类(B类)的对象。而当单例池中没有另一个类(B类)的对象的时候,就会去创建另一个类(B类)的对象。在创建另一个类(B类)的对象的时候,又需要之前那个类(A类)的对象,然后进入死循环。
在Spring框架中,会使用三级缓存来解决循环依赖问题。
我们再来回顾Spring中一个Bean的创建过程:
①通过构造函数构造出一个普通对象
②给对象的属性赋值
③初始化前的操作
④初始化
⑤初始化后的操作(一般为aop)
⑥将初始化后的代理对象放入单例池中。
可以看出来,这里是在第②步会出现问题。为了解决这个循环依赖问题,Spring对Bean的创建过程进行了优化。优化后如下:
①把需要创建的bean的名字放入一个set(命名为creatingSet)中,代表现在正在创建该bean。
②通过构造函数构造出一个普通对象。
③把该普通对象的名字与该普通对象以及一些跟该对象有关的定义放入一个map中,key为对象的名字,value为一个包含普通对象和对象定义的lambda表达式。
③填充属性。去**单例池(一级缓存)中找该属性的bean,如果单例池中没有该属性对象,则先去查看creatingSet,如果set中存在该bean的名字,则说明该属性的bean正在创建中,出现了循环依赖。一旦出现了循环依赖,则会去earlySingleObjects(二级缓存)查找有无该对象,如果存在该对象,直接返回。如果还是没有,则去singletonFactories(三级缓存)**里面通过第③步骤的map,拿出那个lambda表达式,执行表达式得到一个对象。(如果该对象需要进行aop,则生成的是代理对象,如果不需要进行aop,则生成的是普通对象),得到这一个对象之后,把该对象放入earlySingleObjects(二级缓存中)。
④初始化前的操作
⑤初始化
⑥初始化后的操作(如aop)
⑦从二级缓存中取出代理对象,从creatingSet中删除对应的bean名字,把代理对象放入单例池。
从上面的解决方案来看,我们来分析这三级缓存分别起到了什么作用。
一级缓存:就是单例池,作用就是储存bean。
二级缓存earlySingleObjects:该缓存是为了当出现循环依赖的情况下,创建了多个代理对象的问题。假如A 类在创建bean的时候,需要给B类 和 C类两个属性注入bean,而这两个类刚好对A类有着循环依赖的问题。则进行bean创建过程中的第三步的时候,都会根据lambda表达式进行A类对象创建,则出现了多个A类对象,这是我们不希望出现的。A类的bean应该是单例的。
三级缓存singletonFactories:真正解决循环依赖的,起到关键作用的应该是这级缓存。因为它解决了A类对象给B类对象赋值,B类对象又需要给A类对象赋值时,A类对象找不到的情况,如果A类对象找得到就解决问题了。三级缓存就是做这个事情,把A类的普通对象以及跟对象定义相关的东西给保存下来了,当需要的时候,可以根据实际情况创建A类对象。
图片来源:http://c.biancheng.net/spring_mvc/process.html
SpringMvc的执行流程:主要涉及到DispatcherServlet、HandlerMapping、HandlerAdapter以及ViewResolver类。
1.首先用户点击某个请求路径,发出一个Http请求到DispathcherServlet类中。
2.由DispatcherServlet请求一个或者多个HandlerMapping,HandlerMapping根据url找到具体的处理器,生成并返回一个执行链(包括处理器对象和处理器拦截器)。
3.将该执行链发向HandlerAdapter进行处理,如:参数封装,数据格式转换,数据验证等操作。
4.HandlerAdapter根据Handler的信息找到对应的Controller,执行对应的方法
5.Controller里的方法执行完毕返回一个ModelAndView对象给HandlerAdapter,并将其发会给DispatcherServlet。
6.DispatcherServlet收到ModelAndView对象后,会请求ViewResovler进行解析,得到View视图。
7.DispatcherServlet收到View视图后,进行渲染,将Model中的数据填充到View视图中的request域中,生成最终的view视图
8.浏览器呈现视图。
DispatcherServlet是SpirngMVC和SpringBoot里面比较核心的一个类。我们在浏览器中输入一个URL,是如何到达我们Controller类里面的某个方法上的呢?DispatcherServlet起到了重要作用。
如这个URL:http://localhost:8080/demo1/login/toLogin
我们通常要先启动Tomcat,启动Tomcat之后,会先去解析web.xml文件,该文件中会对DispatcherServlet进行配置,进行实例化,实例化之后,就会执行DispatcherServlet的初始化方法,在这个方法里面就会去对webApplicationContext属性进行配置,也就是配置Controller Bean所在的类。这样,DispatcherServlet里面就能找到Controller的所有的bean。回到URL来,8080是Tomcat的端口,所以8080之前是为了找到Tomcat,然后demo1对应的是在Tomcat中运行的DispatcherServlet,根据login就能找到对应的Controller Bean,然后toLogin就可以执行对应的方法。
假如需要调用A方法,而Spring检测到A方法上有@Transactional注解,Spring会给这个方法创建一个事务,而如果在事务执行的过程中,调用了另一个方法(另一个事务),这就叫事务传播。
Spring给事务传播定义了7个特性:
①Required (Spring默认的事务传播机制): 顾名思义,需要。就说明如果在一个事务中调用其他的方法,如果当前没有事务,则新建一个事务,如果当前存在事务,则加入该事务。
②Surpport : 顾名思义,支持事务,但是没必要。就说明如果当前没有事务,就可以以非事务的形式运行,有事务就加入当前事务。
③Mandatory : 顾名思义,强制性的。就说明如果当前有事务,就加入该事务。如果没有事务,就抛出异常。
④Requires_New : 顾名思义,需要新的。就说明如果当前有事务,则还是新建一个事务,在新的事务里面运行。
⑤Not_Surpported : 顾名思义,不支持事务。就说明如果当前有事务,我不需要这个事务,我自己以非事务的形式运行。
⑥Never : 顾名思义,从不使用事务。就说明,如果当前有事务,就抛出异常。如果当前每事务,以非事务的形式运行。
⑦Nested : 顾名思义,嵌套事务。就说明,如果当前有事务,则生成一个子事务在当前事务中。如果当前没事务,则新建事务。
在只使用Spring框架的时候,启动一个Spring程序往往需要编写很多的xml配置文件,才可以启动。而使用Springboot框架后,只需要在主程序中添加@SpringbootApplication注解且引入第三方对应的starter jar包和编写一些简单的yaml配置文件即可以启动springboot程序。谈到自动装配就得提到开箱即用与约定大于配置这两个特点。我们就从springbootApplication注解出发。这个注解是由3个注解组合而成的:@Configuration,@EnableAutoConfiguration和@ComponentScan。
@ComponentScan注解:扫描被@Component
(@Service
,@Controller
)注解的 bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些 bean。
@Configuration注解:首先代表自己是Spring的一个配置类,在此类下被@Bean注解标注的方法或变量将会被注册到IOC容器中。
EnableAutoConfiguration注解(核心):这一个注解加载EnableAutoConfigurationImportSelector这一个类,这一个类继承了AutoConfigurationImportSelector类,这一个父类实现了ImportSelector接口,重写了selectImports()方法,这一个方法中首先判断自动装配开关是否打开,如果打开的话,我们通过这一个方法将所有的 Spring Boot Starter 包下的META-INF/spring.factories文件里面的都是一些需要导入配置的类的全限定名,且并不是把这个文件上的所有类都导入,只有符合@ConditionalXX注解里面的条件满足才会被导入。
拓展1:如何让Springboot通过我们在application.yaml文件配置的属性对相关的类进行配置呢?
在spring.factories文件中被导入的类中会有@ConfigurationProperties注解,该注解就标明了在application.yaml文件中以该字符串为前缀的属性值将会注入到该类中。
拓展2:如何自定义实现一个starter?
创建一个工程,引入springboot依赖,创建一个标有@Configuration注解的配置类,通过@Bean注解标注你想要注入的bean对象(如果在方法上,则方法名是bean的名称,返回值是bean的对象),最后一步,工程的 resources 包下创建META-INF/spring.factories
文件,里面含有配置类的全限定名。
AOP 就是基于Spring框架的应用中,存在着很多的功能模块,而这些功能模块除了要实现它自身的业务逻辑,如查询用户余额等功能外,我们还想要它能实现一些额外的功能,如打印日志、安全管理等系统功能。那么我们就可以把这些额外的功能包装成一个切面,注入到切点中,即这些业务逻辑功能中。总结就是aop能够对业务方法进行增强,在业务方法之前做些事情,在业务方法后做些事情。
IOC : Inversion of Control ,控制反转。那么IOC是如何实现控制反转的呢?
首先IOC的核心是 它实现了一个容器,容器里面装载着程序启动后,通过读取web.xml文件获取的bean对象以及程序中标有@Controller、@Service、@Repository等等类,将这些对象存入一个map容器中。当某个类需要其他类的对象时,可以从这个map中 直接通过对象名进行获取对象。
那么具体什么是控制反转呢? 就是原来没有IOC机制的时候,某个类中给某个属性对象赋值,是需要我们程序员手动去new一个对象,用构造方法去传值,控制权是在我们的手里。而现在有了IOC后,某个类需要给某个属性对象赋值的时候,可以直接去IOC容器中找需要的bean,现在返回什么样的bean,什么类型的bean就完全由Spring去掌握了,控制权在Spring中。此为控制反转。
①单例模式:
Spring中的bean默认作用域就是singleton都是单例的。
②代理模式:
Spring AOP主要是基于动态代理实现的,如果要代理的类,实现了某个接口,则使用JDK动态代理,如果没有实现接口则使用Cglib动态代理。
③工厂模式:定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。在Spring中BeanFactory是一个创建bean对象的接口,其定义了getBean(),containsBean()等很多方法,我们只需要让所有的Bean对象都去实现BeanFactory这个接口,重写getBean()的方法,这样,当Spirng调用不同的Bean对象的getBean()方法时就会取得不同的对象,那Spring是如何获取不同的Bean对象呢? 是通过反射机制,通过class.forName(String beanName)获取bean对象所在的类。
④模板方法模式:
模板方法模式的简单介绍:在抽象类中定义了一个算法的轮廓或者框架,它基本上是由模板方法和若干个基本方法构成。
模板方法:定义了按某种顺序调用基本方法。
基本方法:大体上包括抽象方法和具体方法:抽象方法在抽象类中声明,在子类中实现;而具体方法是在抽象类中声明且实现,子类可以继承这个方法或者重写它。
Spring中的事务管理器就运用模板模式的设计。其中在抽象类中声明了提交与回滚的方法,而具体实现得看你使用了JdbcTemplate还是Hibernate的框架,实现不同的doCommit() 和 doRollback()方法。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。