spring

2022-05-06  本文已影响0人  sizuoyi00

1: spring boot 要如何知道我们有哪些AutoConfiguration类呢?
大致的原理:spring boot应用启动时的@SpringBootApplication 注解中
使用了@EnableAutoConfiguration注解;@EnableAutoConfiguration注解通过@import注解将AutoConfigurationImportSelector类实例化,这个类会在selectImports()方法中去读取spring.factories中的配置,确定了哪些类是需要自动装配的。

说说Spring 里用到了哪些设计模式?
单例模式:Spring 中的 Bean 默认情况下都是单例的。无需多说。
工厂模式:工厂模式主要是通过 BeanFactory 和 ApplicationContext 来生产 Bean 对象。
代理模式:最常见的 AOP 的实现方式就是通过代理来实现,Spring主要是使用 JDK 动态代理和 CGLIB 代理。
模板方法模式:主要是一些对数据库操作的类用到,比如 JdbcTemplate、JpaTemplate,因为查询数据库的建立连接、执行查询、关闭连接几个过程,非常适用于模板方法。

谈谈你对IOC 和 AOP 的理解?他们的实现原理是什么?
IOC叫做控制反转,指的是通过Spring来管理对象的创建、配置和生命周期,这样相当于把控制权交给了Spring,不需要人工来管理对象之间复杂的依赖关系,这样做的好处就是解耦。在Spring里面,主要提供了 BeanFactory 和 ApplicationContext 两种 IOC 容器,通过他们来实现对 Bean 的管理。

IOC 的核心:怎么样通过配置文件来启动 Spring 的 ApplicationContext,基于xml启动spring容器;
基于注解,采用 java 配置类和各种注解来配置,启动spring容器

AOP 叫做面向切面编程,他是一个编程范式,目的就是提高代码的模块性,与业务解耦。Spring AOP 基于动态代理的方式实现,如果是实现了接口的话就会使用 JDK 动态代理,反之则使用 CGLIB 代理,Spring中 AOP 的应用主要体现在 事务、日志、异常处理等方面,通过在代码的前后做一些增强处理,可以实现对业务逻辑的隔离,提高代码的模块化能力,同时也是解耦。Spring主要提供了 Aspect 切面、JoinPoint 连接点、PointCut 切入点、Advice 增强等实现方式。

IOC

BeanFactory 简介
BeanFactory,从名字上也很好理解,生产 bean 的工厂,它负责生产和管理各个 bean 实例。
ApplicationContext 其实就是 BeanFactory的子类

FactoryBean:FactoryBean是一个Bean,实现了FactoryBean接口的类有能力改变bean,类似于普通bean的一个装饰器,普通的bean对象可以通过实现FactoryBean接口,对bean进行改造。
在Ioc容器的基础上给Bean的实现增加了一个工厂模式和装饰模式,返回的对象由getObject( )决定。

BeanFactory启动过程
从 ClassPathXmlApplicationContext 的构造方法说起,
// 根据提供的路径,处理成配置文件数组(以分号、逗号、空格、tab、换行符分割)
设置 BeanFactory 的类加载器
初始化所有的 singleton beans

AOP
切面编程,与业务无关

Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象;
对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib ,这时候Spring AOP会使用 Cglib 生成一个被代理对象的“子类”来作为代理

jdk动态代理与CGLIB字节码区别
JDK 动态代理主要是针对类实现了某个接口,AOP 则会使用 JDK 动态代理。他基于反射的机制实现,生成一个实现同样接口的一个代理类,然后通过重写方法的方式,实现对代码的增强。
而如果某个类没有实现接口,AOP 则会使用 CGLIB 代理。他的底层原理是基于 asm 第三方框架,通过修改字节码生成成成一个子类,然后重写父类的方法,实现对代码的增强。

JDK 动态代理和 CGLIB 字节码生成的区别?
(1)JDK 动态代理只能对实现了接口的类生成代理,而不能针对类
(2)CGLIB 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或方法最好不要声明成 final

Spring AOP 和 AspectJ AOP 有什么区别?
Spring AOP 基于动态代理实现,属于运行时增强。
AspectJ 则属于编译时增强,主要有3种方式:

编译时织入:指的是增强的代码和源代码我们都有,直接使用 AspectJ 编译器编译就行了,编译之后生成一个新的类,他也会作为一个正常的 Java 类装载到JVM。
编译后织入:指的是代码已经被编译成 class 文件或者已经打成 jar 包,这时候要增强的话,就是编译后织入,比如你依赖了第三方的类库,又想对他增强的话,就可以通过这种方式。
加载时织入:指的是在 JVM 加载类的时候进行织入。

