Java劝退之spring系列:第一篇 spring必知必会 前言 坊间有传言,java程序员实际上是spring程序员,spring是每个java开发者必须掌握的技术。由此可见,spring的影响之大,说spring是java技术的集大成者一点也不为过。从某种角度来说,java和spring已经是互相成就的关系了,java的迭代推动了spring强大,spring的广泛使用也推动java的迭代。
说了那么多,简言之,学java,那就学spring,spring的深度和广度都值得每一个java开发者去研究。
本人在学习和使用spring框架一段时间后,用本系列记录和总结自己的使用心得,通过知识点配合代码的方式来呈现,目的是加深理解,方便回忆,最好能让没接触过spring的小伙伴照着学习后,也能够快速入门。
spring是什么 spring一般说的是spring framework,它是spring家族强大生态下的一个项目,其他的还有诸如springboot,springcloud等等,本文用spring代指spring framework。作为系列的开篇,只展开介绍spring的核心部分,
即spring作为容器的部分。
spring作为容器 几乎每篇介绍spring的文章都会提到spring作为IOC容器的特性,可见,这必是spring的核心所在。把组件交给spring管理,spring把组件存起来,如同一个容器一般。这很好理解,但是,从源码角度直观的感受下:
类DefaultSingletonBeanRegistry
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { protected static final Object NULL_OBJECT = new Object(); protected final Log logger = LogFactory.getLog(getClass()); private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256 ); private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16 ); private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16 ); private final Set<String> registeredSingletons = new LinkedHashSet<String>(256 ); private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16 )); private final Set<String> inCreationCheckExclusions = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16 )); private Set<Exception> suppressedExceptions; private boolean singletonsCurrentlyInDestruction = false ; private final Map<String, Object> disposableBeans = new LinkedHashMap<String, Object>(); private final Map<String, Set<String>> containedBeanMap = new ConcurrentHashMap<String, Set<String>>(16 ); private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<String, Set<String>>(64 ); private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<String, Set<String>>(64 ); }
由此可见,容器就是通过一个个map来保存组件的。
为什么是DefaultSingletonBeanRegistry这个类来保存组件的呢?
其中的设计思想我不理解,但是这里通过源码角度分析下其继承关系
一般而言,我们通过注解方式来使用spring,使用的上下文是AnnotationConfigApplicationContext 这个类
这个类的继承关系如下:
当我们启动容器的时候,会经历刷新容器的阶段,其中有一个步骤是获bean factory:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public void refresh () throws BeansException, IllegalStateException { synchronized (this .startupShutdownMonitor) { prepareRefresh(); ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); prepareBeanFactory(beanFactory); }
获取bean factory实际就是获取父类GenericApplicationContext 的成员变量bean factory,其所属类是DefaultListableBeanFactory
DefaultListableBeanFactory 的继承关系如下
DefaultListableBeanFactory 是DefaultSingletonBeanRegistry 的一个子类。
由此可见,总结一下:
AnnotationConfigApplicationContext 这个是实际使用的上下文,其有着成员变量DefaultListableBeanFactory ,这个成员变量是DefaultSingletonBeanRegistry 的子类。DefaultSingletonBeanRegistry 有着许多的map来存放组件,所以最终得出AnnotationConfigApplicationContext 也是通过这些map来存放组件的。
IOC容器使用实战 接下来是如何使用spring作为ioc容器,从以下几个方面记录下:
作为IOC容器是怎么使用的
怎么将类交给spring管理
spring管理的组件怎么赋值,怎么解决依赖问题
注意:本文使用注解方式使用spring,spring的版本为4.3.12
spring管理组件 为了演示这个功能,创建三个类:测试类,配置类,实体类
使用@Bean方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class Person { private String name; private String age; public String getName () { return name; } public void setName (String name) { this .name = name; } public String getAge () { return age; } public void setAge (String age) { this .age = age; } @Override public String toString () { return "Person{" + "name='" + name + '\'' + ", age='" + age + '\'' + '}' ; } }
1 2 3 4 5 6 7 8 @Configuration public class MainConfig { @Bean public Person person () { return new Person(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class Test { @org .junit.Test public void testIOC () { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class); String[] definitionNames = applicationContext.getBeanDefinitionNames(); System.out.println("容器种所有的组件名称如下:" ); List<String> names = Arrays.asList(definitionNames); for (String name : names) { System.out.println(name); } System.out.println("------------------------" ); System.out.println("获取person组件" ); Person person = applicationContext.getBean("person" , Person.class); System.out.println("person = " + person); } } 容器种所有的组件名称如下: org.springframework.context.annotation.internalConfigurationAnnotationProcessor org.springframework.context.annotation.internalAutowiredAnnotationProcessor org.springframework.context.annotation.internalRequiredAnnotationProcessor org.springframework.context.annotation.internalCommonAnnotationProcessor org.springframework.context.event.internalEventListenerProcessor org.springframework.context.event.internalEventListenerFactory mainConfig person ------------------------ 获取person组件 person = Person{name='null' , age='null' }
从控制台输出可以看出,spring容器会自己注入一些组件
使用扫描注解@ComponenScan 在实体类上标上@Component
1 2 3 4 @Component public class Person { }
配置类
1 2 3 4 @Configuration @ComponentScan(basePackages = {"com.hhdd.bean"}) public class MainConfig2 {}
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @org .junit.Testpublic void test2 () { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class); String[] definitionNames = applicationContext.getBeanDefinitionNames(); System.out.println("容器种所有的组件名称如下:" ); List<String> names = Arrays.asList(definitionNames); for (String name : names) { System.out.println(name); } System.out.println("------------------------" ); System.out.println("获取person组件" ); Person person = applicationContext.getBean("person" , Person.class); System.out.println("person = " + person); } 容器种所有的组件名称如下: org.springframework.context.annotation.internalConfigurationAnnotationProcessor org.springframework.context.annotation.internalAutowiredAnnotationProcessor org.springframework.context.annotation.internalRequiredAnnotationProcessor org.springframework.context.annotation.internalCommonAnnotationProcessor org.springframework.context.event.internalEventListenerProcessor org.springframework.context.event.internalEventListenerFactory mainConfig person ------------------------ 获取person组件 person = Person{name='null' , age='null' }
@import方式 直接引入类 1 2 3 4 5 @Configuration @Import({Person.class}) public class MainConfig3 {}
此时就不需要在Person类上标注@Component注解了
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @org .junit.Test public void test3 () { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig3.class); String[] definitionNames = applicationContext.getBeanDefinitionNames(); System.out.println("容器种所有的组件名称如下:" ); List<String> names = Arrays.asList(definitionNames); for (String name : names) { System.out.println(name); } System.out.println("------------------------" ); System.out.println("获取person组件" ); Person person = applicationContext.getBean(Person.class); System.out.println("person = " + person); } } 容器种所有的组件名称如下: org.springframework.context.annotation.internalConfigurationAnnotationProcessor org.springframework.context.annotation.internalAutowiredAnnotationProcessor org.springframework.context.annotation.internalRequiredAnnotationProcessor org.springframework.context.annotation.internalCommonAnnotationProcessor org.springframework.context.event.internalEventListenerProcessor org.springframework.context.event.internalEventListenerFactory mainConfig3 com.hhdd.bean.Person ------------------------ 获取person组件 person = Person{name='null' , age='null' }
可见,通过这种方式,注入的组件名称是全路径类名com.hhdd.bean.Person
importSelector方式 实现这个接口,返回需要导入的组件的全类名
1 2 3 4 5 6 public class MyImportSelector implements ImportSelector { public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[]{"com.hhdd.bean.Person" }; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @org .junit.Testpublic void test4 () { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig4.class); String[] definitionNames = applicationContext.getBeanDefinitionNames(); System.out.println("容器种所有的组件名称如下:" ); List<String> names = Arrays.asList(definitionNames); for (String name : names) { System.out.println(name); } System.out.println("------------------------" ); System.out.println("获取person组件" ); Person person = applicationContext.getBean(Person.class); System.out.println("person = " + person); }
ImportBeanDefinitionRegistrar 实现这个接口,直接拿registrar给容器种注册对象
1 2 3 4 5 6 7 8 9 public class MyBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { public void registerBeanDefinitions (AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { boolean res = registry.containsBeanDefinition("person" ); if (!res) { RootBeanDefinition beanDefinition = new RootBeanDefinition(Person.class); registry.registerBeanDefinition("person" , beanDefinition); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @org .junit.Testpublic void test5 () { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig5.class); String[] definitionNames = applicationContext.getBeanDefinitionNames(); System.out.println("容器种所有的组件名称如下:" ); List<String> names = Arrays.asList(definitionNames); for (String name : names) { System.out.println(name); } System.out.println("------------------------" ); System.out.println("获取person组件" ); Person person = applicationContext.getBean(Person.class); System.out.println("person = " + person); }
总结 以上就是spring注入bean的最最基本也最常见的几种方式,更多信息请查阅spring的官方文档
给组件赋值 @Value 此注解可以给组件的基本成员变量赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @Component public class Person2 { @Value("${person.name}") private String name; @Value("#{20-2}") private String age; @Value("male") private String sex; public String getName () { return name; } public void setName (String name) { this .name = name; } public String getAge () { return age; } public void setAge (String age) { this .age = age; } public String getSex () { return sex; } public void setSex (String sex) { this .sex = sex; } @Override public String toString () { return "Person2{" + "name='" + name + '\'' + ", age='" + age + '\'' + ", sex='" + sex + '\'' + '}' ; } }
1 2 3 4 5 6 @Configuration @ComponentScan("com.hhdd.bean") @PropertySource(value = {"classpath:/person.properties"}) public class DIConfig {}
测试类
1 2 3 4 5 6 7 8 9 10 @org .junit.Testpublic void test6 () { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(DIConfig.class); System.out.println("--------获取Person2组件-----------" ); Person2 person = applicationContext.getBean(Person2.class); System.out.println("person = " + person); }
@AutoWired spring利用依赖注入DI,完成对ioc容器中各个组件依赖关系的赋值
@Autowired 注入的基本规则
默认优先按照类型去容器中找对应的组件
如果找到多个相同类型的组件,再将属性名称作为组件id去容器中找
@Qualifier(BeanId)明确指出要装配的bean
自动装配默认必须要在容器中找到依赖,否则报错;可通过属性required = false取消这个特性
这个注解可以在的位置:构造器,参数,方法,属性
示例,假设有实体类Student Money Dog Book
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 @Component public class Student { @Autowired private Book book; private Dog dog; private Money money; @Autowired public Student (Money money) { this .money = money; } public Book getBook () { return book; } public void setBook (Book book) { this .book = book; } public Dog getDog () { return dog; } @Autowired public void setDog (Dog dog) { this .dog = dog; } public Money getMoney () { return money; } public void setMoney (Money money) { this .money = money; } @Override public String toString () { return "Student{" + "book=" + book + ", dog=" + dog + ", money=" + money + '}' ; } }
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @org .junit.Testpublic void test7 () { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(DIConfig.class); System.out.println("-------获取student组件-----------" ); Student student = applicationContext.getBean(Student.class); System.out.println("student = " + student); } -------获取student组件----------- student = Student{book=com.hhdd.bean.Book@163e4e87, dog=com.hhdd.bean.Dog@56de5251, money=com.hhdd.bean.Money@419c5f1a}
最后 本文简单示例介绍了spring中管理组件和给组件赋值的功能,所有的示例代码已经放在github地址:spring-practice
有任何问题欢迎评论区留言,本人创建了一个java交流群:624017389,任何热爱技术的小伙伴都可以加入探讨技术。
后期,会继续更新spring系列的笔记总结,会更加深入,主要是原理方向的东西了。