Spring基础

IOC控制反转

如A类中依赖了B类,传统生成实例的过程是需要先实例化B,在实例化A时传入B;控制反转实现了先实例化A,扫描到需要使用B时再实例化B
实现了实例化过程的解耦,A、B可以单独实例化,再实现依赖关系

Spring容器管理时的循环依赖问题

三级缓存解决循环依赖
三级缓存实际对应的是三次实例化过程,如A与B产生循环依赖
第一次:实例化A,放入一级缓存
第二次:实例化A是发现依赖了B,会将A的实例化放到二级缓存,然后开始B的实例化
第三次:实例化B时发现依赖了A,此时A已存在在二级缓存中,会将A放入三级缓存-B的实例化工厂,实例化工厂会将A注入到B,最后Spring容器再将B注入到A中。

Spring Bean的作用域

  • singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
  • prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续 getBean() 两次,得到的是不同的 Bean 实例。
    可作为DTO使用,使用方式:
    @Component
    @Scope("prototype")
    public class MyBean {
    	// ...
    }
    
  • request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
  • session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
  • application/global-session (仅 Web 应用可用):每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。
  • websocket (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。

Spring Bean的线程安全问题

Bean的线层安全取决于作用域和数据状态;理论上论,大部分业务代码中都是使用的单例作用域,所以非线程安全,但是我们定义的Bean一般都无数据状态,比如service、mapper,只做数据处理和传递,也不存在线程安全问题。

Spring Bean的生命周期

  1. 加载
    • Bean 容器找到配置文件中 Spring Bean 的定义。
    • Bean 容器利用 Java Reflection API 创建一个 Bean 的实例。
  2. 初始化(包含Bean自身数据状态和对象头部信息)
    • 如果涉及到一些属性值 利用 set()方法设置一些属性值。
    • 如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName()方法,传入 Bean 的名字。
    • 如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader()方法,传入 ClassLoader对象的实例。
    • 如果 Bean 实现了 BeanFactoryAware 接口,调用 setBeanFactory()方法,传入 BeanFactory对象的实例。
    • 与上面的类似,如果实现了其他 *.Aware接口,就调用相应的方法。
  3. 回调操作
    • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessBeforeInitialization() 方法。
    • 如果 Bean 实现了InitializingBean接口,执行afterPropertiesSet()方法。
    • 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
    • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessAfterInitialization() 方法
  4. 销毁
    • 当要销毁 Bean 的时候,如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。
    • 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。

@Component 和 @Bean 的区别

@Bean用于配置文件中,作用于方法,通过自定义的方式生成实例托管到Spring容器中。
@Component通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中,利用的Spring的自动装配机制来加载。

Spring中的事务管理

事务管理方式

  1. 自定义事务管理器
    可在Mybatise的配置文件中制定自定义事务管理器,核心要点是解决事务开启、提交、回滚过程
@Bean
    public DataSourceTransactionManager dataSourceTransactionManager() {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource());
        return dataSourceTransactionManager;
    }
  1. 业务代码中通过使用@Transactional注解开启事务

事务的传播行为

常用的事务传播行为:

  1. TransactionDefinition.PROPAGATION_REQUIRED
    使用的最多的一个事务传播行为,我们平时经常使用的@Transactional注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  2. TransactionDefinition.PROPAGATION_REQUIRES_NEW
    创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
  3. TransactionDefinition.PROPAGATION_NESTED
    如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。类似于分布式事务,将一个大事务拆分成多个小事务,任一小事务回滚会导致大事务回滚。事务的拆分可以提高执行效率。
  4. TransactionDefinition.PROPAGATION_MANDATORY如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)

事务失效的场景

  1. 开启事务错误
    没使用@Transactional注解或错误使用了Propagation.NOT_SUPPORTED传播行为
  2. 调用方式错误
    没用通过Spring容器管理的Bean对象调用事务方法,导致AOP失效
  3. 业务代码异常没被捕获
    业务代码自己处理的异常,但是没有抛出,导致事务没法回滚
  4. 事务注解的方法不支持AOP
    final和static修饰的方法由于无法被AOP托管,导致事务失效
  5. 声明的事务捕获异常类型不匹配
    如指定事务捕获的异常类型为@Transactional(rollbackFor = RuntimeException.class),但实际抛出的是Exception.class异常
  6. 使用的数据库不支持事务
    如Mysql的数据表使用了MyISAM存储引擎

