Spring

100 阅读8分钟

前置知识

什么是 Spring 框架

Spring 框架是 Java 应用最广的框架,它的成功来源于理念,而不是技术本身,它的理念包括 IoC (Inversion of Control,控制反转) 和 AOP(Aspect Oriented Programming,面向切面编程)。理念:每个bean与bean之间的关系统一交给 Spring IoC 容器管理。

Spring体系结构

  1. Spring Core:主要组件是BeanFactory,创建JavaBean的工厂,使用控制反转(IOC) 模式 将应用程序的配置和依赖性规范与实际的应用程序代码分开。

  2. Spring AOP:集成了面向切面的编程功能(AOP把一个业务流程分成几部分,例如权限检查、业务处理、日志记录,每个部分单独处理,然后把它们组装成完整的业务流程。每个部分被称为切面),可以将声明性事物管理集成到应用程序中。

  3. Spring Cntext:一个核心配置文件,为Spring框架提供上下文信息。

  4. Spring Do:Spring操作数据库的模块。

  5. Spring ORM:Spring集成了各种orm(object relationship mapping 对象关系映射)框架的模块,集成mybatis

  6. Spring Web集成各种优秀的web层框架的模块(Struts、Springmvc)

  7. Spring web MVC:Spring web层框架

思考一个问题 为什么启动SpringBoot项目的时候需要加上Configuration、@ComponentScan

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

看一下 @SpringBootApplication 注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

看一下 @SpringBootConfiguration 注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

@SpringBootApplication 包含 @ComponentScan 和 @SpringBootConfiguration。@SpringBootConfiguration 包含 @Configuration。

@Configuration 是为了注册bean,@CompnentScan 是为了扫描要注册的bean。

快速构建Spring环境

Maven依赖信息

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.0.RELEASE</version>
</dependency>

xml方式环境搭建

配置文件:src\main\resources\applicationContext.xml

<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
        http://www.springframework.org/schema/beans/spring-beans.xsd
      ">
    <bean id="userEntity" class="com.pjw.entity.UserEntity">
        <property name="userId" value="10"/>
        <property name="userName" value="pjw"/>
    </bean>
</beans>

UserEntity.java

public class UserEntity {
    private Integer userId;
    private String userName;

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    @Override
    public String toString() {
        return "UserEntity{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                '}';
    }
}

Main.java

public class Main {
    public static void main(String[] args) {
        // 1.读取spring配置文件,创建IOC容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 2.从SpringIoc容器获取userEntity
        UserEntity userEntity = (UserEntity) applicationContext.getBean("userEntity");
        System.out.println(userEntity);
    }
}

Spring 中注入的 bean id 重复的话会怎么样

IoC 容器启动的时候会报错

注解方式环境搭建

UserEntity.java

public class UserEntity {
    private Integer userId;
    private String userName;

    public UserEntity(Integer userId, String userName) {
        this.userId = userId;
        this.userName = userName;
    }

    @Override
    public String toString() {
        return "UserEntity{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                '}';
    }
}

MySpringConfig.java MySpringConfig 这个类也会被注册成为 bean

// @Configuration 等同于配置的spring配置文件
@Configuration
public class MySpringConfig {

    // bean id 为方法名称
    @Bean
    public UserEntity userEntity() {
        return new UserEntity(10, "pjw");
    }
}

Main.java

public class Main {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MySpringConfig.class);
        UserEntity userEntity = applicationContext.getBean("userEntity", UserEntity.class);
        System.out.println(userEntity);
    }
}

@ComponentScan用法

bean id默认为小写开头的类名,可以在@Component注解里指定 bean id

  1. @ComponentScan 会把指定包路径上包含 @Component 注解的类注册成 bean
@Configuration
@ComponentScan("com.pjw")
public class MySpringConfig { }
  1. 指定包路径上包含指定注解的类注册成 bean
@Configuration
@ComponentScan(value = "com.pjw", includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class)}, useDefaultFilters = false)
public class MySpringConfig {
    @Bean
    public UserEntity userEntity() {
        return new UserEntity(10, "pjw");
    }
}
  1. 指定包路径上排除指定注解,包含 @Component 注解的类注册成 bean
@Configuration
@ComponentScan(value = "com.pjw", excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class)}, useDefaultFilters = true)
public class MySpringConfig {
    @Bean
    public UserEntity userEntity() {
        return new UserEntity(10, "pjw");
    }
}

