spring源码解析(一)
开始探索spring的奥秘吧:
1.什么是spring
首先,spring的官网地址:https://spring.io/projects/spring-framework
官网是这么定义的:
The Spring Framework provides a comprehensive programming and configuration model for modern Java-based enterprise applications - on any kind of deployment platform. A key element of Spring is infrastructural support at the application level: Spring focuses on the "plumbing" of enterprise applications so that teams can focus on
application-level business logic, without unnecessary ties to specific deployment environments.
翻译:Spring框架可在任何类型的部署平台上为基于Java的现代企业应用程序提供全面的编程和配置模型。
Spring的一个关键元素是在应用程序级别的基础架构支持:Spring专注于企业应用程序的“管道”,以便团队可以专注于应用程序级别的业务逻辑,而不必与特定的部署环境建立不必要的联系。
简单理解:spring一个框架,它的作用是让开发人员专注于业务逻辑的开发,只要把项目配置好之后,就可以简单的进行业务开发,大大减少了开发者的难度与工作量。
2.spring最核心的就是IOC容器:
什么是IOC容器:
官网是这么介绍的:
翻译
简而言之:IOC(控制反转)就是,将创建对象的权限,交给spring去管理。以前我们创建对象时,需要去new一个对象,比如,User user = new Use();
而现在的创建工作交由spring去帮我们创建,我们只需要在xml文件中去配置即可。
DI(依赖注入)就是,将对象注入给另一个对象。例如,汽车需要轮胎,我们需要去配置一个汽车对象,和一个轮胎对象。然后把轮胎对象,
通过构造函数注入或者参数注入(setter)等方法,将轮胎对象,传入给汽车对象。
好了。下面就开始用代码说话吧:
3.如何去创建一个spring项目呢:
(1)因为我们是源码分析,所以我们先得把spring源码导入,我这里用idea。如果还未导入的同学,可以参考我的另一篇博客:https://www.cnblogs.com/takeyblogs/p/13476233.html
(2)导入源码之后:创建一个Pig的pojo类
Pig.java
package bat.ke.qq.com.bean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; public class Pig { private int id; private String name; private int age; private Dog dog; public Dog getDog() { return dog; } public void setDog(Dog dog) { this.dog = dog; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Pig{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", dog=" + dog + '}'; } }
(3)接着我们在resources下创建一个spring.xml文件,将pig类配置在xml文件中:
spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="pig" class="bat.ke.qq.com.bean.Pig"></bean>
</beans>
(4)我们创建一个测试类,将spring容器启动,并从容器中获取pig对象
public class IOCTest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); System.out.println(context.getBean("pig")); } }
(5)最后我们可以在控制台看见pig对象的输出。
思考:通过上面的demo。spring是怎么帮我们去new这个Pig对象的呢?
上一个简单的流程图:
首先,有一个Resource的类去读取我们的spring.xml文件。再通过ResourceLoader类将我们的配置信息读取加载进内存中,
并通过BeanDefinitionRegistry注册器将这些配置注册成BeanDeifinition,存入beanDefinitionMap中。
最后通过反射创建成bean对象,存入单例对象池中,singletonObjects。
这里有一个具体的分析图:https://www.processon.com/view/link/5cd10507e4b085d010929d02
大体流程是这样。我们再通过具体的实例,一点一点的去分析源码。
4.IOC的应用
说明,我们不仅可以通过配置xml配置文件去定义bean,还可以通过一系列的注解进行装配bean,在通过beanDefinition去实例化,填充属性,最后初始化bean。
(1)bean的装配方式:
<1>xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user" class="bat.ke.qq.com.bean.User" /> </beans>
public class IOCTest01 { @Test public void testxml(){ ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); Object user = context.getBean("user"); System.out.println(user); } }
<2>@ImportResource
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user" class="bat.ke.qq.com.bean.User" /> </beans>
@ImportResource("spring.xml") public class AppConfig { }
@Test public void testAnnotation(){ ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); Object user = context.getBean("user"); System.out.println(user); }
<3>FactoryBean的使用
先看看什么是FactoryBean
官网是这么介绍的:
翻译:
简而言之:FactoryBean是一个接口,实现此接口,就必须实现它的方法:该接口包含3个方法:
T getObject() throws Exception; 获取一个对象。我们可以在该实现方法中去自定义我们想要返回的bean对象。
Class<?> getObjectType(); 返回一个对象类型,跟getObject的类型一样
default boolean isSingleton() { 是否是返回单例,默认是返回单例
return true;
}
FactoryBean的使用:
首先先创建一个MyFactoryBean去实现FactoryBean接口,并实现FactoryBean接口的方法:
@Component
public class MyFactoryBean implements FactoryBean {
public Object getObject(){
return new User();
}
public Class getObjectType(){
return User.class;
}
}
@Test public void testAnnotation(){ ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); Object user = context.getBean("myFactoryBean"); System.out.println(user); Object user2 = context.getBean("&myFactoryBean"); System.out.println(user2); }
说明:如果我们context.getBean("myFactoryBean"),会返回一个User对象。如果我们context.getBean("&myFactoryBean"),加了一个“&”,会返回FactoryBean自身的对象。
思考:那么FactoryBean和BeanFactory的区别是什么?
BeanFactory是spring中的一个基类,它创建和管理了bean。
FactoryBean是一个bean,可以返回我们自定义的bean。
<4>@Component+@ComponentScan
@ComponentScan 默认可扫描 @Component, @Repository,@Service, @Controller
可以扫描到bat.ke.qq.com.bean 包下的, @Component, @Repository,@Service, @Controller这些注解
@Component public class User{ private String name; private int age; //此处省略了getter,setter }
@ComponentScan("bat.ke.qq.com.bean") public class AppConfig { }
@Test public void testAnnotation(){ ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); Object user = context.getBean("user"); System.out.println(user); Object user2 = context.getBean("&myFactoryBean"); System.out.println(user2); }
@CompentScan 注解扩展用法:
排除用法 excludeFilters:
@ComponentScan(value = "bat.ke.qq.com.bean",excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class}),@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = {User.class})}) public class AppConfig { }
包含用法 includeFilters:
public class CustomTypeFilter implements TypeFilter { @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { ClassMetadata classMetadata = metadataReader.getClassMetadata(); System.out.println(classMetadata.getClassName()); if(classMetadata.getClassName().contains("Service")){ System.out.println("111"); return true; } return false; } }
@ComponentScan(value = "bat.ke.qq.com.bean",includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM,value = {CustomTypeFilter.class})},useDefaultFilters = false) public class AppConfig { }
<5>@Bean+@Configuration
@Configuration public class AppConfig { @Bean public User user(){ return new User(); } }
@Test public void testAnnotation(){ ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); String[] beanDefinitionNames = context.getBeanDefinitionNames(); for (String s: beanDefinitionNames) { System.out.println(s); } }
思考:配置@Configuration和不配置的区别:
不配置@Configuration: 当内部method bean发生彼此依赖的时候会导致多例
@Configuration的作用:
1.表明当前类是配置类,是方法bean的源。
2.将@Configuration配置的AppConfig的BeanDefinition属性赋值为full型,保证AppConfig类型可以转变为cglib类型。
3.将@Configuration配置的AppConfig由普通类型转变为cglib类型,最后会生成cglib代理对象,通过代理对象的方法拦截器,可以解决AppConfig内部方法bean之间发生依赖调用的时候从容器中去获取,避免了多例的出现。
<6>@Import
ImportBeanDefinitionRegistrar
public class MyImportBeanDefinitionRegistrar2 implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(User.class); registry.registerBeanDefinition("takey",rootBeanDefinition); } }
@Import(value = MyImportBeanDefinitionRegistrar2.class) public class AppConfig { }
导入一个或多个配置类
@Configuration @Import(AppConfig2.class) public class AppConfig { } @Configuration public class AppConfig2 { @Bean public User user2(){ return new User(); } }
ImportSelector:
public class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[]{"bat.ke.qq.com.bean.Cat"}; } }
@Configuration @Import(value = MyImportSelector.class) public class AppConfig {
}
应用场景:
中间件底层大量使用,和Spring集成的核心扩展技术
mybatis-spring.jar
@MapperScan
spring boot
@SpringBootApplication XXXAutoConfiguration
spring cloud
@EnableEurekaServer @EnableCircuitBreaker @EnableFeignClients @EnableZuulProxy
<7>@Conditional
@Conditional是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册 bean。
public class MyConditional implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { if(context.getBeanFactory().containsBean("user")){ return true; } return false; } }
@Configuration public class AppConfig { //@Bean //把@Bean注释掉的话,cat无法创建。必须要user先创建 public User user(){ return new User(); } @Bean @Conditional(value = MyConditional.class) public Cat cat(){ return new Cat(); } }
应用场景:
Spring boot 自动配置实现核心技术之一: 条件装配 ,
Spring Boot进行了扩展
@ConditionalOnWebApplication:当前项目是 Web项目的条件下
@ConditionalOnBean:当容器里有指定 Bean 的条件下
@ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下
@ConditionalOnClass:当类路径下有指定类的条件下
@ConditionalOnMissingClass:当类路径下没有指定类的条件下
@ConditionalOnProperty:指定的属性是否有指定的值
5.Bean的依赖注入
查找方式:
byType
byName
手动装配(手动注入)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user" class="bat.ke.qq.com.bean.User" > <property name="name" value="takey"></property> <property name="age" value="25"></property> </bean> <bean id="userService" class="bat.ke.qq.com.bean.UserService"> <!--setter--> <property name="user" ref="user"></property> <!--constructor-arg--> <!--<constructor-arg ref="user"></constructor-arg>--> </bean> </beans>
利用autowire属性改为自动装配:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user" class="bat.ke.qq.com.bean.User" > <property name="name" value="takey"></property> <property name="age" value="25"></property> </bean> <bean id="userService" class="bat.ke.qq.com.bean.UserService" autowire="constructor"> </bean> </beans>
自动装配(自动注入)注解:
@Autowired
@Autowired是spring自带的注解,通过 AutowiredAnnotationBeanPostProcessor 类实现的依 赖注入;
@Autowired是根据类型进行自动装配的,如果需要按名称进行装配,则需要配合@Qualifier;
@Autowired有个属性为required,可以配置为false,如果配置为false之后,当没有找到相应 bean的时候,系统不会抛错;
@Autowired可以作用在变量、setter方法、构造函数上。
@Component public class UserService { @Autowired private User user;
@Component public class UserService { private User user; @Autowired public void setUser(User user) { this.user = user; }
@Component public class UserService { private User user; public void setUser(User user) { this.user = user; } @Autowired public UserService(User user){ this.user=user; }
@Resource
@Resource是JSR250规范的实现,需要导入javax.annotation实现注入;
@Resource是根据名称进行自动装配的,一般会指定一个name属性,当找不到与名称匹配的 bean时才按照类型进行装配;
@Resource可以作用在变量、setter方法上。
@Inject
@Inject是JSR330 (Dependency Injection for Java)中的规范,需要导入javax.inject.Inject;实现注 入。
@Inject是根据类型进行自动装配的,如果需要按名称进行装配,则需要配合@Named;
@Inject可以作用在变量、setter方法、构造函数上。