Spring Security

利用shrio管理用户、角色、权限

AOP-面向切面编程

生成切面,利用切面的切点动态代码业务代码中的方法,在方法执行时添加前置操作或后置操作;
传统的动态代理需要先实现InvocationHandler去完成代理过程中需要执行的前置操作与后置操作,再通过InvocationHandler实现类的invoke方法完成代理过程,如下:

public class VectorProxy implements InvocationHandler {

    private Object subject = null;

    public VectorProxy(Object subject) {
        this.subject = subject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        preRequset(method);
        if (args != null){
            for (int i = 0; i < args.length; i++) {
                System.out.println(args[i]);
            }
        }
        Object result = method.invoke(subject, args);
        afterRequset(method);
        return result;
    }

    private void preRequset(Method method){
        System.out.println("before calling "+method);
    }

    private void afterRequset(Method method){
        System.out.println("after calling "+method);
    }
}

AOP实现切面入驻时无法获取到InvocationHandler实现类,因此无法采用传统方式,而是使用了动态生成字节码(Cglib)的方式直接生成了被代理类的子类,通过子类完成代理过程,如下:

// 添加注解标记为切面
@Aspect
@Component
public class AspectLearn {

    Logger logger = LoggerFactory.getLogger(AspectLearn.class);

    // 指定切点,AOP采用Cglib根据切点生成子类
    @Pointcut("execution(* com.zwj.test.commonlearn.controller.UserInfoController.*(..))")
    private void controllerPointcut() {}

    // 切点环绕操作
    @Around("controllerPointcut()")
    private void controllerLog(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.info("第一次执行around");
        String method = joinPoint.getSignature().getName();
        logger.info("调用{}请求参数:" + Arrays.asList(joinPoint.getArgs()), method);
        Object result = joinPoint.proceed();
        logger.info("调用{}返回结果:{}", result);
        logger.info("第二次执行around");
    }

    // 切点前置操作
    @Before("controllerPointcut()")
    private void controllerBefore(JoinPoint joinPoint) {
        logger.info("执行before");
    }

 // 切点后置操作
    @After("controllerPointcut()")
    private void controllerAfter(JoinPoint joinPoint) {
        logger.info("执行after");
    }
}

Spring boot

spring-boot启动过程

由SpringApplication.run()执行开始,执行过程分两个步骤,完成SpringApplication的初始创建,再执行run方法

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return (new SpringApplication(primarySources)).run(args);
    }

SpringApplication创建

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.sources = new LinkedHashSet();
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.addConversionService = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = Collections.emptySet();
        this.isCustomEnvironment = false;
        this.lazyInitialization = false;
        this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
        this.applicationStartup = ApplicationStartup.DEFAULT;
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// ApplicationContext上下文构造器构建
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 监听器创建
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }

run方法执行

public ConfigurableApplicationContext run(String... args) {
// 开始时钟,记录启动过程总耗时
        long startTime = System.nanoTime();
        DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
        ConfigurableApplicationContext context = null;
// 加载java.awt.headless相关配置
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
// 触发监听器的starting动作
        listeners.starting(bootstrapContext, this.mainApplicationClass);

        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 触发监听器的environmentPrepared动作,并完成环境变量的准备工作
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
//  打印Banner
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);
// 触发监听器的contextPrepared动作,完成初始ApplicationContext上下文
            this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 刷新上下文,将所有的Bean加载到上下文中
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
            }
// 触发监听器的started动作
            listeners.started(context, timeTakenToStartup);
// 执行启动命令行的回调,通过回调去执行启动配置的命令参数,也可再该回调中完成数据预热的操作
            this.callRunners(context, applicationArguments);
        } catch (Throwable var12) {
            this.handleRunFailure(context, var12, listeners);
            throw new IllegalStateException(var12);
        }

        try {
            Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
// 触发监听器的ready动作
            listeners.ready(context, timeTakenToReady);
            return context;
        } catch (Throwable var11) {
            this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var11);
        }
    }

归纳可得出如下核心步骤:

  1. 创建SpringApplication应用对象,该过程中完成了ApplicationContext上下文构造器的创建,同时完成了监听器的创建
  2. 开始run方法的执行,开始时钟,记录启动过程总耗时
  3. 完成环境变量的准备工作
  4. 完成创建初始ApplicationContext上下文
  5. 刷新上下文,将所有的Bean加载到上下文中
  6. 执行启动命令行的回调,通过回调去执行启动配置的命令参数,也可再该回调中完成数据预热的操作
    image