FilterType 有五种类型

  • ANNOTATION:注解类型

  • ASSIGNABLE_TYPE:ANNOTATION:指定的类型

  • ASPECTJ:按照Aspectj的表达式,基本上不会用到

  • REGEX:按照正则表达式

  • CUSTOM:自定义规则

获取所有 IoC 容器中的 bean

String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
    System.out.println(beanDefinitionName);
}

bean 的作用域

Spring 中 bean 默认是单例

可以使用 @Scope 来设置 bean 的作用域

@Scope 值描述
singleton全局有且仅有一个实例。
prototype每次获取Bean的时候都会有一个新的实例。
request针对每次请求都会产生一个新的Bean对象,并且该Bean对象仅在当前Http请求内有效。
session针对每次请求都会产生一个新的Bean对象,并且该Bean仅在当前Http session内有效。

bean 是什么时候创建的

单例的时候默认是在容器初始化的时候创建的。

可以使用 @Lazy 注解来设置什么时候创建。@Lazy 注解值为 true 的时候是第一次获取的时候创建,false 的话容器初始化的时候创建。

@Lazy 默认值为 true。

@Condition 根据条件判断注册 bean

public class MyCondition implements Condition{

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        // 获取当前环境配置
        Environment environment = conditionContext.getEnvironment();
        String osName = environment.getProperty("os.name");
        if (osName.equals("Windows 7")) {
            // 返回true 就是能够创建该bean
            return false;
        }
        return false;
    }
}
@Configuration
public class MySpringConfig {
    @Bean
    @Conditional(MyCondition.class)
    public UserEntity userEntity() {
        return new UserEntity(10, "pjw");
    }
}

基于 @Import 注解注册 bean

bean id 为类的全路径名称

@Configuration
@Import(UserEntity.class)
public class MySpringConfig {
}

@EnableXXX 功能性注解实现原理

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({PayEntity.class, MyPayEntity.class})
public @interface EnablePayEntity {
    // 只要启动的时候 加入该EnablePayEntity  就会将PayEntity,MyPayEntity实体类注入到spring ioc容器
    // Enable注解的话 底层 实际上在调用 @Import({PayEntity.class, MyPayEntity.class})
}
@Configuration
@EnablePayEntity
public class MyConfig {
}

基于 ImportSelector 接口注册 bean

public class MyImportSelector implements ImportSelector {
    
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"com.pjw.MemberEntity", "com.pjw.MsEntity"};
    }
}
@Configuration
@Import(MyImportSelector.class)
public class MyConfig {
}

基于 ImportBeanDefinitionRegistrar 接口注册 bean

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        // 手动注册到ioc容器中
        // spring容器中 bean 的信息是用 BeanDefinition 来描述
        BeanDefinition beanDefinition = new RootBeanDefinition(SmsEntity.class);
        // smsEntity 是 bean id
        beanDefinitionRegistry.registerBeanDefinition("smsEntity", beanDefinition);
    }
}
@Configuration
@Import(MyImportBeanDefinitionRegistrar.class)
public class MyConfig {
}

基于 FactoryBean 接口注册 bean

// 要注册的类为 MyEntity
public class MyFactoryBean implements FactoryBean<MyEntity> {
    @Override
    public MyEntity getObject() throws Exception {
        return new MyEntity();
    }

    @Override
    public Class<?> getObjectType() {
        return MyEntity.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}
@Configuration
@Import(MyFactoryBean.class)
public class MyConfig {
}

@Autowired 和 @Resource

@Autowired 和 @Resource 找 bean 的时候根据条件找到的bean必须是唯一的

@Autowired为Spring提供的注解,只按照类型查找。

@Resource为J2EE提供的注解。在该注解中:

  1. 如果同时指定了name和type,找name和type同时匹配的,找不到则抛出异常。

  2. 如果指定了name,找name匹配的,找不到则抛出异常。

  3. 如果指定了type,找type匹配的,找不到或是找到多个,都会抛出异常。