总结下来的话,就是 Spring AOP 只能在运行时织入,不需要单独编译,性能相比 AspectJ 编译织入的方式慢,而 AspectJ 只支持编译前后和类加载时织入,性能更好,功能更加强大。

①选择spring的AOP还是AspectJ?
spring确实有自己的AOP。功能已经基本够用了,除非你的要在接口上动态代理或者方法拦截精确到getter和setter。这些都是写奇葩的需求,一般不使用。

②在使用AOP的时候,你是用xml还是注解的方式(@Aspect)?
1)如果使用xml方式,不需要任何额外的jar包。
2)如果使用@Aspect方式,你就可以在类上直接一个@Aspect就搞定,不用费事在xml里配了。但是这需要额外的jar包( aspectjweaver.jar)。因为spring直接使用AspectJ的注解功能,注意只是使用了它 的注解功能而已。并不是核心功能 !!!

注意到文档上还有一句很有意思的话:文档说到 是选择spring AOP还是使用full aspectJ?
什么是full aspectJ?如果你使用"full aspectJ"。就是说你可以实现基于接口的动态代理,等等强大的功能。而不仅仅是aspectj的 注-解-功-能 !!!
如果用full AspectJ。比如说Load-Time Weaving的方式 还 需要额外的jar包 spring-instrument.jar
当然,无论是使用spring aop还是 aspectj都需要aspectjweaver.jar spring-aop.jar这两个jar包

举个栗子
一个对象被对个aop拦截,他的代理对象是什么样的
实际上是一个aop代理对象数组,而不是嵌套代理

Spring 中的 bean 生命周期
https://www.jianshu.com/p/1dec08d290c1
SpringBean 生命周期简单概括为4个阶段:

  1. 实例化,创建一个Bean对象
  2. 填充属性,为属性赋值
  3. 初始化

如果实现了xxxAware接口,通过不同类型的Aware接口拿到Spring容器的资源
如果实现了BeanPostProcessor接口,则会回调该接口的postProcessBeforeInitialzation和postProcessAfterInitialization方法
如果Bean实现了InitializingBean接口,执行afterPropertiesSet()方法。
如果配置了init-method方法,则会执行init-method配置的方法

  1. 销毁

容器关闭后,如果Bean实现了DisposableBean接口,则会回调该接口的destroy方法
如果配置了destroy-method方法,则会执行destroy-method配置的方法

  • ApplicationContextAware: 获得ApplicationContext对象,可以用来获取所有Bean definition的名字。
  • BeanFactoryAware:获得BeanFactory对象,可以用来检测Bean的作用域。
  • BeanNameAware:获得Bean在配置文件中定义的名字。
  • ResourceLoaderAware:获得ResourceLoader对象,可以获得classpath中某个文件。
  • ServletContextAware:在一个MVC应用中可以获取ServletContext对象,可以读取context中的参数。
  • ServletConfigAware在一个MVC应用中可以获取ServletConfig对象,可以读取config中的参数。

Spring是怎么解决循环依赖的?
首先,Spring 解决循环依赖有两个前提条件:
不全是构造器方式的循环依赖
必须是单例

答:Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!

基于上面的问题,我们知道Bean的生命周期,本质上解决循环依赖的问题就是三级缓存,通过三级缓存提前拿到未初始化完全的对象。
第一级缓存:存储的是所有创建好了的单例Bean
第二级缓存:完成实例化,但是还未进行属性注入及初始化的对象
第三级缓存:用来保存一个对象工厂,提供一个匿名内部类,用于创建二级缓存中的对象(二级缓存中存储的就是从这个工厂中获取到的对象)

假设一个简单的循环依赖场景,A、B互相依赖。

1.A对象的创建过程:
创建对象A,实例化的时候把A对象工厂放入三级缓存


2.A注入属性时,发现依赖B,转而去实例化B
同样创建对象B,注入属性时发现依赖A,依次次从一级到三级缓存查询A,从三级缓存通过对象工厂拿到A,把A放入二级缓存,同时删除三级缓存中的A,此时,B已经实例化并且初始化完成,把B放入一级缓存。



