Spring相关问题整理

目录

1.Spring Boot与以前的Spring有什么区别?

2.Spring Boot启动加载过程是什么样的?

先进行总的分析汇总

具体详细如下:

一、基本代码启动

二、初始化SpringApplication

1.初始化SpringApplication的initialize方法

2.调用deduceWebEnvironment来判断当前的应用是否是web应用,并设置到webEnvironment属性中。

3.找出所有的应用程序初始化器 getSpringFactoriesInstances + setInitializers

补充:获取初始化器

4.找出所有的应用程序事件监听器  getSpringFactoriesInstances + setListeners

补充:获取监听器

5.调用deduceMainApplicationClass方法找出main类

三、运行SpringApplication

1.SpringApplicationRunListeners类

2.SpringApplicationRunListener类:监听SpringApplication的run方法执行

补充:获取SpringApplicationRunListeners

四、run方法详细

1.配置并准备环境

2.创建Spring容器上下文

3.配置Spring容器上下文

4.Spring容器创建之后回调方法postProcessApplicationContext

5.初始化器开始工作

6.Spring容器创建完成之后会调用afterRefresh方法

3.Spring的IOC/AOP的实现(必考)

4.动态代理的实现方式(必考)是否使用过GCLB,和JDK的区别是什么?

知识背景:JDK和CGLIB动态代理总结

补充问题:

何时使用JDK还是CGLiB?

如何强制使用CGLIB实现AOP?

JDK动态代理和CGLIB字节码生成的区别?

CGlib比JDK快?

Spring在选择用JDK还是CGLiB的依据:

5.Spring如何解决循环依赖(三级缓存)(必考)

循环依赖的产生和解决的前提

Spring使用了三级缓存解决了循环依赖的问题

6.Spring的@Transactional如何实现的(必考)

7.Spring的事务传播级别

补充:隔离级别

8.BeanFactory和ApplicationContext的联系和区别

9.Spring的后置处理器

10.Spring Cloud Zuul网关的调优策略有哪些?怎么实现其高可用?Zuul和Gataway,你们项目中是怎么选择的?项目中对Zuul网关层的要求是什么样的?

11.Spring Cloud Eureka和Nacos对比?怎么做选择?Eureka中高可用是怎么做的?进行的调优有哪些?原理是什么?

12.Spring Cloud 中常用的注解有哪些?怎么用的?

13.Spring Cloud中的组件有哪些?具体说说?微服务架构中用到的关键技术有哪些?

14.Spring Cloud Config配置架构是什么样的?可视化怎么做的?设计的业务有哪些?

参考书籍、文献和资料


备注:针对基本问题做一些基本的总结,不是详细解答!

1.Spring Boot与以前的Spring有什么区别?

具体可以见博客:https://blog.csdn.net/xiaofeng10330111/article/details/87271456

Spring开发WEB应用程序过程广泛采用的固定开发模式:通常包括使用Maven、Gradle等工具搭建工程、web.xml定义Spring的DispatcherServlet、完成启动Spring MVC的配置文件、编写响应HTTP请求的Controller以及服务部署到Tomcat Web服务器等步骤。但是,基于传统Spring框架进行开发的开发过程中,逐渐暴露出一些问题,典型的就是过于复杂和繁重的配置工作