  4. 如果既没有指定name,又没有指定type,先找name匹配的,此时name是字段属性名;如果没有匹配,找type匹配的。

找name是匹配@Resource里的name属性值和bean的id。

@Primary 和 @Qualifier

public interface Animal {
    void sayHi();
}
public class Cat implements Animal{
    @Override
    public void sayHi() {
        System.out.println("cat say hi");
    }
}
public class Dog  implements Animal{
    @Override
    public void sayHi() {
        System.out.println("dog say hi");
    }
}
@RestController
public class MyController {
    @Autowired
    private Animal animal;
}

假设一个接口有两个实现类,使用 @Autowired 的话会报错。@Autowired 是按照类型查找的。

这时候有两个方案:

  1. 在 @Autowired 下面或者上面使用 @Qualifier 注解来指定 bean id

  2. 在 Dog 类 或者 Cat 类上面写 @Primary,表示优先使用这个 bean

java 子父类中的构造函数

在对子类对象进行初始化时,父类的构造函数也会运行,

那是因为子类的构造函数默认的第一行有一条隐式的语句: super();

super():会访问父类中的空参数构造函数。

而且子类中所有的构造函数默认的第一行都是:super();

为什么子类一定要访问父类中的构造函数?

因为父类中的数据子类可以直接获取,所以子类对象在建立时,需要先查看父类是如何对这些数据进行初始化的。

所以子类在对象初始化时,要先访问一下父类中的构造函数。

如果要访问父类中指定的构造函数,可以通过手动定义super语句的方式来指定。

super语句一定定义在子类构造函数的第一行。

子类中所有的构造函数,默认都会访问父类中空参数的构造函数。

因为子类每一个构造函数内的第一行都有一句隐式的super();

当父类中没有空参数的构造函数时,子类必须手动通过super或者this语句形式来指定要访问父类中的构造函数。

子类的构造函数第一行也可以手动指定this语句来访问本类中的构造函数。子类中至少会有一个构造函数会访问父类中的构造函数

Spring Bean 的生命周期

BeanFactory 和 ApplicationContext

Spring 有两个核心接口:BeanFactory 和 ApplicationContext,其中 ApplicationContext 是 BeanFactory 的子接口。他们都可代表 Spring 容器,Spring 容器是生成 Bean 实例的工厂,并且管理容器中的 Bean。

BeanFactory 负责读取 bean 配置文档,管理 bean 的加载,实例化,维护 bean 之间的依赖关系,负责 bean 的生命周期。

ApplicationContext 除了提供上述 BeanFactory 所能提供的功能之外,还提供了更完整的框架功能:

a:国际化支持

b:资源访问

c:事件传递:通过实现 ApplicationContextAware 接口

常用的获取 ApplicationContext 的方法:

a:FileSystemXmlApplicationContext:从文件系统或 url 指定的 xml 配置文件创建,参数为配置文件名或文件名数组

b:ClassPathXmlApplicationContext:从 classpath 的 xml 配置文件创建,可以从 jar 包中读取配置文件

c:WebApplicationContextUtils:从 web 应用的根目录读取配置文件,需要先在 web.xml 中配置,可以配置监听器或者 servlet 来时间

d:AnnotationConfigApplicationContext:是基于注解来使用的,它不需要配置文件,采用 java 配置类和各种注解来配置。

AnnotationConfigApplicationContext 分析

AnnotationConfigApplicationContext 是 ApplicationContext 的实现类 GenericApplicationContext 的子类。

GenericApplicationContext

public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
    // === 字段属性 ===
	private final DefaultListableBeanFactory beanFactory;
    // === 构造方法 ===
    public GenericApplicationContext() {
		this.beanFactory = new DefaultListableBeanFactory();
	}
}

AnnotationConfigApplicationContext

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {
    // === 字段属性 ===
    // 基于注解方式
	private final AnnotatedBeanDefinitionReader reader;
    // === 构造方法 ===
	public AnnotationConfigApplicationContext() {
		this.reader = new AnnotatedBeanDefinitionReader(this);
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}
	public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
		this();
		register(annotatedClasses);
		refresh();
	}
    // === 方法 ===
	public void register(Class<?>... annotatedClasses) {
		Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified");
		this.reader.register(annotatedClasses);
	}
}

AnnotatedBeanDefinitionReader

public class AnnotatedBeanDefinitionReader {
	// === 字段属性 ===
    private final BeanDefinitionRegistry registry;
    private ConditionEvaluator conditionEvaluator;
    // === 构造方法 ===
	public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
		this(registry, getOrCreateEnvironment(registry));
	}
	public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
		Assert.notNull(environment, "Environment must not be null");
		this.registry = registry;
		this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}    
}