Spring

作者: 机智的柠檬 | 来源:发表于2020-03-20 11:43 被阅读0次

    Spring常见面试题整理:https://blog.csdn.net/a745233700/article/details/80959716?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
    本文主要包括四节内容:

    • Spring概述
    • Spring IOC(控制反转) 和 DI(依赖注入)
    • Spring AOP(面向切面编程)
    • 其他面试问题

    一、Spring

    1.概念

    Spring是一个轻量级的IoC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。常见的配置方式有三种:基于XML的配置、基于注解的配置、基于Java的配置。

    主要由以下几个模块组成:

    Spring Core:核心类库,提供IOC服务;

    Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等);

    Spring AOP:AOP服务;

    Spring DAO:对JDBC的抽象,简化了数据访问异常的处理;

    Spring ORM:对现有的ORM框架的支持;

    Spring Web:提供了基本的面向Web的综合特性,例如多方文件上传;

    Spring MVC:提供面向Web应用的Model-View-Controller实现。

    2.优点

    (1)spring属于低侵入式设计,代码的污染极低;

    (2)spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性;

    (3)Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。

    (4)spring对于主流的应用框架提供了集成支持。

    二、Spring IOC

    Spring IOC和DI用法详解:https://blog.csdn.net/qq_43154385/article/details/85160684
    https://blog.csdn.net/qq_43154385/article/details/85161945

    1.概念

    IOC概念:将对象的创建权交给Spring

    2.Bean的作用域

    ① singleton: 单例模式,Spring IoC 容器中只会存在一个共享的 Bean 实例,由自身的BeanFactory来维护
    ② prototype: 每次通过 Spring 容器获取 prototype 定义的 bean 时,容器都将创建一个新的 Bean 实例
    ③ Request: 在一次 Http 请求中,容器会返回该 Bean 的同一实例。在请求完成后,该实例会失效并被垃圾回收器回收
    ④ Session: 在一次 Http Session 中,容器会返回该 Bean 的同一实例。在Session过期后,Bean 会失效
    ⑤ global: 在一个全局的 Http Session 中,容器会返回该 Bean 的同一个实例

    3.Bean的生命周期

    ①实例化Bean
    对于BeanFactory容器,当客户向容器请求一个尚未初始化的Bean时,或初始化Bean的时候需要注入另一个尚未初始化的依赖时,容器就会自动调用creatBean进行实例化。
    对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的Bean。
    ②DI(依赖注入)
    实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring通过BeanDefinition中的信息,以及通过BeanWrapper提供的设置属性的接口完成依赖注入。
    ③处理Aware接口
    接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean:

    • BeanNameAware接口
      如果这个 Bean 已经实现了 BeanNameAware 接口,会调用它实现的 setBeanName(String)方法,此处传递的就是 Spring 配置文件中 Bean 的 id 值
    • BeanFactoryAware接口
      如果这个 Bean 已经实现了 BeanFactoryAware 接口,会调用它实现的 setBeanFactory,setBeanFactory(BeanFactory)传递的是 Spring 工厂自身(可以用这个方式来获取其它 Bean,只需在 Spring 配置文件中配置一个普通的 Bean 就可以)。
      -ApplicationContextAware
      如果这个 Bean 已经实现了 ApplicationContextAware 接口,会调用setApplicationContext(ApplicationContext)方法,传入 Spring 上下文(同样这个方式也可以实现步骤 4 的内容,但比 4 更好,因为 ApplicationContext 是 BeanFactory 的子接口,有更多的实现方法)

    ④BeanPostProcessor (postProcessBeforeInitialization 方法)
    如果这个 Bean 关联了 BeanPostProcessor 接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor 经常被用作是 Bean 内容的更改,并且由于这个是在 Bean 初始化结束时调用那个的方法,也可以被应用于内存或缓存技术。
    ⑤InitializingBean 与 init-method
    如果 Bean 在 Spring 配置文件中配置了 init-method 属性会自动调用其配置的初始化方法
    ⑥BeanPostProcessor (postProcessAfterInitialization 方法)
    BeanPostProcessor 经常被用作是 Bean 内容的更改,并且由于这个是在 Bean 初始化结束时调用那个的方法,也可以被应用于内存或缓存技术。

    注意:经过以上几个步骤,Bean 就被正确创建了,之后就可以使用这个Bean 了,此时Bean是单例的。

    ⑦Destroy 过期自动清理阶段
      当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;

    ⑧ destroy()方法
    destroy-method 自配置清理
      最后,如果这个 Bean 的 Spring 配置中配置了 destroy-method 属性,会自动调用其配置的销毁方法。

    image.png

    4、依赖注入的方式

    5、自动装备方式

    在XML中:
    ①no:默认的方式是不进行自动装配,通过显式设置 ref 属性来进行装配。
    ②byName:通过参数名 自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byname,之后容器试图匹配、装配和该 bean 的属性具有相同名字的 bean
    ③byType:通过参数类型自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byType,之后容器试图匹配、装配和该 bean 的属性具有相同类型的 bean。如果有多
    个 bean 符合条件,则抛出错误。
    ④constructor:这个方式类似于 byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常
    ⑤autodetect:首先尝试使用 constructor 来自动装配,如果无法工作,则使用 byType 方式。
    使用注解:
    使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置,<context:annotation-config />。在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:

    如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;

    如果查询的结果不止一个,那么@Autowired会根据名称来查找;

    如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。

    @Autowired可用于:构造函数、成员变量、Setter方法
    
    注:@Autowired和@Resource之间的区别
    
    (1) @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。
    
    (2) @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。
    
    image.png

    6、BeanFactory 与 ApplicationContext的区别

    BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。

    (1)BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
    ①继承MessageSource,因此支持国际化。
    ②统一的资源文件访问方式。
    ③提供在监听器中注册bean的事件。
    ④同时加载多个配置文件。
    ⑤载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。

    (2)①BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
    ②ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
    ③相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。

    (3)BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。

    (4)BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。

    三、AOP

    OOP面向对象,允许开发者定义纵向的关系,但并适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用。

    AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理。

    AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。

    (1)AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。

    (2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

    1、概念

    面向切面编程,"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

    使用"横切"技术,AOP 把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

    2、AOP底层实现原理

    AOP底层采用动态代理来实现
    参考文章:https://blog.csdn.net/flyfeifei66/article/details/81481222

    ①JDK动态代理(只能对实现接口的类产生代理)

    代码示例:

    package com;
    public interface UserDao {  
        void save();
        void find();
        void update();
        void delete();
    }
    
    package com;
    
    public class UserDaoImpl implements UserDao {
    
        public void save() {
            System.out.println("save执行了");
        }
    
        public void find() {
            System.out.println("find执行了");
        }
    
        public void update() {
            System.out.println("update执行了");
        }
    
        public void delete() {
            System.out.println("delete执行了");
        }
    
    }
    
    package com;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class JdkProxy implements InvocationHandler {
        //传入要增强的目标对象类
            private UserDao userDao;    
        public JdkProxy (UserDao userDao) {
            this.userDao = userDao;
        }
            
        public UserDao createProxy() {
            
            UserDao userDaoProxy=(UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), 
                    userDao.getClass().getInterfaces(), this);
                    return userDaoProxy;        
        }
    
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if("save".equals(method.getName())){
                System.out.println("权限校验");
                return method.invoke(userDao, args);
            }
            return method.invoke(userDao, args);
        }
    
    }
    
    ②Cglib动态代理(第三方)

    针对没有实现接口的类 Customer

    package com;
    
    public class Customer {
        public void save() {
            System.out.println("save方法执行了。。。");
        }
        
    }
    
    package com;
    
    import java.lang.reflect.Method;
    
    import org.springframework.cglib.proxy.Enhancer;
    import org.springframework.cglib.proxy.MethodInterceptor;
    import org.springframework.cglib.proxy.MethodProxy;
    
    public class Cglib implements MethodInterceptor{
        //要增强的类
        private Customer customer;
        public Cglib(Customer customer) {
            this.customer = customer;   
        }
        
        public Customer createCglibProxy() {
            //1、创建cglib核心类对象
            Enhancer enhancer = new Enhancer();
            //2、设置父类
            enhancer.setSuperclass(customer.getClass());
            //3、设置回调(类似于InvocationHandler对象)
            enhancer.setCallback(this); 
            //4、创建强转对象
            Customer proxy = (Customer) enhancer.create();
            return proxy;
            
        }
    
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            // TODO Auto-generated method stub
            if("save".equals(method.getName())){
                System.out.println("权限校验");
                return methodProxy.invokeSuper(proxy, args);
            }
            return methodProxy.invokeSuper(proxy, args);
        }
    
    }
    
    

    入门开发

    image.png

    3、相关术语

    image.png

    ① 切面(aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象
      ② 横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点。
      ③ 连接点(joinpoint):被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring
      中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。
      ④ 切入点(pointcut):对连接点进行拦截的定义
      ⑤ 通知(advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、
    异常、最终、环绕通知五类。
      ⑥ 目标对象:代理的目标对象
      ⑦ 织入(weave):将切面应用到目标对象并导致代理对象创建的过程13/04/2018 Page 130 of 283
      ⑧ 引入(introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。

    三、Spring 事务的实现方式及其原理

    Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。

    1.Spring事务的种类:

    spring支持编程式事务管理和声明式事务管理两种方式:

    ①编程式事务管理使用TransactionTemplate。

    ②声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

    声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中。

    声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式,使业务代码不受污染,只要加上注解就可以获得完全的事务支持。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

    2.事务的隔离级别

    ① ISOLATION_DEFAULT:这是个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。
    ② ISOLATION_READ_UNCOMMITTED:读未提交,允许另外一个事务可以看到这个事务未提交的数据。
    ③ ISOLATION_READ_COMMITTED:读已提交,保证一个事务修改的数据提交后才能被另一事务读取,而且能看到该事务对已有记录的更新。
    ④ ISOLATION_REPEATABLE_READ:可重复读,保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新。
    ⑤ ISOLATION_SERIALIZABLE:一个事务在执行的过程中完全看不到其他事务对数据库所做的更新。

    3.事务的传播行为

    • 保证在同一个事务中
      ① PROPAGATION_REQUIRED:默认值,如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
      ② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。‘
      ③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。

    • 保证不在同一个事务中
      ④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
      ⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
      ⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

    • 嵌套式业务
      ⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

    四、其他问题

    1.Spring框架中的单例Beans是线程安全的么?

        Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。但实际上,大部分的Spring bean并没有可变的状态(比如Serview类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。如果你的bean有多种状态的话(比如 View Model 对象),就需要自行保证线程安全。最浅显的解决办法就是将多态bean的作用域由“singleton”变更为“prototype”。
    

    2.Spring如何处理线程并发问题?

    在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。

    ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。

    ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

    3.Spring 框架中都用到了哪些设计模式?

    (1)工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
    (2)单例模式:Bean默认为单例模式。
    (3)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
    (4)模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
    (5)观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现--ApplicationListener。

    4.Spring框架中有哪些不同类型的事件?

    Spring 提供了以下5种标准的事件:
    (1)上下文更新事件(ContextRefreshedEvent):在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。
    (2)上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。
    (3)上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。
    (4)上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。
    (5)请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。
    如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知。

    5.Spring通知有哪些类型?

    (1)前置通知(Before advice):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
    (2)返回后通知(After returning advice):在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
    (3)抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。
    (4)后通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
    (5)环绕通知(Around Advice):包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。 环绕通知是最常用的一种通知类型。大部分基于拦截的AOP框架,例如Nanning和JBoss4,都只提供环绕通知。

    相关文章

      网友评论

          本文标题:Spring

          本文链接:https://www.haomeiwen.com/subject/penryhtx.html