Spring Boot优化了开发过程,采用约定优于配置思想的自动化配置、启动依赖项目自动管理、简化部署并提供监控等功能,是开发过程变得简单。其核心优势体现在编码、配置、部署、监控等多个方面:

  • 编码方面:只需要在maven中添加依赖并实现一个方法就可以提供微服务架构所推荐的RESTful风格接口。
  • 配置方面简单化--->1>把Spring中基于XML的功能配置方式转换为Java Config;2>把基于*.properties/*.xml文件部署环境配置转换成语言更为强大的*.yml;3>对常见的各种功能组件均提供了各种默认的starter依赖以简化Maven的配置。
  • 部署方面:相较于传统模式下的war包,Spring Boot的部署既包含了业务代码和各种第三方类库,同时也内嵌了HTTP容器。新的部署方式包结构支持java-jar standalone.jar方式的一键启动,不需要预部署应用服务器,通过默认内嵌Tomcat降低对运行环境的基本要求。
  • 监控方面:基于spring-boot-actuator组件,可以通过RESTful接口以及HATEOAS表现方式获取JVM性能指标、线程工作状态等运行信息。

2.Spring Boot启动加载过程是什么样的?

先进行总的分析汇总

Spring Boot通常有一个名为*Application的入口类,在入口类里有一个main方法,这个main方法其实就是一个标准的java应用的入口方法。在main方法中使用SpringApplication.run方法启动SpringBoot应用项目。

其中@SpringBootApplication是Spring Boot的核心注解,主要组合了@Configuration、@EnableAutoConfiguration、@ComponentScan。(如果不使用@SpringBootApplication注解,则可以使用在入口类上直接使用@Configuration、@EnableAutoConfiguration、@ComponentScan也能达到相同效果。)

其中几个注解的作用大致说一下:

  • @Configuration:是做类似于spring xml 工作的注解,标注在类上,类似与以前的**.xml配置文件。
  • @EnableAutoConfiguration:spring boot自动配置时需要的注解,会让Spring Boot根据类路径中的jar包依赖为当前项目进行自动配置。同时,它也是一个组合注解。
  • 在@EnableAutoConfiguration中用了@Import注解导入EnableAutoConfigurationImportSelector类,而EnableAutoConfigurationImportSelector就是自动配置关键。(SpringBoot的自动配置:SpringBoot的一大特色就是自动配置,例如,添加了spring-boot-starter-web依赖,会自动添加Tomcat和SpringMVC的依赖,SpringBoot会对Tomcat和SpringMVC进行自动配置。又例如:添加了spring-boot-starter-data-jpa依赖,SpringBoot会自动进行JPA相关的配置。)
  • @ComponentScan:告诉Spring 哪个packages 的用注解标识的类,会被spring自动扫描并且装入bean容器。SpringBoot会自动扫描@SpringBootApplication所在类的同级包以及下级包的Bean(如果为JPA项目还可以扫描标注@Entity的实体类),所以建议入口类放置在最外层包下。

spring-boot启动过程:

在这个静态方法中,创建并构造了SpringApplication对象,并调用该对象的run方法

构造SpringApplication对象:主要是对一些属性附上初始值,关键在与SpringApplication对象的initialize方法

  • 调用deduceWebEnvironment来判断当前的应用是否是web应用,并设置到webEnvironment属性中
  • 找出所有的应用程序初始化器调用getSpringFactoriesInstancesspring.factories文件中找出key为ApplicationContextInitializer的类并实例化,然后调用setInitializers方法设置到SpringApplicationinitializers属性
  • 找出所有的应用程序事件监听器,调用getSpringFactoriesInstances从spring.factories文件中找出key为ApplicationListener的类并实例化,然后调用setListeners方法设置到SpringApplicationlisteners属性中。
  • 调用deduceMainApplicationClass方法找出main类

初始化SpringApplication完成之后,调用run方法运行,run方法执行完成之后,Spring容器也已经初始化完成,各种监听器和初始化器也做了相应的工作

  • 具体运行SpringApplication,重点由SpringApplicationRunListeners和SpringApplicationRunListener类实现
  • SpringApplicationRunListeners内部持有SpringApplicationRunListener集合和1个Log日志类用于SpringApplicationRunListener监听器的批量执行
  • SpringApplicationRunListener类:监听SpringApplication的run方法执行。
  • run具体的实现包括:配置并准备环境--->创建Spring容器上下文--->配置Spring容器上下文--->Spring容器创建之后回调方法postProcessApplicationContext--->初始化器开始工作--->Spring容器创建完成之后会调用afterRefresh方法

具体详细如下:

一、基本代码启动

启动代码很简单,直接如下便可以完成:

@SpringBootApplication
public class SpringBootDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }
}

SpringApplication.run方法实际执行的方法如下:

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
    return new SpringApplication(sources).run(args);
}

二、初始化SpringApplication

1.初始化SpringApplication的initialize方法

SpringApplication的构造函数中调用了initialize方法来初始化SpringApplication

private void initialize(Object[] sources) {
    if (sources != null && sources.length > 0) {
        this.sources.addAll(Arrays.asList(sources));
    }
    this.webEnvironment = deduceWebEnvironment();
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

2.调用deduceWebEnvironment来判断当前的应用是否是web应用,并设置到webEnvironment属性中

deduceWebEnvironment方法通过获取

javax.servlet.Servlet
org.springframework.web.context.ConfigurableWebApplicationContext

这两个类来判断,如果能获得这两个类则说明是web应用,否则不是。

3.找出所有的应用程序初始化器 getSpringFactoriesInstances + setInitializers

调用getSpringFactoriesInstancesspring.factories文件中找出key为ApplicationContextInitializer的类并实例化,然后调用setInitializers方法设置到SpringApplicationinitializers属性。这个过程就是找出所有的应用程序初始化器

当前的初始化器有如下几个:

DelegatingApplicationContextInitializer
ContextIdApplicationContextInitializer
ConfigurationWarningsApplicationContextInitializer
ServerPortInfoApplicationContextInitializer
SharedMetadataReaderFactoryContextInitializer
AutoConfigurationReportLoggingInitializer

补充:获取初始化器

初始化器的获取由SpringApplication.getSpringFactoriesInstances方法完成:

  • 读取ApplicationContextInitializer的实现类
  • 实例化ApplicationContextInitializer的实现类
private  Collection getSpringFactoriesInstances(Class type,
        Class[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // Use names and ensure unique to protect against duplicates
    // 读取ApplicationContextInitializer的实现类
    Set names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 实例化ApplicationContextInitializer的实现类
    List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

SpringFactoriesLoader.loadFactoryNames方法获取ApplicationContextInitializer接口实现的类:

  • 获取接口类的名称
  • 获取FACTORIES_RESOURCE_LOCATION(META-INF/spring.factories)的多个位置
  • 从META-INF/spring.factories文件中加载配置
  • 从配置中读取ApplicationContextInitializer的实现类
public static List loadFactoryNames(Class factoryClass, ClassLoader classLoader) {
    // 获取接口类的名称
    String factoryClassName = factoryClass.getName();
    try {
        // 获取FACTORIES_RESOURCE_LOCATION(META-INF/spring.factories)的多个位置
        Enumeration urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        List result = new ArrayList();
        /**
         * urls有
         * spring-boot/META-INF/spring.factories
         * spring-beans/META-INF/spring.factories
         * spring-boot-autoconfigure/META-INF/spring.factories
         * 
         */
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            // 从META-INF/spring.factories文件中加载配置
            Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
            // 从配置中读取ApplicationContextInitializer的实现类
            String factoryClassNames = properties.getProperty(factoryClassName);
            result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
        }
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
                "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

