Spring框架一------Spring
1.SSM框架
●Spring+SpringMVC(model ,view,controller)+Mybatis
●Spring:使用注解和自动装配,即IOC,AOP(业务层)
●SpringMVC:使用MVC模型,将流程控制代码放到controller层处理,将业务逻辑代码放到service层处理(表示层)
●Mybatis:在持久层(Repository)与数据进行交互
2.什么是Spring
●一款轻量级(体积小,引用jar包少)的开源的JAVAEE框架。两个核心IOC和AOP。
3.什么是IOC
●IOC就是控制反转,即将对象的创建以及对象之间的调用过程交给容器来管理,我们只需要在Spring配置文件里配置相应的bean,或者在类上加上相应的注解,并设置相关属性,让Spring容器来生成类的实例对象以及管理对象,这样就不需要自己在类中new对象,而是通过反射来注入,使用IOC可以降低耦合度。
●IOC的底层原理:xml配置文件解析+工厂模式+反射
●SpringIOC的思想基于IOC容器完成,IOC容器的底层就是对象工程,Spring提供两种IOC容器实现方式BeanFactory和ApplicationContext(两个都是接口)。
■BeanFactory:先加载Bean配置文件(不创建对象),并通过xmlBeanFactory去创建工厂Bean,最后通过工厂Bean的getBean()方法得到所需要的bean。使用这个对象的时候才去创建。
■ApplicationContext容器:BeanFactory的子接口,先加载bean配置文件,并使用FileSystemXmlApplicationContext去创建工厂Bean,加载的时候就创建对象了。(一般使用这个进行web开发,将创建对象这种耗时耗资源的操作都在服务器启动前实现比较好)
●DI(依赖注入):与控制反转其实是一个概念,当某个角色需要另外一个角色协助的时候,在传统的程序设计过程中,通常由调用者来创建被调用者的实例,但在Spring中创建被调用者的工作不再由调用者来完成,因此称为控制反转。创建被调用者的工作由Spring来完成,然后注入调用者。例如容器在运行时直接将某个类需要用到的一些对象提供给他。Spring为DI提供了三种方式:
■Setter方法:直接设置属性
■构造方法:使用有参构造注入
■注解方法:(Autowired Resourse value)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gmyiOM82-1650540657599)(http://luxiaolumm.gitee.io/luxiao-lu-mm/pic/148.png)]
●基于注解的Bean管理
■注解扫描器:@ComponentScan(basePackages = 包名)
■@Configuraion配置类注解:完全注解开发,替代xml文件
■对象创建,在类上加入其中一个注解,@Componet,@Service,@Controller,@Reposity。四个功能是一样的,只是为了用在不同层表示当前组件的作用。
■属性注入:
▼@AutoWired:根据属性类型自动装配,如果一个类型只有一个实现实例属性的话,可以用这个
▼@Qualifier:根据名称进行注入,与AutoWired一起使用
▼@Resource:根据名称和类型进行注入(有name属性)
▼@Value:注入基本类型(有value属性)
4.什么是JavaBean
●一种符合特殊规范的类,所有属性都为private;提供默认的构造方法,提供setter和getter;实现serializable接口
5.Bean的生命周期
●Spring根据@ComponentScan找到带有@Componet,@Service,@Controller,@Resposity的类,然后解析并将Bean的定义保存,将bean定义发送到Spring容器中创建bean实例。
●Spring为创建的bean实例设置对象属性(依赖注入)
●将bean实例传递给Bean后置处理器的方法postProcessBeaforeInitialization
●调用bean的初始化方法(需要进行配置初始化的方法)
●将bean实例传递给Bean后置处理器方法postProcessAfterInitialization
●使用bean
●当容器关闭时,调用Bean的销毁方法(销毁方法也需要配置)
6.什么是AOP
(发生时间:有循环依赖就是在bean实例化以后,没有循环依赖就是bean初始化后)
●AOP即面向切面编程,利用了代理模式。AOP是对OOP(Object Oriented Programming 面向对象编程)的补充完善。OOP通过封装、继承和多态建立了一种对象层次结构,但是OOP只允许定义从上到下的关系,不适合定义从左到右的关系。
●AOP通过动态代理的方式,对消息进行截取和装饰,可以将各种交叉业务逻辑封装成一个切面,然后注入到目标对象(具体业务逻辑)中,通俗来讲,就是在不改变源代码的情况下,在主干功能里添加新功能。
●底层原理-动态代理:
■JDK动态代理:通过反射来接受被代理的类,要求被代理的类必须试下一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。Proxy类基于getProxy方法和传递的参数创建动态代理类。InvocationHandler接口则基于invoke()激发动态代理对象的方法。
▼创建被代理对象
▼创建InvocationHandler实现类,重写Invoke方法并封装被代理对象
▼获取被代理对象的实现接口集合以及类加载器
▼使用Proxy类的newProxyInstance(加载器,接口集合,动态代理实例)创建代理
■CGLib动态代理:如果目标类没有实现接口,那么SpringAOP会选择使用CGLib来动态代理目标类。CGLib是一个代码生成的类库,可以在运行时动态的生成某个类的子类(通过修改字节码来实现代理)。CGLib通过集成方式做的动态代理,所以如果某个类被标记为final,那么是无法使用CGLib做动态代理的。
■JDK动态代理比CGLib执行速度快,但性能不如CGLib
■代码实现:https://juejin.cn/post/6844903762025250824#heading-3
●术语:
■连接点:类中哪些方法可以被增强,称为连接点
■切入点:实际被增强的方法,称为切入点
■通知:实际增强的逻辑部分称为通知
▼前置通知:在方法前面执行
▼后置通知:在方法后面执行
▼环绕通知:在方法前后都有
▼异常通知:只有增强的方法出现异常的时候才会执行
▼最终通知:一定会执行
▼切面:一个动作,把通知应用到切入点的过程
●优势:使用AOP可以对业务逻辑的各个部分进行隔离(不用写到一起了),从而使得业务逻辑各个部分之间的耦合度降低,提高程序的可重用性,以及开发效率。
7.SpringAOP和AspectJ AOP的区别
●SpringAOP属于运行时增强,基于代理实现,而AspectJAOP属于编译时增强,基于字节码操作。
●SpringAOP已经集成了AspectJ,AspectJ是Java生态系统中最完整的AOP框架,功能比SpringAOP强大,但是SpringAOP相对简单。
●如果切面较少,两者性能差异不大,如果多的话,AspectJ性能更好。
8.Spring中使用了哪些设计模式
●工厂模式:IOC容器就像是一个工厂,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,不用考虑对象是如何创建出来的。IOC容器负责创建对象,将对象链接到一起,配置这些对象。
■通过BeanFactory创建对象(延迟注入,直到使用某个bean的时候才会注入,启动的时候更快)
■通过ApplicationContext创建对象(容器启动时一次性创建所有bean):实现类,ClassPathXmlApplication,FileSystemXmlApplication,xmlWebApplication。
●依赖注入:是实现控制反转的一种设计模式,依赖注入就是将实例变量传到一个对象中去。
●单例模式:Spring中bean的默认作用域就是singleton(单例)的。可以改为以下几种:
■Prototype:每次请求就会创建一个新的bean实例
■request:每次http请求就会产生一个新的bean,该bean仅在http request内有效
■session:每次http请求就会产生一个新的bean,该bean仅在当前http session内有效。
■global-session:全局session作用域
■为什么要设置成默认单例模式:
▼减少了创建实例产生的资源消耗(创建bean肯定就会消耗资源)
▼减少JVM垃圾回收(因为只有一个)
▼可以快速获取到bean(单例bean会被保存到map中,相当于缓存)
▼缺点:所有请求共享一个bean,而bean的成员变量在堆内存中,不能保证线程安全。
▼缺点的解决:使用ThreadLocal为每个使用bean变量的线程建立变量副本(将request对象放入当前线程的thread的ThreadLocal.threadLocalMap属性中,在请求结束后会销毁ThreadLocal),每个线程可以修改自己内部的副本变量,不会影响到其他线程中副本变量,实现了线程间数据的隔离和数据封闭。
●代理模式:SpringAOP就是基于动态代理的,SpringAOP会使用JDK proxy或者CGLIB实现动态代理,将一些通用的功能抽象出来,在需要使用的地方直接使用。
9.Spring中的事务管理
●事务即Service是真正的业务,比如转账(而不是加钱和扣钱),对应JAVAEE的Service层,而Dao层只是对数据的操作,与业务无关(这里写加钱,扣钱)。
●创建Service,在Service中注入dao,在倒注入JdbcTemplate,在jdbcTemplate中注入DataSource
●@Transactional事务注解,加到类上,则类中所有方法都添加事务,加到方法上则仅为这个方法添加事务。在注解后面可以设置参数来进行配置,以下为常用参数:
■事务隔离级别:isolation
▼READ UNCOMMITTED(读未提交):三个问题都有
▼READ COMMITTED(读已提交):存在不可重复读,幻读
▼REPEATABLE READ(可重复读):存在幻读
▼SERIALIZABLE(串行化):解决了幻读
■事务传播行为:propagation 多个事务相互调用,整个过程如何管理(7种)
▼Required(默认):如当前方法(调用方法)不在事务中就创建一个新事务,如果当前方法在事务中,就加入此事务。
▼Required_news():不管调用方是否有事务,都自身新建事务
▼Supports:如果调用者有事务则加入,如果没有,那也以非事务运行(与不加事务注解一样)
▼Not_supports:一直以非事务形式运行
▼Never:无论谁调用我,你都不能有事务,有就抛异常
▼Mandatory:调用者有事务就加入,没有就抛出异常
▼Nested(嵌套):如果调用者有事务我就创建子事务,父事务提交后,子事务再提交,调用者无事务,就新建事务(子异常,父不一定异常,父异常 ,子一定异常)
■超时时间:timeout 事务需要在一定时间内进行提交否则回滚,默认-1,以s为单位
■是否只读:readOnly 决定事务是否可以增删改查
■回滚:rollbackfor 出现哪些异常就回滚
■不回滚:noRollballFor 设置哪些异常不回滚
●在配置类上使用EnableTransactionManagement开启事务
10.Spring中的过滤器和拦截器
●过滤器(Filter):依赖于Servlet容器,是在请求进入容器之后,还未进入Servlet之前进行预处理,并且在请求结束返回给前端之前进行后期处理。可以对几乎所有请求进行过滤,过滤器实例只能在容器初始化时调用一次。使用过滤器可以取你所想取,忽视不想要的东西,其底层实现方式是基于函数回调的。自定义过滤器需要实现Filter接口,doFilter方法(init和destroy可以不实现,有默认实现)。方法有一个参数filterchain是一个回调接口,基于函数回调实现,并在FilterConfig中通过FilterRegistrationBean配置过滤器。
●拦截器:依赖于Web框架,是一个Spring组件,由Spring容器管理,不仅可以用于web程序,还可以用于Application和swing程序中。实现HandlerInterceptor接口,在preHandler中处理请求,并通过实现WebMbcConfigurer接口,固废addInterceptors方法,在addPathPatterns中设置拦截规则。
●二者区别:
■过滤器依赖于servlet容器,只能在servlet容器中使用,只用于web,拦截器依赖于Spring容器,可以在Spring容器中使用,不单用于web还可以用于Application Swing等
■过滤器控制粒度比较粗,只能在请求进来时进行处理,对请求进行响应和包装;拦截器提供更细粒度的控制,可以在controller对请求处理之前或者之后被调用,也可以在渲染视图呈现给用户后调用。
■中断链执行的难易程度不同:拦截器可以preHandler方法内返回false进行中断;过滤器比较复杂,需要处理请求和相应对象来引发中断,需要额外动作,比如将用户重定向到错误页面。
■过滤器能做的拦截器基本都能做,所以优先使用拦截器。
11.Spring循环依赖
●循环依赖:如果配置了A,B两个Bean对象互相依赖,会抛出StackOverFlowError,因为创建A的时候依赖B,创建B的时候又依赖A,造成死循环。
●Spring通过三级缓存(三个map)和提前曝光机制来解决这个问题:
■一级缓存singletonObject(可以看做单例池):该缓存key = beanName,value = bean;这里的bean是已经创建完成的。该bean经历实例化,属性填充,初始化以及各类的后置处理。所以一旦需要获取bean时,我们第一时间就会找一级缓存。
■二级缓存earlySingletonObject(可以看做是半成品池):该缓存key = beanName,value = bean;这里与一级缓存的区别为该缓存所获取到的bean是提前曝光出来的,是还没有创建完成的,即获取到的bean只能确保完成了实例化,但是属性填充以及初始化还没有完成,因此该bean还没有创建完成,仅仅能作为指针提前曝光,被其他bean引用。
■三级缓存singletoFactories(可以看做是工厂池):该缓存key = beanName, value = beanFactory,在bean实例化后,属性填充以及初始化之前,如果允许提前曝光,spring会将实例化后的bean提前曝光,也就是把该bean转换为beanFactory并加入到三级缓存。在需要引用提前曝光对象时通过singletonFactory.getObject()获取
●获取bean的流程:
■从ObjectFactory(bean工厂)中直接获取
■bean工厂先看SingletoBeanRegistry(一级缓存)中是否有,有就直接返回,没有就进行创建,创建好了丢到单例池中。
●循环依赖问题的解决
■创建bean对象A,实例化后添加到三级缓存中
■属性填充A的过程中需要创建beanB
■B的填充过程中需要A,在一级缓存中没有找到,就去三级缓存中创建,在三级缓存中有添加提前曝光度 A工厂
■创建后完成了B创建的整个步骤,然后将B放入单例池中,并填充作为A的属性
■BeanA的创建完成,将A放入一级缓存中,并删除三级缓存中的A
●AOP代理问题:在创建完成后单例池中保存的是A的代理对象,而B中引用的是半成品池中原本的A对象。二级缓存解决了循环依赖问题但是解决解决不了AOP问题。
●整体流程:
■创建Bean对象,此时属性全为null(只是实例化而已 )
■添加到三级缓存,加进去的是Factory(A),只有在发生提前引用(需要的时候,单例池中没有)的时候,才会发生作用,创建半成品A。
■填充属性,循环依赖情况下,A/B循环依赖。当填充A属性的时候会去获取BeanB,BeanB又需要实例化+属性注入
■B中发现有个属性为A,然后去getBean(A),发现一级缓存中没有
■于是去三级缓存中,拿到了A的ObjectFactory。
■然后调用ObjectFactory的getObject(),获取A的半成品
■然后调用AOP后置处理器的getEarlyBeanReference方法拿到代理后的bean,并且从三级缓存中删除了A的ObjectFactory,然后将代理了不完整A的代理对象放入二级缓存。
■B中属性已经填充完成,且填充的field是代理后的A,称为proxyA。
■B继续其他的后续处理,如果需要的话,在初始化完成后,B会进行AOP动态代理,创建并返回代理类ProxyB
■B处理完成后,被填充到当前A的field中(如果满足切面则填充的是proxyB)
■同时将B或proxyB放到一级缓存中,删除二级三级缓存
■对A进行后置处理,调用aop后置处理器的postProcessAfter
■从二级缓存中获取proxyA并放入一级缓存
●为什么需要二级缓存:如果只有一级缓存的话,根据提前曝光机制,半成品与成品Bean都在一级缓存池中,如果有其他线程去缓存里获取这个bean,则得到的bean可能不完整。
●为什么要三级缓存:如果不考虑AOP代理的话,二级缓存足够了,如果只有二级缓存,在B进行属性填充的时候,就没办法等到A初始化完成后生成Proxy然后再被B的属性引用。所以设置三级缓存,用来存放一个工厂对象ObjectFactory,作用就是在满足切面的时候将AOP提前,也就是将生成ProxyA的过程从初始化A后提前至B的属性填充的时候。
●AOP时机
■没有循环依赖的话就是在Bean初始化完成后创建动态代理
■如果有就是在bean实例化之后创建。
12.Spring异常处理器
●Spring提供一个处理异常的接口:HandlerExceptionResolver可以基于配置处理,下面介绍常用的注解处理
■ControllerAdvice:标记在类上,将当前类表示为处理异常的组件
■ExceptionHandler:标记在方法上,设置标着方法处理的异常,出现这个异常时会调用这个方法。