开始探索spring的奥秘吧:
1.什么是spring
.
官网是这么定义的:
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。如果还未导入的同学,可以参考我的另一篇博客:
(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方法、构造函数上。