4.找出所有的应用程序事件监听器  getSpringFactoriesInstances + setListeners

调用getSpringFactoriesInstances从spring.factories文件中找出key为ApplicationListener的类并实例化,然后调用setListeners方法设置到SpringApplicationlisteners属性中。这个过程就是找出所有的应用程序事件监听器

当前的事件监听器有如下几个:

ConfigFileApplicationListener
AnsiOutputApplicationListener
LoggingApplicationListener
ClasspathLoggingApplicationListener
BackgroundPreinitializer
DelegatingApplicationListener
ParentContextCloserApplicationListener
ClearCachesApplicationListener
FileEncodingApplicationListener
LiquibaseServiceLocatorApplicationListener

补充:获取监听器

获取监听器的方法与获取初始化器的方法一致:

唯一的区别在于获取org.springframework.context.ApplicationListener接口的实现类

5.调用deduceMainApplicationClass方法找出main类

就是这里的SpringBootDemoApplication

三、运行SpringApplication

重点:SpringApplicationRunListeners和SpringApplicationRunListener类

1.SpringApplicationRunListeners类

SpringApplicationRunListeners内部持有SpringApplicationRunListener集合和1个Log日志类。用于SpringApplicationRunListener监听器的批量执行。

2.SpringApplicationRunListener类:监听SpringApplication的run方法执行

SpringApplicationRunListener用于监听SpringApplication的run方法的执行,它定义了5个步骤

  • starting:run方法执行的时候立马执行,对应的事件类型是ApplicationStartedEvent
  • environmentPrepared:ApplicationContext创建之前并且环境信息准备好的时候调用,对应的事件类型是ApplicationEnvironmentPreparedEvent
  • contextPrepared:ApplicationContext创建好并且在source加载之前调用一次,没有具体的对应事件
  • contextLoaded:ApplicationContext创建并加载之后并在refresh之前调用,对应的事件类型是ApplicationPreparedEvent
  • finished:run方法结束之前调用,对应事件的类型是ApplicationReadyEvent或ApplicationFailedEvent

SpringApplicationRunListener目前只有一个实现类EventPublishingRunListener,它把监听的过程封装成了SpringApplicationEvent事件并让内部属性ApplicationEventMulticaster接口的实现类SimpleApplicationEventMulticaster广播出去,广播出去的事件对象会被SpringApplication中的listeners属性进行处理。

所以说SpringApplicationRunListener和ApplicationListener之间的关系是通过ApplicationEventMulticaster广播出去的SpringApplicationEvent所联系起来的

补充:获取SpringApplicationRunListeners

