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的生命周期
- 加载
- Bean 容器找到配置文件中 Spring Bean 的定义。
- Bean 容器利用 Java Reflection API 创建一个 Bean 的实例。
- 初始化(包含Bean自身数据状态和对象头部信息)
- 如果涉及到一些属性值 利用 set()方法设置一些属性值。
- 如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName()方法,传入 Bean 的名字。
- 如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader()方法,传入 ClassLoader对象的实例。
- 如果 Bean 实现了 BeanFactoryAware 接口,调用 setBeanFactory()方法,传入 BeanFactory对象的实例。
- 与上面的类似,如果实现了其他 *.Aware接口,就调用相应的方法。
- 回调操作
- 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessBeforeInitialization() 方法。
- 如果 Bean 实现了InitializingBean接口,执行afterPropertiesSet()方法。
- 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
- 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessAfterInitialization() 方法
- 销毁
- 当要销毁 Bean 的时候,如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。
- 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。
@Component 和 @Bean 的区别
@Bean用于配置文件中,作用于方法,通过自定义的方式生成实例托管到Spring容器中。
@Component通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中,利用的Spring的自动装配机制来加载。
Spring中的事务管理
事务管理方式
- 自定义事务管理器
可在Mybatise的配置文件中制定自定义事务管理器,核心要点是解决事务开启、提交、回滚过程
@Bean
public DataSourceTransactionManager dataSourceTransactionManager() {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource());
return dataSourceTransactionManager;
}
- 业务代码中通过使用@Transactional注解开启事务
事务的传播行为
常用的事务传播行为:
- TransactionDefinition.PROPAGATION_REQUIRED
使用的最多的一个事务传播行为,我们平时经常使用的@Transactional注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。 - TransactionDefinition.PROPAGATION_REQUIRES_NEW
创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。 - TransactionDefinition.PROPAGATION_NESTED
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。类似于分布式事务,将一个大事务拆分成多个小事务,任一小事务回滚会导致大事务回滚。事务的拆分可以提高执行效率。 - TransactionDefinition.PROPAGATION_MANDATORY如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
事务失效的场景
- 开启事务错误
没使用@Transactional注解或错误使用了Propagation.NOT_SUPPORTED传播行为 - 调用方式错误
没用通过Spring容器管理的Bean对象调用事务方法,导致AOP失效 - 业务代码异常没被捕获
业务代码自己处理的异常,但是没有抛出,导致事务没法回滚 - 事务注解的方法不支持AOP
final和static修饰的方法由于无法被AOP托管,导致事务失效 - 声明的事务捕获异常类型不匹配
如指定事务捕获的异常类型为@Transactional(rollbackFor = RuntimeException.class),但实际抛出的是Exception.class异常 - 使用的数据库不支持事务
如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);
}
}
归纳可得出如下核心步骤:
- 创建SpringApplication应用对象,该过程中完成了ApplicationContext上下文构造器的创建,同时完成了监听器的创建
- 开始run方法的执行,开始时钟,记录启动过程总耗时
- 完成环境变量的准备工作
- 完成创建初始ApplicationContext上下文
- 刷新上下文,将所有的Bean加载到上下文中
- 执行启动命令行的回调,通过回调去执行启动配置的命令参数,也可再该回调中完成数据预热的操作
监听器
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组件。