ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.test();
我们很少按照上面的方式使用 Spring, 而是使用 Spring MVC, 或者 Spring Boot, 但是它们本质上都是基于这种方式的, 都需要在内部去创建一个 ApplicationContext
AnnotationConfigApplicationContext 是研究学习的主类
IoC容器加载流程可以分成两个步骤
new AnnotationConfigApplicationContext(Config.class) 的过程就是 IoC 容器的加载流程, 扫描生成 BeanDefinition, 遍历生成单例 Bean. 最后人为调用 getBean(beanName) 去缓存中拿到对应的 Bean
class -> 反射newInstance -> 原始对象 -> 依赖注入(属性赋值) -> 一堆Aware -> 初始化前(@PostConstruct) -> 初始化(InitailizedBean) -> 初始化后(AOP) -> 代理对象(代理对象.target=原始对象) -> Bean
Bean 的作用域分为单例和原型两种
懒加载的 Bean 在 IoC 容器加载时不会被创建和缓存, 在使用时才会创建和缓存
实例化时默认使用无参构造器(写了有参默认就没有无参了, 除非显式定义)
有无参构造器就使用无参构造器, 没无参构造器则判断有几个有参构造器, 只有一个的话就使用这一个有参构造器, 有多个的话因为不能确认使用哪个, 所以报错找不到默认无参构造器
有多个有参构造器的话, 也可以加 @Autowired 来指定使用哪个有参构造器
有参构造器的参数对象哪里来?
先按类型到容器中过滤, 匹配的如果只有一个则直接使用, 多个则再按名称匹配, 有匹配到的直接使用, 没有则报错
在创建 Bean 的最后一步, 会判断是否需要做 AOP, 需要则做动态代理
public class UserService {
@Autowired
private OrderService orderService;
public void test() {
sout;
}
}
class UserServiceProxy extend UserService {
UserService target;
public void test() {
// @Before 的逻辑
target.test();
// @After 的逻辑
}
}
代理类继承自原始类, 所以原始类的字段在代理类中也有, 但是 Spring 并不会为代理类做依赖注入, 因为没有必要
代理对象仅仅用于强化原始对象的某方法, 如果在切面逻辑中需要用到原始对象的依赖注入的字段, 也可以通过 JoinPoint.getTarget()
拿到原始对象来操作, 而原始对象中各字段已经做过依赖注入了
某方法添加了 @Transactional 注解后, 会在 AOP 阶段给本类生成代理类和代理对象
事务是否会失效, 就看执行 @Transactional 注解方法的是哪个对象
最常见的例子, 类中有两个事务方法 a 和 b, 而 a 中会调用 b, 单独执行 a 和 b 事务都不会失效, 但是在 a 中执行 b 时, b 的事务注解上的配置会失效
因为执行 a 的流程是这样的, 拿到类的代理对象, 执行其 a, 先走切面逻辑, 创建连接, 设置不自动提交, 然后才执行 target.a 即原始对象的 a 方法, 此时的主体是原始对象而非代理对象. 执行到 b 方法时, 本质是 this.b, 主体还是原始对象, 并没有切面逻辑, 所以在 a 里面的 b 方法的事务注解配置都会失效
当然还有很多其他原因, 需要具体分析
其他的 AOP 失效很多也是一样的原因, 都是自调用导致的
有解决办法, 就是自引用, 类中依赖注入自身 self, 此时的 self 是代理对象, 在 a 中调用 b 的时候, 用 self.b, 这样主体是代理对象, 有切面强化逻辑, b 的事务配置就会生效了
@Configuration
public class Config {
@Bean
public TransactionManager transationManager() {
return new DataSourceTransactionManager(dataSource());
}
@Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource())
}
@Bean
public DataSource dataSource() {
return new DataSource();
}
}
在 JdbcTemplate 中获取连接时, 会检查当前是否为事务环境, 是的话会从 TransactionSynchronizationManager.getResource(dataSource);
中获取线程绑定的连接, 即事务管理器创建的那个连接, 需要使用同一个数据源对象才能拿到同一个连接, 这样事务管理器的提交和回滚操作才会对 JdbcTemplate 生效
Spring 源码里有很多抽象和工具, 需要提前有一定了解, 读源码时能轻松一些
BeanDefinition 用来记录 Bean 配置的各种信息
定义 Bean 的方式有申明式和编程式两种, 通过各种方式定义的 Bean 最终都会被解析为 BeanDefinition 并缓存起来
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 生成一个BeanDefinition对象,并设置beanClass为User.class,并注册到ApplicationContext中
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(User.class);
context.registerBeanDefinition("user", beanDefinition);
System.out.println(context.getBean("user"));
用于根据某些规则将资源解析成为 BeanDefinition
可以将某个类解析成为 BeanDefinition, 包括类上的注解(@Conditional,@Scope、@Lazy、@Primary、@DependsOn、@Role、@Description)
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(context);
// 将User.class解析为BeanDefinition
reader.register(User.class);
System.out.println(context.getBean("user"));
可以解析 < bean/> 标签配置的 Bean
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(context);
int i = reader.loadBeanDefinitions("spring.xml");
System.out.println(context.getBean("user"));
扫描器, 但是它的作用和 BeanDefinitionReader 类似, 它可以进行扫描, 扫描某个包路径, 对扫描到的类进行解析
如果扫描到的类上存在 @Component 注解, 那么就会把这个类解析为一个 BeanDefinition
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.refresh();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.scan("com.coder");
System.out.println(context.getBean("userService"));
Spring 容器的根接口, Bean 工厂, 负责创建 Bean 和获取 Bean, 提供各种 getBean() 方法的定义
BeanFactory 有一个最核心的实现类 DefaultListableBeanFactory, 可以直接当作 BeanFactory 来使用, 可以替代 ApplicationContext 来使用, 就是功能会少一点而已
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(User.class);
beanFactory.registerBeanDefinition("user", beanDefinition);
System.out.println(beanFactory.getBean("user"));
DefaultListableBeanFactory 的架构体系如上, 有很多接口(能力)和类
Map aliasMap
, 在这里是 alias 与 beanName 的多对一关系, 便于从别名找到原名(别名也能起别名), 当然也可以从原名找到所有别名BeanFactory 有一个最核心的子接口 ApplicationContext, 其定义如下
public interface ApplicationContext
extends EnvironmentCapable,
ListableBeanFactory,
HierarchicalBeanFactory,
MessageSource,
ApplicationEventPublisher,
ResourcePatternResolver
ApplicationContext 的定位是 Spring 的应用上下文, 负责管理和组织应用程序的各个部分. 从代码层面来说, ApplicationContext 是一个 BeanFactory, 从架构层面来说, ApplicationContext 是比 BeanFactory 更加高级的存在, 它统御 BeanFactory, EnvironmentCapable, MessageSource 等这些组件完成相应的功能, BeanFactory 只是它的一个零件而已
照着这个思路来看, GenericApplicationContext 不继承 DefaultListableBeanFactory 而是将之作为一个属性, 从 BeanFactory 继承来的功能全部委托其持有的 DefaultListableBeanFactory 来执行, 就是非常合理的事情了
ApplicationContext 接口继承了 ListableBeanFactory 和 HierarchicalBeanFactory, 但它的定位是一个高位 BeanFactory, 只是聚焦于 BeanFactory 一定程度的基础功能即可, 并不需要中低层更强大的更加细节的全部功能
ApplicationContext 有两个重要的实现类
同样继承了 AbstractApplicationContext,但是相对于AnnotationConfigApplicationContext 而言,功能没有AnnotationConfigApplicationContext 强大,比如不能注册 BeanDefinition
@Configuration
public class MessageSourceConfig {
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource();
resourceBundleMessageSource.setDefaultEncoding("UTF-8");
resourceBundleMessageSource.setBasename("i18n.messages");
return resourceBundleMessageSource;
}
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
System.out.println(Locale.getDefault()); // zh_CN
System.out.println(context.getMessage("test", null, Locale.getDefault())); // 你好
System.out.println(context.getMessage("test", null, new Locale("zh_CN"))); // 你好
System.out.println(context.getMessage("test", null, new Locale("en_US"))); // Hello
System.out.println(context.getMessage("test", null, new Locale("de_DE"))); // 你好, 不存在, 走默认
}
# messages.properties
test = 你好啊
# messages_en_US.properties
test = Hello
# messages_zh_CN.properties
test = 你好