首先看getRunListeners方法:

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class[] types = new Class[] { SpringApplication.class, String[].class };
    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
            SpringApplicationRunListener.class, types, this, args));
}

可以看到通过调用构造函数来实例化SpringApplicationRunListeners,传入的参数有logger以及调用getSpringFactoriesInstance获得的SpringApplicationRunListener集合。

再看getSpringFactoriesInstance方法,它和获取初始化器的方法一样:

获取的接口类型是org.springframework.boot.SpringApplicationRunListener

获取到的实现类为org.springframework.boot.context.event.EventPublishRunListener

四、run方法详细

初始化SpringApplication完成之后,调用run方法运行,run方法执行完成之后,Spring容器也已经初始化完成,各种监听器和初始化器也做了相应的工作

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();    // 构造一个任务执行观察者
    stopWatch.start();    // 开始执行,记录开始时间
    ConfigurableApplicationContext context = null;
    FailureAnalyzers analyzers = null;
    configureHeadlessProperty();
    // 获取SpringApplicationRunListeners,内部只有一个EventPublishingRunListener
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 封装成SpringApplicationEvent事件然后广播出去给SpringApplication中的listeners所监听
    // 这里接受ApplicationStartedEvent事件的listener会执行相应的操作
    listeners.starting();
    try {
        // 构造一个应用程序参数持有类
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 准备并配置环境
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        // 打印banner图形
        Banner printedBanner = printBanner(environment);
        // 创建Spring容器
        context = createApplicationContext();
        analyzers = new FailureAnalyzers(context);
        // 配置Spring容器
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        // 容器上下文刷新,详见Spring的启动分析
        refreshContext(context);
        
        // 容器创建完成之后调用afterRefresh方法
        afterRefresh(context, applicationArguments);
        // 调用监听器,广播Spring启动结束的事件
        listeners.finished(context, null);
        // 停止任务执行观察者
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        return context;
    }
    catch (Throwable ex) {
        handleRunFailure(context, listeners, analyzers, ex);
        throw new IllegalStateException(ex);
    }
}

具体步骤如:

1.配置并准备环境

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
    // 创建应用程序的环境信息。如果是web程序,创建StandardServletEnvironment;否则,创建StandardEnvironment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 配置环境信息。比如profile,命令行参数
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // 广播出ApplicationEnvironmentPreparedEvent事件给相应的监听器执行
    listeners.environmentPrepared(environment);
    // 环境信息的校对
    if (!this.webEnvironment) {
        environment = new EnvironmentConverter(getClassLoader())
                .convertToStandardEnvironmentIfNecessary(environment);
    }
    return environment;
}

2.创建Spring容器上下文

protected ConfigurableApplicationContext createApplicationContext() {
    Class contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            // 判断是否是web应用,
            // 如果是则创建AnnotationConfigEmbeddedWebApplicationContext,否则创建AnnotationConfigApplicationContext
            contextClass = Class.forName(this.webEnvironment
                    ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}

3.配置Spring容器上下文

private void prepareContext(ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    // 设置Spring容器上下文的环境信息
    context.setEnvironment(environment);
    // Spring容器创建之后做一些额外的事
    postProcessApplicationContext(context);
    // SpringApplication的初始化器开始工作
    applyInitializers(context);
    // 遍历调用SpringApplicationRunListener的contextPrepared方法。目前只是将这个事件广播器注册到Spring容器中
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }

    // 把应用程序参数持有类注册到Spring容器中,并且是一个单例
    context.getBeanFactory().registerSingleton("springApplicationArguments",
            applicationArguments);
    if (printedBanner != null) {
        context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
    }

    // 加载sources,sources是main方法所在的类
    Set sources = getSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    // 将sources加载到应用上下文中。最终调用的是AnnotatedBeanDefinitionReader.registerBean方法
    load(context, sources.toArray(new Object[sources.size()]));
    // 广播出ApplicationPreparedEvent事件给相应的监听器执行
    // 执行EventPublishingRunListener.contextLoaded方法
    listeners.contextLoaded(context);
} 
  

4.Spring容器创建之后回调方法postProcessApplicationContext

protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
    // 如果SpringApplication设置了实例命名生成器,则注册到Spring容器中
    if (this.beanNameGenerator != null) {
        context.getBeanFactory().registerSingleton(
                AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
                this.beanNameGenerator);
    }
    // 如果SpringApplication设置了资源加载器,设置到Spring容器中
    if (this.resourceLoader != null) {
        if (context instanceof GenericApplicationContext) {
            ((GenericApplicationContext) context)
                    .setResourceLoader(this.resourceLoader);
        }
        if (context instanceof DefaultResourceLoader) {
            ((DefaultResourceLoader) context)
                    .setClassLoader(this.resourceLoader.getClassLoader());
        }
    }
}