监听器

SpringBoot提供了监听器机制,用于在SpringBoot的各声明周期阶段完成自定义操作,主要分类如下:

  • SpringApplicationRunListener:
    用于监听SpringBoot的整个生命周期;实现SpringApplicationRunListener,并在spring.factories中配置后可生效
/**
 * SpringBoot启动生命周期监听器,可以监听启动过程的每一个阶段
 */
public class MySpringApplicationRunListener implements SpringApplicationRunListener {


    private final SpringApplication application;
    private final String[] args;

    public MySpringApplicationRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
    }

    @Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        System.out.println("【starting】项目开始启动......");
    }

    @Override
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        System.out.println("【environmentPrepared】项目环境配置数据开始准备......");

    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        System.out.println("【contextPrepared】项目运行上下文对象开始准备......");
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        System.out.println("【contextLoaded】项目运行上下文对象开始加载......");
    }

    @Override
    public void started(ConfigurableApplicationContext context, Duration timeTaken) {
        System.out.println("【started】项目启动成功.....耗时:" + timeTaken.getSeconds() + "秒");
    }

    @Override
    public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
        System.out.println("【ready】项目正在运行,准备接收客户端执行指令.....");
    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        System.out.println("【failed】项目启动失败.....");
    }
}


//spring.factorise文件配置
org.springframework.boot.SpringApplicationRunListener=\
  com.zwj.test.commonlearn.springBoot.life.MySpringApplicationRunListener
  • ApplicationContextInitializer:
    ApplicationContext上下文初始前执行,此时已经完成了环境变量的加载工作,因此可利用该监听器完成环境变量的检查与修改;ApplicationContextInitializer,并在spring.factories中配置后可生效。
/**
 * 当环境变量加载完成,开始初始化Application上下文时调用,可用用于检查修改环境变量
 */
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        System.out.println("MyApplicationContextInitializer执行.......");
    }
}


//spring.factorise文件配置
org.springframework.context.ApplicationContextInitializer=\
  com.zwj.test.commonlearn.springBoot.life.MyApplicationContextInitializer
  • CommandLineRunner:
    启动命令行回调执行器,SpringBoot启动完成后执行,通过该监听器可以执行启动过程中配置的启动参数,也可做缓存预热使用。实现CommandLineRunner接口,并通过@Component注入到Spring容器中可生效(此时SpringBoot已经完成了启动过程,所以可使用注解注入)
/**
 * 启动命令行参数回调,当应用启动完成后执行,参数为启动时配置的命令
 * 可做缓存预热
 */
@Component
public class MyCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("CommandLineRunner执行......  参数: " + Arrays.toString(args));
    }
}
  • ApplicationRunner:
    启动命令行回调执行器,SpringBoot启动完成后执行,功能和用法与CommandLineRunner类似,区别在于ApplicationRunner提供的命令行参数是复杂类型,可以做更多的扩展功能。
/**
 * 启动命令行参数回调,当应用启动完成后执行,参数为启动时配置的命令
 * 效果与CommandLineRunner一致,区别在于ApplicationRunner中的参数时ApplicationArguments,可以扩展命令的执行方式
 * 可做缓存预热
 */
@Component
public class MyApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("ApplicationRunner正在执行......");
    }
}

Spring boot的自动装配

自动装配作用

Spring Boot应用项目在构建过程中会大量使用外部组件,如Mybatis、Redis等等,传统的Spring方式需要我们自己在xml文件中定义各种组件相关的Bean,通过IOC注入到Spring容器。Spring Boot的自动装配提供了组件自动将自己相关的Bean注入到Spring容器的功能,不需要Spring Boot应用项目再单独注入相关Bean。

自动装配依赖技术

自动装载的核心是@Import和@Conditional相关注解,并通过@Enable相关注解作为统一启用注解

  • @Import注解
    用于将指定的类注入到Spring容器中,指定类的类型包含:单独的Bean、配置类、依赖于DeferredImportSelector的Bean选择器以及依赖于ImportBeanDefinitionRegistrar的Bean注册器
    • 单独的Bean:直接将制定的Bean实例化,注入到IOC中
    • 配置类:导入的配置类里面所有的Bean都会被注入到Spring容器
    • Bean选择器:通过实现DeferredImportSelector接口的selectImports方法,通过自定义的方式提供要注入的Bean的全路径数组
    • Bean注入器:通过实现ImportBeanDefinitionRegistrar接口的registerBeanDefinitions方法,通过自定义的方式直接生成Bean定义,并注册Bean定义
      @Import注解使用示例:
