本文共 16136 字,大约阅读时间需要 53 分钟。
2、Core Container 核心容器模块:spring的核心部分,分为4个小模块
3、AOP模块:提供对面向切面编程(AOP)的支持。spring有2种方式实现aop,一种是spring在早期版本中提供的spring aop,一种是spring在后续版本中集成的aspectj。
4、Data Access/Integration 数据访问/集成模块:提供对jdbc、orm映射框架、xml解析、消息队列、事务管理的支持。
5、Web模块:提供符对web应用的支持。
IoC的原理
IoC只是一种编程思想,所有实现了IoC的容器都叫做IoC容器,Spring容器只是IoC的其中一种实现,Spring容器≠IoC容器,只是IoC容器中的一种。
IoC容器的优势
IoC容器初始化的过程
IoC容器提供的功能
IoC容器的核心接口
这2个接口都有众多的子接口、实现类。
二者的区别:BeanFactory提供spring框架的基础功能,主要在spring框架内部使用,面向spring框架本身;ApplicationContext在BeanFactory的基础上封装了更多的基础功能,主要提供给spring的使用者调用,面向spring的使用者。
aop中的主要名词
aop的三种织入时机|方式
advice(通知|增强的种类)
1、检查所依赖的bean是否已实例化,如果未实例化,则先实例化所依赖的bean;
如果依赖的bean都已实例化,则调用bean的构造方法或工厂方法创建bean的实例2、注入所需依赖
3、如果bean实现了一些接口,则执行一些相应的方法
4、如果使用了aop,则包装实例进行增强
5、容器前处理
至此,实例创建完成,可以正常使用了
6、如果作用域是singleton,则放到Spring IoC缓存池中,触发spring容器的生命周期管理;如果指作用域是prototype,交给调用者,由调用者管理。
7、销毁实例或spring容器关闭时进行容器后处理(singleton)
bean的后置处理器BeanPostProcessor可以对bean做二次处理
/** * 1、实现 BeanPostProcessor 接口,重写2个方法。处理对象的是所有的bean,可根据beanName进行过滤。 * 2、如果bean二次处理的类有多个,还需要实现 Ordered 接口,指定这多个类执行的先后顺序 * 3、需要放到spring容器中 */@Componentpublic class Xxx implements BeanPostProcessor, Ordered { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { //... return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { //... return bean; } @Override public int getOrder() { return 0; }}
执行顺序依次为
作用域 | 描述 | 优点 | 缺点 |
---|---|---|---|
singleton 单例 (默认) | 该类在spring容器中只有一个实例,无论引用、获取多少次,使用的都是同一个对象。spring容器启动时就创建实例,由spring容器管理bean的生命周期,实例的创建、销毁均由spring容器负责。 | 不用重新生成实例,减少了新生成实例的资源开销,降低了GC频率,可以快速获取到bean。 | 不是线程安全的,多线程下访问同一个单例的bean,如果该bean存在描述状态的字段,可能出现并发问题。 |
prototype 原型 | 每次获取该类的实例时,都会重新创建一个实例。需要时(从容器中获取该类的实例时)才创建,由spring容器负责创建,创建好之后交给调用者,由调用者负责管理。 | 是线程安全的,多线程下操作的是不同的实例,不会导致线程安全问题。 | 频繁创建bean会加大内存占用,增加GC频率,慎用。 |
request | 在web项目中使用。在一次http请求中,获取的是同一个实例,该实例只在此次请求中有效。新的http请求,会创建新的实例。 | ||
session | 在web项目中使用。在一次会话中获取的是同一个实例,只在本次session中有效。新的session,会创建新的实例。 | ||
globalSession | 在web项目中使用,会为每个全局的Http Session创建一个Bean实例,此作用域仅对Portlet有效 |
@Component@Scope("prototype") //指定作用域,缺省默认为singletonpublic class Xxx { }
都可以用value属性指定bean的名称
//可指定bean的name,缺省一般默认为类名的camel写法,类名前2个字母都是大写时除外@Controller("userController")//可以指定bean的初始化、销毁方法,相当于标签。name属性的别名是value@Bean(name = "xxx", initMethod = "init", destroyMethod = "destroy")
组件扫描:自动扫描指定包下标注bean的注解。
//值是string[]@ComponentScan(basePackages = { "com.xxx.xxx1", "com.xxx.xxx2"})//注解特性:值是只有一个元素的数组时,数组可以直接写成元素形式@ComponentScan(basePackages = "com.xxx")//basePackages的别名是value,注解特性:只设置value属性时,可以直接写值@ComponentScan("com.xxx")
自动装配:IoC容器自动注入bean所需的其它bean。
自动装配的2种方式
自动装配的贪婪原则:自动装配会尽可能多的注入依赖(数据),setter注入方式会注入尽可能多的值,构造器注入方式会优先使用参数个数多的构造器。
如果自动装配时报错找不到对应的bean,常见原因如下
@Value("1") //值是String形式private int xxx;
以下3个是自动装配的注解
@Autowired(required = false) //默认required=true 容器中必需要有该类型的bean。@Autowired@Qualifier("xxx") //@Qualifier可指定beanName,未指定时默认取变量名//可指定装配方式,未指定时先按byName方式装配,如果容器中找不到指定名称的bean,则按byType方式装配@Resource(name = "xxx") @Resource(type = Xxx.class) @Resource
一个接口|类可能有多个可用的bean实例,用byType方式注入bean实例时可能会报错:存在多个可用实例。常见的解决方式有2种
循环依赖:bean之间互相依赖,形成闭环,eg. A -> B -> A,A -> B -> C -> A 。
bean的创建过程
循环依赖有2种情况
spring只解决了set注入方式的单例循环依赖。单例模式会使用三级缓存缓存bean实例,原型模式每次都是创建实例,不会缓存创建的bean的实例,三级缓存不能解决原型模式的循环依赖问题。
https://blog.csdn.net/u010853261/article/details/77940767
https://zhuanlan.zhihu.com/p/84267654
https://blog.csdn.net/weixin_38405253/article/details/113449152
https://www.cnblogs.com/leeego-123/p/12165278.html
https://www.jianshu.com/p/8bb67ca11831
https://www.cnblogs.com/tiger-fu/p/8961361.html
使用@Controller、@Service之类的注解标注bean时,bean的名称由AnnotationBeanNameGenerator类的generateBeanName()方法获取
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) { if (definition instanceof AnnotatedBeanDefinition) { String beanName = this.determineBeanNameFromAnnotation((AnnotatedBeanDefinition)definition); //如果设置了beanName,则直接使用设置的beanName if (StringUtils.hasText(beanName)) { return beanName; } } //否则使用默认的生成策略生成beanName return this.buildDefaultBeanName(definition, registry);}//默认生成策略的核心代码如下,参数name是类名,返回值是默认的beanNamepublic static String decapitalize(String name) { if (name == null || name.length() == 0) { return name; } //如果类名的前2个字母都是大写,则直接取类名作为默认的beanName if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0))){ return name; } //否则取类名的camel写法作为默认的beanName char chars[] = name.toCharArray(); chars[0] = Character.toLowerCase(chars[0]); return new String(chars);}
使用注解标注bean,如果类名的前2个字母都是大写,且用byName=类名的camel写法的方式注入该Bean,会报NoSuchBeanDefinitionException。
类名的前2个字母都是大写时
spring的核心是spring容器,spring容器的核心是负责bean的创建、管理。
spring提供了2个核心接口用于操作spring容器,也是spring容器的2种实现方式,可以把bean放到容器中,也可以从容器中获取bean
//原有的main()方法SpringApplication.run(DemoApplication.class, args);//实质是调用SpringApplication的构造方法创建spring容器,并启动容器new SpringApplication(DemoApplication.class).run(args);//调用的SpringApplication的构造方法如下public SpringApplication(Class ... primarySources) { this((ResourceLoader)null, primarySources);}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 = this.getBootstrapRegistryInitializersFromSpringFactories(); //设置应用上下文的初始化器 this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); //设置应用监听器 this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = this.deduceMainApplicationClass();}//run()方法源码,返回值是ConfigurableApplicationContextpublic ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); DefaultBootstrapContext bootstrapContext = this.createBootstrapContext(); ConfigurableApplicationContext context = null; this.configureHeadlessProperty(); SpringApplicationRunListeners listeners = this.getRunListeners(args); listeners.starting(bootstrapContext, this.mainApplicationClass); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments); this.configureIgnoreBeanInfo(environment); Banner printedBanner = this.printBanner(environment); context = this.createApplicationContext(); context.setApplicationStartup(this.applicationStartup); this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); this.refreshContext(context); this.afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch); } listeners.started(context); this.callRunners(context, applicationArguments); } catch (Throwable var10) { this.handleRunFailure(context, var10, listeners); throw new IllegalStateException(var10); } try { listeners.running(context); return context; } catch (Throwable var9) { this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null); throw new IllegalStateException(var9); }}
import lombok.extern.slf4j.Slf4j;import org.springframework.context.ApplicationContext;/** * ApplicationContext(应用上下文)常用操作的工具类 */@Slf4jpublic class ApplicationContextUtils { /** * 应用上下文的引用 */ private static ApplicationContext applicationContext; public static void setApplicationContext(ApplicationContext applicationContext) { ApplicationContextUtils.applicationContext = applicationContext; log.info("已保存应用上下文的引用"); } public static ApplicationContext getApplicationContext() { return ApplicationContextUtils.applicationContext; } /** * 通过name获取bean * * @param name beanName * @return 获取到的bean */ public static Object getBean(String name) { return applicationContext.getBean(name); } /** * 通过class获取bean * * @param clazz bean的Class对象 * @return 获取到的bean */ public staticT getBean(Class clazz) { return applicationContext.getBean(clazz); } /** * 通过name+class获取bean * * @param name beanName * @param clazz bean的Class对象 * @return 获取到的bean */ public static T getBean(String name, Class clazz) { return applicationContext.getBean(name, clazz); }}
最简单的方式
@SpringBootApplicationpublic class DemoApplication { public static void main(String[] args) { //ApplicationContext是接口,run()方法返回的ConfigurableApplicationContext是ApplicationContext的子接口 ConfigurableApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args); ApplicationContextUtils.setApplicationContext(applicationContext); }}
import lombok.extern.slf4j.Slf4j;import org.springframework.context.ApplicationContextInitializer;import org.springframework.context.ConfigurableApplicationContext;/** * 自定义的应用上下文初始化器 * 实现ApplicationContextInitializer,重写方法即可 */@Slf4jpublic class MyApplicationContextInitializer implements ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext configurableApplicationContext) { //保存应用上下文的引用 ApplicationContextUtils.setApplicationContext(configurableApplicationContext); }}
修改引导类的main()方法如下
@SpringBootApplicationpublic class DemoApplication { public static void main(String[] args) { //创建spring容器 SpringApplication application = new SpringApplication(DemoApplication.class); //添加应用上下文的初始化器。此时已经生成了应用上下文,应用上下文初始化器可以对应用上下文做进一步处理 application.addInitializers(new MyApplicationContextInitializer()); //启动容器 application.run(args); }}
监听器是观察者模式的典型应用,主题对象发生更新时会自动通知所有的观察者。
抽象类ApplicationContextEvent有4个实现类,对应应用上下文的四种事件
import lombok.extern.slf4j.Slf4j;import org.springframework.context.ApplicationListener;import org.springframework.context.event.ApplicationContextEvent;/** * 自定义的应用监听器 * 实现ApplicationListener,泛型指定要监听的应用事件 */@Slf4j// @Componentpublic class MyApplicationListener implements ApplicationListener { /** * 触发应用上下文事件后的处理 * * @param applicationContextEvent 触发的应用上下文事件 */ @Override public void onApplicationEvent(ApplicationContextEvent applicationContextEvent) { //保存应用上下文的引用 ApplicationContextUtils.setApplicationContext(applicationContextEvent.getApplicationContext()); }}
ApplicationListener可以用@Component直接放在容器中,也可以在引导类中手动添加
@SpringBootApplicationpublic class DemoApplication { public static void main(String[] args) { //创建spring容器 SpringApplication application = new SpringApplication(DemoApplication.class); //设置应用监听器 application.addListeners(new MyApplicationListener()); //启动容器 application.run(args); }}
aware 感知、察觉,类似于listener。Aware接口常见的子接口如下
import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component;/** * 自定义的ApplicationContextAware */@Component //放到容器中public class MyApplicationContextAware implements ApplicationContextAware { @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { //保存应用上下文的引用 ApplicationContextUtils.setApplicationContext(applicationContext); }}
最常用的是第三四种,通过ApplicationListener、ApplicationContextAware保存应用上下文。