5.初始化器开始工作

首先调用getInitializers方法获取之前取得的初始化器。之后调用初始化器的initialize方法。

protected void applyInitializers(ConfigurableApplicationContext context) {
    // 遍历每个初始化器,调用对应的initialize方法
    for (ApplicationContextInitializer initializer : getInitializers()) {
        Class requiredType = GenericTypeResolver.resolveTypeArgument(
                initializer.getClass(), ApplicationContextInitializer.class);
        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
        initializer.initialize(context);
    }
}

6.Spring容器创建完成之后会调用afterRefresh方法

protected void afterRefresh(ConfigurableApplicationContext context,
        ApplicationArguments args) {
    callRunners(context, args);
}

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List runners = new ArrayList();
    // 找出Spring容器中ApplicationRunner接口的实现类
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    // 找出Spring容器中CommandLineRunner接口的实现类
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    // 对runners进行排序
    AnnotationAwareOrderComparator.sort(runners);
    // 遍历runners依次执行
    for (Object runner : new LinkedHashSet(runners)) {
        if (runner instanceof ApplicationRunner) {
            callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            callRunner((CommandLineRunner) runner, args);
        }
    }
} 
  

3.Spring的IOC/AOP的实现(必考)

IOC相关知识见博客:https://blog.csdn.net/xiaofeng10330111/article/details/105631666

AOP的实现方式:动态代理的实现方式

  • JDK动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
  • CGlib动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
  • 区别:JDK代理只能对实现接口的类生成代理;CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。

后面会自己补一个详细的。

4.动态代理的实现方式(必考)是否使用过GCLB,和JDK的区别是什么?

实现方式有两种:JDK动态代理和CGLIB动态代理

  • JDK动态代理只能对实现了接口的类生成代理,而不能针对类;
  • CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承);
  • JDK动态代理:利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

  • CGLiB动态代理:利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

知识背景:JDK和CGLIB动态代理总结

相关更细的知识见博客:https://blog.csdn.net/xiaofeng10330111/article/details/105633821

  • JDK动态代理只能对实现了接口的类生成代理,而不能针对类 ,使用的是 Java反射技术实现,生成类的过程比较高效
  • CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 ,使用asm字节码框架实现,相关执行的过程比较高效,生成类的过程可以利用缓存弥补,因为是继承,所以该类或方法最好不要声明成final 
  • JDK代理是不需要第三方库支持,只需要JDK环境就可以进行代理,使用条件:实现InvocationHandler + 使用Proxy.newProxyInstance产生代理对象 + 被代理的对象必须要实现接口
  • CGLib必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法,是一种继承但是针对接口编程的环境下推荐使用JDK的代理

补充问题:

何时使用JDK还是CGLiB?

  • 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。
  • 如果目标对象实现了接口,可以强制使用CGLIB实现AOP。
  • 如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。

如何强制使用CGLIB实现AOP?

  • 添加CGLIB库(aspectjrt-xxx.jar、aspectjweaver-xxx.jar、cglib-nodep-xxx.jar)
  • 在Spring配置文件中加入

JDK动态代理和CGLIB字节码生成的区别?

  • JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
  • CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final,对于final类或方法,是无法继承的。

CGlib比JDK快?

使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类
在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐

Spring在选择用JDK还是CGLiB的依据:

  • 当Bean实现接口时,Spring会用JDK的动态代理。
  • 当Bean没有实现接口时,Spring使用CGlib是实现。
  • 如果Bean实现了接口,强制使用CGlib时,(添加CGLIB库,在spring配置中加入)。

5.Spring如何解决循环依赖(三级缓存)(必考)

循环依赖的产生和解决的前提

循环依赖的产生可能有很多种情况,例如:

  • A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象
  • A的构造方法中依赖了B的实例对象,同时B的某个field或者setter需要A的实例对象,以及反之
  • A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象,以及反之

当然,Spring对于循环依赖的解决不是无条件的,首先前提条件是针对scope单例并且没有显式指明不需要解决循环依赖的对象,而且要求该对象没有被代理过。同时Spring解决循环依赖也不是万能,以上三种情况只能解决两种,第一种在构造方法中相互依赖的情况Spring也无力回天

