一、Spring中注册和获取对象的步骤
在Spring中通常会使用如下的方法去注册Bean以及获取Bean
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();
只需要在xml中做如下配置对象就可以注册到容器中
<bean id="userService" class="com.milkcoffee.service.UserService"/>
Spring的功能看上去是不是非常强大,只要把需要管理对象配置在xml中在整个应用中想如何使用就如何使用,但是这里回去思考那这个对象为何这样就可以被Spring的管理起来了呢?其实上述的这种用法是非常古老的使用方法,在经常使用的SpringMVC以及SpringBoot中使用的是基于注解,下面会讲述基于注解的是如何使用的。
二、SpringBoot和SpringMVC中注册和获取对象的步骤
在SpringBoot中通常会使用如下方式来注册和获取对象
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) applicationContext.getBean("userService");
userService.test();
然后在配置类上只需要需要扫描的包路径如下所示
@ComponentScan("com.milkcoffee")
public class AppConfig {
}
这样需要被Spring管理的Bean就会被注册到Spring容器在使用时直接从容器获取到对象然后直接使用。
看了上述的逻辑总结起来就是注册对象、获取对象、方法调用。
三、Spring中是如何创建一个对象的呢?
- 从上面的两段逻辑中看到并没有显式去创建对象,那对象是在哪儿创建的呢?答案很简单,就使用配置类方法作为案例。可能会在执行new AnnotationConfigApplicationContext()时候去创建对象也可能在getBean的时候去创建对象。
- 那对象生成简单的流程就可以这样去描述:
- a:由于传递进去的是一个配置在配置类上指定需要扫描的包路径,在Spring里面可以根据类的元信息获取这个配置类指定的扫描路径
- b:根据第一步获取到的扫描路径去扫描在target目录中所有的类,那Spring又是如何知道要将一个类扫描为被管理的对象呢?那这个肯定是需要特殊标识的,在Spring里面可以使用@Component、@Service等这些注解标注一个类需要被Spring管理。在扫描完成完成之后将扫描的信息保存起来那如何保存呢?为了方便后面使用是保存在Map中key为Bean名称value为在Spring描述一个Bean的对象。
- c: 在调用getBean时根据的在编写类时指定的规则去生成对象。
- 在上面的步骤中描述一个Bean的对象在扫描完成之后被保存了起来,其实这个就是Spring中的BeanDefinition,那在获取到BeanDefinition之后是如何去生成一个完整的对象并且交于Spring管理的呢
- a: 加载类,在Java中创建一个对象是需要把类加载到jvm的方法区,那第一步就是根据BeanDefinition加载类。
- b: 推断构造方法,在Java中创建一个对象的方法有很多种吗,在Spring中是使用反射调用构造方法的方式去创建对象,到这里会存在一个问题?当在类中没有指定构造方法的数据都是通过默认的构造方法去实现的。但是在一个类中出现如下的情况Spring会如何去选择呢?
1: 只有一个构造方法但是不是无参数构造
@Service
public class OrderService {
public void testOrderService(){
System.out.println("This is OrderService");
}
}
@Component
public class UserService {
private OrderService orderService;
public UserService(OrderService orderService){
this.orderService = orderService;
}
public void test1(){
orderService.testOrderService();
}
}
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) applicationContext.getBean("userService");
userService.test1();
}
// 最终的输出结果是:This is OrderService
// 因此在只有一个构造方法时不管是有参构造还是无参构造都会使用这个构造方法
2:存在多个不包括无参构造
@Component
public class UserService {
private OrderService orderService;
public UserService(OrderService orderService){
this.orderService = orderService;
}
public UserService(OrderService orderService,OrderService orderService1){
this.orderService = orderService;
}
public void test1(){
orderService.testOrderService();
}
}
// 执行结果:Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.milkcoffee.service.UserService]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.milkcoffee.service.UserService.<init>().存在多个但是不包含无参数构造时会出错
3: 存在多个但是包含无参数构造
public class UserService {
private OrderService orderService;
public UserService(){}
public UserService(OrderService orderService){
this.orderService = orderService;
}
public UserService(OrderService orderService,OrderService orderService1){
this.orderService = orderService;
}
public void test1(){
orderService.testOrderService();
}
}
// 结果正常运行
选择构造函数的逻辑如下:
- c: 依赖注入,通过上述步骤对象已经被创建完成,但是这个对象里面的属性依然是null因为没有做属性赋值的操作。那Spring是如何知道需要对类中某个属性进行赋值呢?其实就是找在属性上有@AutoWired以及@Resource这样的属性进行赋值。这个是程序猿表名这个是需要Spring帮助进行依赖注入对象
- d: Aware回调,执行Aware相关的回调,通过instanceof判断Bean是否实现了如下的接口如:BeanNameAware、BeanClassLoaderAware、BeanFactoryAware如果实现了这些接口那么将会将对象进行强转之后调用对应的方法,简单描述就是这些接口是为了开发者可以更加方便的根据需求修改Bean的属性
- e 初始化前,Aware回调完成,判断对象中某个方法是被@PostConstruct注解修饰,假如被这个注解修饰直接调用该方法,执行初始化之前相关操作
- f 初始化,初始化之前方法执行完成,判断对象是否实现InitializingBean接口,假如实现调用方法afterPropertiesSet()
- g aop生成对象 判断对象是否需要进行aop假如对象不需要进行aop直接返回对象或者扔到单列池,需要进行aop生成代理对象再把代理对象返回或者放到单例池。
- h 上述的步骤可以通过的如下的流程图描述出来
到这里为止Spring中一个对象创建的大概的流程就已经完成了,但是还有很多细节需要去补充。