/**
 * 创建Enable注解,当spring-boot应用找那个需要使用此模块功能时,
 * 可添加改注解,注入当前starter模块的所有Bean
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({
        // 导入配置类OtherStarterConfig,通过配置类批量注入Ban
        OtherStarterConfig.class,
        // 导入OtherStarterImportSelector选择器,通过选择器自定义扫描Bean过程
        OtherStarterImportSelector.class,
        // 导入OtherImportBeanDefinitionRegistrar注册器,通过选择器自定义扫描Bean过程
        OtherImportBeanDefinitionRegistrar.class})
// 直接导入Bean
public @interface EnableOtherStarter {
}
  • @Conditional相关注解
    用于过滤不需要注入的Bean;针对于Spring Boot的自动装配功能,如果外部组件的所有Bean都默认自动注入,会极大的影响系统的启动速度,因此提供了@Conditional注解,给Bean定义添加一些注入条件,当符合条件时才注入相关组件的Bean;如Redis组件相关Bean的自动装配前提是需要在application.yml中配置Redis数据源。常用的@Conditional如下:
    • @ConditionalOnBean: 根据Spring容器是否已经注入了指定Bean为条件
    • @ConditionalOnProperty:根据配置文件中的属性配置为条件
    • @ConditionalOnClass:根据是否加载了指定了Class为条件
      如SpringBoot的web运行容器便是通过@ConditionalOnClass实现,默认状态下spring-boot-starter-web依赖了tomcat容器相关jar,因此项目启动时可以加载tomcat相关的class从而默认使用tomcat容器;若需要切换容易,只需要排除tomcat依赖,添加其它容器依赖即可,示例:
implementation('org.springframework.boot:spring-boot-starter-web') {
	exclude group: 'org.springframework.boot',module: 'spring-boot-starter-tomcat'
}
//    替换内置的web容器,屏蔽tomcat后引入jetty
implementation('org.springframework.boot:spring-boot-starter-jetty')
  • @Enable相关注解
    一般用作某个外部插件的启用注解,即仅当Spring Boot应用项目中使用了外部组件的@Enable注解,组件才会开始自动装配;如EurekaServer服务注册中心,近当添加了@EnableEurekaServer注解后才会开启相关功能。
    其实现原理是@Enable注解通过@Import对组件相关的各种Bean注入渠道做了集成,当@Enable被使用时,所有被集成的注入渠道都会开始自动注入。
    针对上述示例中EnableOtherStarter注解的使用如下:
//该外部插件@Enable注解依次导入配置类、Bean选择器、Bean注册器
@EnableOtherStarter
// 直接导入外部插件配置类,将配置类里面的Bean统一IOC到Spring容器
@Import(OtherExecuteStarterConfig.class)
@SpringBootApplication
public class CommonLearnApplication {

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

自动装配实现原理

总体来说Spring Boot的自动装配使用了@Import(Bean选择器)的方式。
通过源码可以看出Spring Boot的启动类使用了@SpringBootApplication注解。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)

而@SpringBootApplication继承了@EnableAutoConfiguration,@EnableAutoConfiguration便是开启自动装配组件的开关注解。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

@EnableAutoConfiguration通过@Import({AutoConfigurationImportSelector.class})指定了Bean选择器,自动装配的核心代码便是在Bean选择器中实现。

通过AutoConfigurationImportSelector源码可以看出Spring Boot扫描了所有spring.factories文件中的org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的配置类列表,然后以循环添加@AutoConfiguration注解的方式逐个注入配置文件中的Bean。
spring.factories示例:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.zwj.learn.springcloud.common.mvc.config.MvcConfigs,\
com.zwj.learn.springcloud.common.shrio.config.ShiroConfig

项目中有些配置类没在spring.factories文件中制定,但是仍然可以加载,是因为引入了自动扫描的功能,譬如Mybtise组件。

posted @ 2024-03-21 14:38  周仙僧  阅读(1)  评论(0编辑  收藏  举报