总览:
首先BeanFactory会读取配置文件,创建一个BeanDefinition(封装了要实例化的bean的信息,比如是否单例,依赖哪些类),然后以配置文件中定义的名称为键,BeanDefinition为值put进一个BeanDefinitionMap中,然后遍历这个Map集合将bean实例化,随后将bean初始化,最后加入到单例池中。
BeanDefinition
BeanDefinition封装了bean的信息,包括作用域范围,类信息,属性值集合等等,下面是BeanDefinition接口的部分源码:
public interface BeanDefinition extends AttributeMap, BeanMetadataElement {
// 获取Bean的Class类型
Class<?> getBeanClass();
// 设置Bean的Class类型
void setBeanClass(Class<?> beanClass);
// 获取Bean的属性值集合
PropertyValues getPropertyValues();
// 设置Bean的属性值集合
void setPropertyValues(PropertyValues pvs);
// 是否为抽象Bean
boolean isAbstract();
// 设置是否为抽象Bean
void setAbstract(boolean abstractBean);
// 获取Bean的作用域
String getScope();
// 设置Bean的作用域
void setScope(String scope);
// 获取Bean的初始化方法
String getInitMethodName();
// 设置Bean的初始化方法
void setInitMethodName(String initMethodName);
// 获取Bean的销毁方法
String getDestroyMethodName();
// 设置Bean的销毁方法
void setDestroyMethodName(String destroyMethodName);
// 是否为懒加载
boolean isLazyInit();
// 设置是否为懒加载
void setLazyInit(boolean lazyInit);
// ...
}
BeanfactoryPostProcess
这个接口是Spring框架对外开放的一个作用于beanFactory存入了BeanDefinition信息后的处理接口,有了这个接口我们可以修改BeanDefinitionMap中的信息,同时也可以再次向BeanDefinitionMap中添加新的BeanDefinition,其实基于注解来配置Bean,比如使用@Component注解,我们就可以将该被修饰的类注入到Bean工厂中,下面可以做一个有趣的实验——怎么定义一个自己的@MyComponent注解:
1.定义注解
@Target(ElementType.TYPE)//修饰类的注解
@Retention(RetentionPolicy.RUNTIME)//运行时生效
public @interface MyComponent {
String value() default "";
}
2.引入扫描工具
public class ComponentScanner {
/**
* 扫描指定包及其子包下所有被 @MyComponent 注解的类。
*
* @param packageName 要扫描的包名
* @return 一个 Map,键是注解中定义的类名,值是实际的类
*/
public static Map<String, Class<?>> scanPackageForMyComponents(String packageName) {
Map<String, Class<?>> components = new HashMap<>();
try {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
String path = packageName.replace('.', '/');
Enumeration<URL> resources = classLoader.getResources(path);
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
String protocol = resource.getProtocol();
if ("file".equals(protocol)) {
scanDirectory(new File(resource.toURI()), packageName, components);
} else if ("jar".equals(protocol)) {
scanJar(resource, packageName, components);
}
}
} catch (IOException | URISyntaxException e) {
throw new RuntimeException("Error scanning package", e);
}
return components;
}
private static void scanDirectory(File directory, String packageName, Map<String, Class<?>> components) {
for (File file : directory.listFiles()) {
if (file.isDirectory()) {
scanDirectory(file, packageName + "." + file.getName(), components);
} else if (file.getName().endsWith(".class")) {
try {
String className = packageName + '.' + file.getName().substring(0, file.getName().length() - 6);
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(MyComponent.class)) {
MyComponent annotation = clazz.getAnnotation(MyComponent.class);
String componentName = annotation.value().isEmpty() ? clazz.getSimpleName() : annotation.value();
components.put(componentName, clazz);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
private static void scanJar(URL jarUrl, String packageName, Map<String, Class<?>> components) throws IOException {
try (JarFile jarFile = new JarFile(jarUrl.getFile())) {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String entryName = entry.getName();
if (!entry.isDirectory() && entryName.endsWith(".class") && entryName.startsWith(packageName.replace('.', '/'))) {
String className = entryName.substring(0, entryName.length() - 6).replace('/', '.');
try {
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(MyComponent.class)) {
MyComponent annotation = clazz.getAnnotation(MyComponent.class);
String componentName = annotation.value().isEmpty() ? clazz.getSimpleName() : annotation.value();
components.put(componentName, clazz);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
// 测试方法
public static void main(String[] args) {
Map<String, Class<?>> components = scanPackageForMyComponents("com.example.package");
components.forEach((key, value) -> System.out.println(key + ": " + value.getName()));
}
}
3.实现BeanDefinitionRegistryPostProcessor接口下的方法
@Component
public class MyBeanFactoryPostProcess implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// 通过扫描包及其子包下的所有类, 收获使用@MyComponent的注解的类
Map<String, Class<?>> myComponentAnnotationMap = ComponentScanner.scanPackageForMyComponents("com.spring.springdemo.bean");
// 迭代Map, 组装BeanDefinition进行注册
myComponentAnnotationMap.forEach((beanName, clazz) -> {
// 获取beanClassName
String beanClassName = clazz.getName(); // com.spring.springdemo.bean.Car
// 创建BeanDefinition
BeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClassName(beanClassName);
registry.registerBeanDefinition(beanClassName, beanDefinition);
});
}
}
ps:注意一定要把这个实现类添加到Spring容器中才能生效
4.使用注解
@MyComponent("car")
public class Car implements BeanPostProcessor {
}
5.测试
class SpringDemoApplicationTests {
@Resource
Car car;
@Test
void contextLoads() {
System.out.println(car);
}
}
运行结果如下,可以看到Car能被正常注入:
Bean的实例化
在Spring框架中,创建Bean的方法主要有三种:
- 使用无参构造器:Spring会调用Bean类中的无参构造器来创建Bean实例。
- 使用有参构造器:Spring会调用Bean类中的带有参数的构造器,并且根据
BeanDefinition
中的信息来确定构造器的参数值。 - 使用工厂方法:Spring可以使用静态工厂方法或实例工厂方法来创建Bean实例。
下面详细介绍这三种方法:
1. 使用无参构造器
如果Bean类定义了一个无参构造器,Spring会直接调用它来创建Bean实例。
public class MyService {
private String message;
public MyService() {
// 无参构造器
}
public void setMessage(String message) {
this.message = message;
}
public void doSomething() {
System.out.println(message);
}
}
2. 使用有参构造器
如果Bean类定义了一个或多个带有参数的构造器,Spring会根据BeanDefinition
中的信息来选择合适的构造器,并传递所需的参数。
public class MyService {
private String message;
public MyService(String message) {
this.message = message;
}
public void doSomething() {
System.out.println(message);
}
}
配置xml
<bean id="myService" class="com.example.MyService">
<constructor-arg value="Hello, World!" />
</bean>
3. 使用工厂方法
Spring还可以使用工厂方法来创建Bean实例。工厂方法可以是静态方法,也可以是实例方法。
静态工厂方法
public class MyServiceFactory {
public static MyService createMyService(String message) {
return new MyService(message);
}
}
<bean id="myService" class="com.example.MyServiceFactory">
<constructor-arg value="Hello, World!" />
<factory-method>createMyService</factory-method>
</bean>
实例工厂方法
public class MyServiceFactory {
public MyService createMyService(String message) {
return new MyService(message);
}
}
<bean id="myServiceFactory" class="com.example.MyServiceFactory" />
<bean id="myService" factory-bean="myServiceFactory" factory-method="createMyService">
<constructor-arg value="Hello, World!" />
</bean>
BeanPostProcess
这个接口是作用于Bean被实例化后的初始化的前后,这个过程中我们可以使用BeanPostProcess来实现一个仿AOP。
1.创建目标类
public interface MyService {
void doSomething();
}
public class MyServiceImpl implements MyService {
@Override
public void doSomething() {
System.out.println("Doing something...");
}
}
2.创建切面
public class LoggingAspect implements MethodBeforeAdvice, AfterReturningAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("Before method: " + method.getName());
}
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("After method: " + method.getName());
}
}
3.实现接口
public class AspectJProxyCreator implements BeanPostProcessor {
private final LoggingAspect aspect;
public AspectJProxyCreator(LoggingAspect aspect) {
this.aspect = aspect;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean; // 不做任何处理,直接返回原始 Bean
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof MyService) {
return Proxy.newProxyInstance(
bean.getClass().getClassLoader(),
bean.getClass().getInterfaces(),
(proxy, method, args) -> {
aspect.before(method, args, bean);
Object result = method.invoke(bean, args);
aspect.afterReturning(result, method, args, bean);
return result;
}
);
} else {
return bean; // 如果不是 MyService 的实例,则直接返回 Bean
}
}
}
4.配置上下文
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
@Bean
public LoggingAspect loggingAspect() {
return new LoggingAspect();
}
@Bean
public AspectJProxyCreator aspectJProxyCreator(LoggingAspect aspect) {
return new AspectJProxyCreator(aspect);
}
}
4.测试
public class Application {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyService service = context.getBean(MyService.class);
service.doSomething();
}
}
运行结果:
依赖注入
进行到了初始化阶段就需要进行依赖注入,Spring的依赖注入是一种设计模式(简称DI),其在在运行时自动注入的特性降低了代码耦合度同时还增加了代码的可扩展性,在Spring框架中,主要的注入方式如下:
- 构造器注入
- 属性注入
- 字段注入
1. 构造器注入(Constructor Injection)
构造器注入是指通过构造器来注入依赖项。这种方式的好处在于它能够确保依赖项在对象创建时就已经被正确设置,而且可以避免由于依赖项未被设置而导致的空指针异常。
public class MyService {
private final DataSource dataSource;
public MyService(DataSource dataSource) {
this.dataSource = dataSource;
}
// ... methods
}
<bean id="myService" class="com.example.MyService">
<constructor-arg ref="dataSource" />
</bean>
<bean id="dataSource" class="com.example.DataSource" />
2. 属性注入(Setter Injection)
属性注入是指通过setter方法来注入依赖项。这种方式的好处是它允许在对象创建之后再注入依赖项,使得对象本身更加灵活。
public class MyService {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
// ... methods
}
<bean id="myService" class="com.example.MyService">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="dataSource" class="com.example.DataSource" />
3. 字段注入(Field Injection)
字段注入是指直接通过字段(通常是私有的,并使用@Autowired或@Resource
注解)来注入依赖项。这种方式简单易用,但可能会降低代码的可读性和可维护性,因为依赖关系不是通过构造器或setter方法明确表达的。
@Component
public class MyService {
@Autowired
private DataSource dataSource;
// ... methods
}
三级缓存解决循环依赖
Spring框架中的循环依赖问题是指两个或多个Bean之间互相依赖的情况。为了处理这种情况,Spring采用了三级缓存机制来解决循环依赖问题。这三级缓存分别是:
- 一级缓存:用于存放正在创建的Bean实例。
- 二级缓存:用于存放已经完成填充依赖的Bean实例。
- 三级缓存:用于存放完全初始化完成后的Bean实例。
解决循环依赖的过程
假设我们有两个Bean,BeanA
和BeanB
,它们相互依赖:
public class BeanA {
private BeanB beanB;
public BeanA(BeanB beanB) {
this.beanB = beanB;
}
}
public class BeanB {
private BeanA beanA;
public BeanB(BeanA beanA) {
this.beanA = beanA;
}
}
-
创建BeanA:
- Spring开始创建
BeanA
,发现BeanB
尚未创建,于是尝试创建BeanB
。 - 在创建
BeanB
的过程中,Spring会检查一级缓存中是否有BeanA
的实例。 - 如果没有,Spring会创建
BeanA
的实例,并将其放入一级缓存中。 - 因此,在创建
BeanB
的过程中,Spring可以从一级缓存中获取到BeanA
的实例。
- Spring开始创建
-
创建BeanB:
- 创建
BeanB
的过程中,Spring会从一级缓存中获取BeanA
的实例,并完成BeanB
的依赖注入。 BeanB
完成依赖注入后,被放入二级缓存中。
- 创建
-
完成BeanA的创建:
- 在创建
BeanA
的过程中,Spring从二级缓存中获取BeanB
的实例,并完成BeanA
的依赖注入。 BeanA
完成依赖注入后,也被放入二级缓存中。
- 在创建
-
初始化BeanB:
BeanB
从二级缓存中取出,执行初始化方法。- 完成初始化后,
BeanB
被放入三级缓存中。
-
初始化BeanA:
BeanA
从二级缓存中取出,执行初始化方法。- 完成初始化后,
BeanA
被放入三级缓存中。
单例池
单例池的概念
- 单例池:Spring容器维护的一个内部缓存,用于存储所有
singleton
作用域的Bean实例。 - 单例Bean:指在Spring容器中只有一个实例的Bean,每次请求该Bean时,Spring容器都会返回同一个实例。
非单例Bean的管理
对于非singleton
作用域的Bean,例如prototype
、request
、session
或global session
作用域的Bean,它们的管理方式如下:
- Prototype Scope:对于每次请求,Spring都会创建一个新的Bean实例。这意味着每次调用这些Bean时,都会创建一个新的实例,因此它们不会被加入到单例池中。
- Request Scope:在Web环境中,对于每个HTTP请求,Spring都会创建一个新的Bean实例。这些Bean与每个请求相关联,每个请求结束时这些Bean实例也会被销毁。
- Session Scope:同样在Web环境中,对于每个HTTP Session,Spring都会创建一个新的Bean实例。这些Bean与用户的会话相关联,当会话结束时,这些Bean实例也会被销毁。
- Global Session Scope:在portlet环境中,对于每个全局会话,Spring都会创建一个新的Bean实例。这些Bean与全局会话相关联,当全局会话结束时,这些Bean实例也会被销毁。