3.接着继续创建A,顺利从一级缓存拿到实例化且初始化完成的B对象,A对象创建也完成,删除二级缓存中的A,同时把A放入一级缓存
最后,一级缓存中保存着实例化、初始化都完成的A、B对象



因此,由于把实例化和初始化的流程分开了,所以如果都是用构造器的话,就没法分离这个操作,所以都是构造器的话就无法解决循环依赖的问题了。

为什么要三级缓存?二级不行吗?
不可以,主要是为了生成代理对象。
如果要使用二级(1级+3级)缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。

其他解决循环依赖的方法
1.使用 @Lazy
解决Spring 循环依赖的一个简单方法就是对一个Bean使用延时加载。也就是说:这个Bean并没有完全的初始化完,实际上他注入的是一个代理,只有当他首次被使用的时候才会被完全的初始化。
2.使用 Setter/Field 注入
其中最流行的解决方法,就是Spring文档中建议,使用setter注入。
简单地说,你对你须要注入的bean是使用setter注入(或字段注入),而不是构造函数注入。通过这种方式创建Bean,实际上它此时的依赖并没有被注入,只有在你须要的时候他才会被注入进来。
3.使用 @PostConstruct
打破循环的另一种方式是,在要注入的属性(该属性是一个bean)上使用 @Autowired ,并使用@PostConstruct 标注在另一个方法,且该方法里设置对其他的依赖。
4.实现ApplicationContextAware and InitializingBean接口
如果一个Bean实现了ApplicationContextAware,该Bean可以访问Spring上下文,并可以从那里获取到其他的bean;实现InitializingBean接口,表明这个bean在所有的属性设置完后做一些后置处理操作。并手动设置依赖。

SpringMVC 工作原理
1、客户端(浏览器)发送请求,直接请求到 DispatcherServlet。
2、DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。
3、解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由 HandlerAdapter 适配器处理。
4、HandlerAdapter 会根据 Handler来调用真正的处理器开处理请求,并处理相应的业务逻辑。
处理器处理完业务后,会返回一个 ModelAndView 对象,Model 是返回的数据对象,5、View 是个逻辑上的 View。
6、ViewResolver 会根据逻辑 View 查找实际的 View。
7、DispaterServlet 把返回的 Model 传给 View(视图渲染)。
8、把 View 返回给请求者(浏览器)

@Component 和 @Bean 的区别是什么?
1、作用对象不同: @Component 注解作用于类,而@Bean注解作用于方法。
2、@Component通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用 @ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了Spring这是某个类的示例,当我需要用它的时候还给我。
3、@Bean 注解比 Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册bean。比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现。

事务面试题

1.什么情况导致事务失效

2.事务的传播行为

3.事务的隔离级别

详见文章:https://www.jianshu.com/p/9e7a80fbc73a

4.只在public方法上生效?

只有@Transactional 注解应用到 public 方法,才能进行事务管理。这是因为在使用 Spring AOP 代理时,Spring 在调用 TransactionInterceptor 在目标方法执行前后进行拦截之前,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource(Spring 通过这个类获取表 1. @Transactional 注解的事务属性配置属性信息)的 computeTransactionAttribute 方法。

如果想在非public方法上生效,考虑使用AspectJ(织入方式)。

5.回滚规则

默认只把runtime, unchecked exceptions标记为回滚,即RuntimeException及其子类,Error默认也导致回滚。

6.事务注解在类/方法上

如果在接口、实现类或方法上都指定了@Transactional 注解,则优先级顺序为方法>实现类>接口;

建议只在实现类或实现类的方法上使用@Transactional,而不要在接口上使用,这是因为如果使用JDK代理机制(基于接口的代理)是没问题;

而使用使用CGLIB代理(继承)机制时就会遇到问题,因为其使用基于类的代理而不是接口,这是因为接口上的@Transactional注解是“不能继承的”;

7.事务分类

编程式事务简单eg:

@Autowired
    private TransactionTemplate transactionTemplate;

    public void test() {
        //无返回值
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            public void doInTransactionWithoutResult(TransactionStatus status) {

            }

        });

        //有返回值
        transactionTemplate.execute(new TransactionCallback<String>() {
            @Override
            public String doInTransaction(TransactionStatus status) {
                return null;
            }
        });
    }

https://blog.csdn.net/riemann_/article/details/90725121
https://developer.aliyun.com/article/782407?spm=a2c6h.12873639.article-detail.65.421f78bftg6Pjm#slide-6

上一篇下一篇

猜你喜欢

热点阅读