前置知识
什么是 Spring 框架
Spring 框架是 Java 应用最广的框架,它的成功来源于理念,而不是技术本身,它的理念包括 IoC (Inversion of Control,控制反转) 和 AOP(Aspect Oriented Programming,面向切面编程)。理念:每个bean与bean之间的关系统一交给 Spring IoC 容器管理。
Spring体系结构
-
Spring Core:主要组件是BeanFactory,创建JavaBean的工厂,使用控制反转(IOC) 模式 将应用程序的配置和依赖性规范与实际的应用程序代码分开。
-
Spring AOP:集成了面向切面的编程功能(AOP把一个业务流程分成几部分,例如权限检查、业务处理、日志记录,每个部分单独处理,然后把它们组装成完整的业务流程。每个部分被称为切面),可以将声明性事物管理集成到应用程序中。
-
Spring Cntext:一个核心配置文件,为Spring框架提供上下文信息。
-
Spring Do:Spring操作数据库的模块。
-
Spring ORM:Spring集成了各种orm(object relationship mapping 对象关系映射)框架的模块,集成mybatis
-
Spring Web集成各种优秀的web层框架的模块(Struts、Springmvc)
-
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
- @ComponentScan 会把指定包路径上包含 @Component 注解的类注册成 bean
@Configuration
@ComponentScan("com.pjw")
public class MySpringConfig { }
- 指定包路径上包含指定注解的类注册成 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");
}
}
- 指定包路径上排除指定注解,包含 @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提供的注解。在该注解中:
-
如果同时指定了name和type,找name和type同时匹配的,找不到则抛出异常。
-
如果指定了name,找name匹配的,找不到则抛出异常。
-
如果指定了type,找type匹配的,找不到或是找到多个,都会抛出异常。
-
如果既没有指定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 是按照类型查找的。
这时候有两个方案:
-
在 @Autowired 下面或者上面使用 @Qualifier 注解来指定 bean id
-
在 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);
}
}