Spring循环依赖的理论依据其实是Java基于引用传递当我们获取到对象的引用时,对象的field或者属性是可以延后设置的。
Spring单例对象的初始化其实可以分为三步

  • createBeanInstance, 实例化,实际上就是调用对应的构造方法构造对象,此时只是调用了构造方法,spring xml中指定的property并没有进行populate
  • populateBean,填充属性,这步对spring xml中指定的property进行populate
  • initializeBean,调用spring xml中指定的init方法,或者AfterPropertiesSet方法
    会发生循环依赖的步骤集中在第一步和第二步

对于单例对象来说,在Spring的整个容器的生命周期内,有且只存在一个对象,很容易想到这个对象应该存在Cache中,Spring大量运用了Cache的手段,在循环依赖问题的解决过程中甚至使用了“三级缓存”。“三级缓存”主要是指

/** Cache of singleton objects: bean name --> bean instance */
private final Map singletonObjects = new ConcurrentHashMap(256);
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map> singletonFactories = new HashMap>(16);
/** Cache of early singleton objects: bean name --> bean instance */
private final Map earlySingletonObjects = new HashMap(16);

从字面意思来说:singletonObjects指单例对象的cache(第一级缓存),singletonFactories指单例对象工厂的cache(第三级缓存),earlySingletonObjects指提前曝光的单例对象的cache(第二级缓存)。以上三个cache构成了三级缓存,Spring就用这三级缓存巧妙的解决了循环依赖问题。

Spring使用了三级缓存解决了循环依赖的问题

Bean创建的过程,首先Spring会尝试从缓存中获取,这个缓存就是指singletonObjects,主要调用的方法getSingleton,分析getSingleton的整个过程,Spring首先从singletonObjects(一级缓存)中尝试获取,如果获取不到并且对象在创建中,则尝试从earlySingletonObjects(二级缓存)中获取,如果还是获取不到并且允许从singletonFactories通过getObject获取,则通过singletonFactory.getObject()(三级缓存)获取

在populateBean()给属性赋值阶段里面Spring会解析你的属性,并且赋值,当发现,A对象里面依赖了B,此时又会走getBean方法,但这个时候,你去缓存中是可以拿的到的。因为我们在对createBeanInstance对象创建完成以后已经放入了缓存当中,所以创建B的时候发现依赖A,直接就从缓存中去拿,此时B创建完,A也创建完,一共执行了4次。至此Bean的创建完成,最后将创建好的Bean放入单例缓存池中

6.Spring的@Transactional如何实现的(必考)

@Transactional是spring中声明式事务管理的注解配置方式,@Transactional注解可以帮助我们把事务开启、提交或者回滚的操作,通过aop的方式进行管理。通过@Transactional注解就能让spring为我们管理事务,免去了重复的事务管理逻辑,减少对业务代码的侵入,使我们开发人员能够专注于业务层面开发。

Spring相关问题整理_第1张图片


实现@Transactional原理是基于spring aop,aop又是动态代理模式的实现。主要源码思路如下图:

Spring相关问题整理_第2张图片

具体源码分析不做总结,后期会单独总结一篇。

7.Spring的事务传播级别

事务传播行为(为了解决业务层方法之间互相调用的事务问题):当事务方法被另一个事务方法调用时,必须指定事务应该如何传播

例如:方法可能继续在现有事务中运行,也可能开启一个新事务并在自己的事务中运行

在TransactionDefinition定义中包括了如下几个表示传播行为的常量:

支持当前事务的情况:

  • TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)

不支持当前事务的情况:

  • TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。

其他情况:

  • TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

补充:隔离级别

TransactionDefinition 接口中定义了五个表示隔离级别的常量:

  • TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
  • TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
  • TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能,通常情况下也不会用到该级别。

Spring相关问题整理_第3张图片

8.BeanFactory和ApplicationContext的联系和区别

  • BeanFactory是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能
  • ApplicationContext应用上下文,继承BeanFactory接口,它是Spring的一各更高级的容器,提供了更多的有用的功能。如国际化,访问资源,载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,消息发送、响应机制,AOP等。
  • BeanFactory在启动的时候不会去实例化Bean,从容器中拿Bean的时候才会去实例化
  • ApplicationContext在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化

9.Spring的后置处理器

  • BeanPostProcessor:Bean的后置处理器,主要在bean初始化前后工作
  • InstantiationAwareBeanPostProcessor:继承于BeanPostProcessor,主要在实例化bean前后工作; AOP创建代理对象就是通过该接口实现。
  • BeanFactoryPostProcessor:Bean工厂的后置处理器,在bean定义(bean definitions)加载完成后,bean尚未初始化前执行。
  • BeanDefinitionRegistryPostProcessor:继承于BeanFactoryPostProcessor。其自定义的方法postProcessBeanDefinitionRegistry会在bean定义(bean definitions)将要加载,bean尚未初始化前真执行,即在BeanFactoryPostProcessor的postProcessBeanFactory方法前被调用。

10.Spring Cloud Zuul网关的调优策略有哪些?怎么实现其高可用?Zuul和Gataway,你们项目中是怎么选择的?项目中对Zuul网关层的要求是什么样的?

详细内容查看:https://blog.csdn.net/xiaofeng10330111/article/details/87272495

Spring Cloud Zuul网关的调优策略

Zuul 1.0 是一个基于JVM的后端路由器,同时是一个建立在Servlet上的同步阻塞架构,故在使用时对这部分的优化工作是必要的,根据实践经验,对Zuul的优化分为以下几个类型:

  • 容器优化:内置容器Tomcat与Undertow的比较与参数设置;
  • 组件优化:内部集成的组件优化,如Hytrix线程隔离、Ribbon、HttpClient与OkHttp选择;
  • JVM参数优化:适合于网关应用的JVM参数建议;
  • 内部优化:一些原生参数或者内部源码,以更适当的方式进行重写。

高可用方案

Spring Cloud Zuul网关高可用可以借助OpenResty整合的Nginx和Lua,使用Lua脚本模块与注册中心构建一个服务动态增减的机制,通过Lua获取注册中心状态为UP的服务,动态地加入到Nginx的负载均衡列表中,将其称之为“多层负载”

其实也可以结合K8S特性去实现,这个之前玩K8S的时候试验过,是可以实现的!

Zuul和Gataway对比和选择

从底层源码上来看,Zuul构建于 Servlet 2.5,兼容 3.x,使用的是阻塞式的 API,不支持长连接,比如 websockets。另外

Spring Cloud Gateway构建于 Spring 5+,基于 Spring Boot 2.x 响应式的、非阻塞式的 API。同时,它支持 websockets,和 Spring 框架紧密集成,开发体验相对来说十分不错。

在微服务架构中网关上的选择,最好的方式是使用现在比较成熟的Spring Cloud套件,Zuul和Gataway都可以,最好提供了Spring Cloud Gateway网关,或是结合公司情况来开发一套适合自己的微服务套件,至少从网关上可以看出来其内部实现并不难,同时也比较期待开源项目Nacos、Spring Cloud Alibaba 建设情况,期待它能构建一个高活跃社区的、稳定的、适合中国特色(大流量、高并发)的微服务基础架构。

项目中对网关的要求

基本具备以下功能:认证和鉴权+压力控制+金丝雀测试+动态路由+负载均衡+静态响应处理+主动流量控制+限流+文件上传+参数转换+其他逻辑与业务处理等

11.Spring Cloud Eureka和Nacos对比?怎么做选择?Eureka中高可用是怎么做的?进行的调优有哪些?原理是什么?

详细请查看:https://blog.csdn.net/xiaofeng10330111/article/details/87271881

都是服务注册发现中心,但是Nacos还可以用作配置中心,目前来看,建议使用Nacos,因为Eureka已经不在开源,而且性能上和高可用上没有Nacos方便。

相关调优方案见上面的博客。

12.Spring Cloud 中常用的注解有哪些?怎么用的?

  • @Controller 控制层,里面有多个连接
  • @Service 业务层,一般对于接口和实现
  • @Qualifier 如果一个接口有多个实现,那么注入时候加上唯一标示
  • @Repository 一般的dao层
  • @Autowired 自动注入依赖
  • @RequestMapping (value=’’,method={RequestMethod。GET或者POSt})绑定url
  • @RequestParam (value=’’ required=false)绑定参数
  • @ModelAttribute 一般用于controller层,呗注解的方法会在所以mapping执行之前执行,并且可以绑定参数到Model model里面。
  • @Transactional (readOnly=true)注解式事务
  • @Value(“${}”)可以注入properties里面的配置项
  • @ControllerAdvice 是spring3提供的新注解,控制器增@ExceptionHandler 如果在controller方法遇到异常,就会调用含有此注解的方法。@EnableDiscoveryClient 与@EnableEurekaCLient 具有相同的功能,不同的事该注解同时可以注册Zookeper,也可用于服务发现,标注在主启动类上;
  • @InitBinder 一般用于controller 可以将所有form 传递进来的string 进行html编码,防止xss攻击,比如可以将字符串类型的日期转换成date类型
  • @EnableCaching 注解自动化配置合适的缓存管理器。
  • @EnableWebSecurity 注解开启spring security的功能,集成websercrityconfigureadapter。
  • @SringBootApplication相当于@Configuation、@EnableAutoConfiguation、@ComponentScan三个注解合用。
  • @EnableDiscoveryClient 自定义服务发现的客服端
  • @EnableAdminServer 使用admin监控应用。
  • @EnableEurekaClient配置本应用将使用服务注册和服务发现,注意:注册和发现用这个注解。
  • @EnableHystrix表示启动断路器,断路器依赖于服务注册和发现。
  • @HystrixCommand注解方法失败后,系统将西东切换到fallbackMethod方法执行,
  • @EnableAutoConfiguration spring boot自动配置,尝试根据你添加的jar依赖自动配置你的spring应用。
  • @ComponentScan 表示将该类自动发现并注册bean 可以自动收集所有的spring组件
  • @Comfiguration 相当于传统的xml配置文件
  • @Import 导入其他配置类
  • @ImportResource用来 加载xml配置文件
  • @FeignClient注解中的fallbank属性指定回调类
  • @ResController是@controller和@ResponseBody的结合体
  • @EnableDiscoveryClient 与@EnableEurekaCLient 具有相同的功能,不同的事该注解同时可以注册Zookeper,也可用于服务发现,标注在主启动类上;

13.Spring Cloud中的组件有哪些?具体说说?微服务架构中用到的关键技术有哪些?

在介绍Spring Cloud 全家桶之前,首先要介绍一下Netflix ,Netflix 是一个很伟大的公司,在Spring Cloud项目中占着重要的作用,Netflix 公司提供了包括Eureka、Hystrix、Zuul、Archaius等在内的很多组件,在微服务架构中至关重要,Spring在Netflix 的基础上,封装了一系列的组件。

相关具体组件见:https://blog.csdn.net/xiaofeng10330111/article/details/87271644

关键技术及要求基本有:

微服务架构-实现技术之六大基础组件:服务通信+事件驱动+负载均衡+服务路由+API网关+配置管理

对应博客:https://blog.csdn.net/xiaofeng10330111/article/details/85682513

微服务架构-实现技术之三大关键要素1服务治理:服务注册中心+服务发布与注册+服务发现与调用+服务监控

对应博客:https://blog.csdn.net/xiaofeng10330111/article/details/86770057

微服务架构-实现技术之三大关键要素2数据一致性:分布式事物+CAP&BASE+可靠事件模式+补偿模式+Sagas模式+TCC模式+最大努力通知模式+人工干预模式

对应博客:https://blog.csdn.net/xiaofeng10330111/article/details/86772650

微服务架构-实现技术之三大关键要素3服务可靠性:服务访问失败的原因和应对策略+服务容错+服务隔离+服务限流+服务降级

对应博客:https://blog.csdn.net/xiaofeng10330111/article/details/86772740

14.Spring Cloud Config配置架构是什么样的?可视化怎么做的?设计的业务有哪些?

具体见博客:https://blog.csdn.net/xiaofeng10330111/article/details/87272559

 

参考书籍、文献和资料

1.https://blog.wangqi.love/articles/Spring/SpringBoot%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B.html  Spring Boot启动加载过程讲的很详细

2.https://blog.csdn.net/xiaofeng10330111/article/details/87271456

3.https://blog.csdn.net/zxzzxzzxz123/article/details/69941910

4.https://www.cnblogs.com/liubin1988/p/8909610.html

5.https://blog.csdn.net/weixin_38327420/article/details/85068641  动态代理的举例

6.https://www.cnblogs.com/wangenxian/p/10885309.html

7.https://www.cnblogs.com/gonjan-blog/p/6685611.html

8.https://www.jianshu.com/p/6c359768b1dc

9.https://blog.csdn.net/qq_20597727/article/details/84868035

10.http://www.cppcns.com/ruanjian/java/216206.html

11.https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/index.html

12.https://www.cnblogs.com/chongaizhen/p/11003832.html

13.https://blog.csdn.net/xiaofeng10330111/article/details/87272495

14.https://blog.csdn.net/youanyyou/article/details/90100546

15.https://blog.csdn.net/u010681191/article/details/99656413

16.https://blog.csdn.net/weixin_42685925/article/details/94456156

17.https://blog.csdn.net/xiaofeng10330111/article/details/87271881

18.https://blog.csdn.net/xlgen157387/article/details/77773908

19.https://www.iteye.com/blog/jinnianshilongnian-1413846

 

你可能感兴趣的:(微服务架构与开发,Spring,Cloud技术应用)