注:此笔记为尚硅谷Spring注解驱动教程(雷丰阳源码级讲解)学习笔记,并同时参考[https://blog.csdn.net/xjhqre/article/details/123264069]博主文章,其中包含个人的笔记和理解,仅做学习笔记之用。
整个专栏分成了三个大的部分,分别是:容器、扩展原理以及Web
容器作为整个专栏的第一大部分,内容包括:
扩展原理作为整个专栏的第二大部分,内容包括:
Web作为整个专栏的第三大部分,内容包括:
这部分,其实就是SpringMVC,在这个部分中,我们会重点来说异步请求。
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.13version>
dependency>
在 resources 目录下创建 application.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">
beans>
public class Person {
private String name;
private Integer age;
// 省略构造方法,get/set方法,toString方法
}
<bean id="person" class="com.kdz.pojo.Person">
<property name="name" value="kdz"/>
<property name="age" value="18"/>
bean>
public class SpringTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
Person person = (Person) applicationContext.getBean("person");
System.out.println(person);
}
}
在方法上面加上@Bean注解后,Spring会以方法返回类型作为组件的类型,方法名作为组件的 id
当向@Bean中添加参数时,默认添加的第一个参数是value,强制给组件赋值id
// 配置类等同于xml配置文件
@Configuration // 告诉Spring这是一个配置类
public class MainConfig {
// 给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id
@Bean("person")
public Person person01(){
return new Person("lisi", 20);
}
}
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
Person bean = applicationContext.getBean(Person.class);
System.out.println(bean);
}
只要标注了@Controller、@Service、@Repository、@Component的,都会被扫描加入到容器里
注意:配置类自身也会被扫描到容器中,如果存在多个配置类,则多个配置类里的所有bean对象都会被扫描进容器中
<context:component-scan base-package="com.kdz"/>
在配置类上添加注解@ComponentScan
@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz")
public class MainConfig {
// ...
}
注解排除excludeFilters的返回类型为Filter[]
Filter 的排除类型 FilterType 有一下几种:
ANNOTATION:按注解排除,常用
ASSIGNABLE_TYPE:按类型排除,常用
ASPECTJ:按AspectJ 类型模式表达式排除,不常用
REGEX:按正则表达式排除
CUSTOM:自定义排除
只包含过滤规则生效,需要禁用默认的规则
扫描的时候只需要包含哪些组件,编写方式和excludeFilters
一样
注意要关闭默认的扫描过滤器
@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz", includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})
}, useDefaultFilters = false)
public class MainConfig {
// ...
}
在 jdk8 之后可以写多个@ComponentScans,写法如下:
@Configuration // 告诉Spring这是一个配置类
@ComponentScans(
value = {
@ComponentScan(value = "com.kdz", includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})
}, useDefaultFilters = false)
}
)
public class MainConfig {
// ...
}
@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz", includeFilters = {
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookController.class})
}, useDefaultFilters = false)
public class MainConfig {
// ...
}
public class MyTypeFilter implements TypeFilter {
/**
* @param metadataReader 读取到当前正在扫描类的信息
* @param metadataReaderFactory 可以获取其他任何类的信息
*/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
// 获取当前类注解的信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
// 获取当前正在扫描类的信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
// 获取当前类的资源信息(类的路径)
Resource resource = metadataReader.getResource();
String className = classMetadata.getClassName();
System.out.println(className);
return className.contains("er");
}
}
@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz", includeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})
}, useDefaultFilters = false)
public class MainConfig {
// ...
}
bean对象默认是单实例的
@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz")
public class MainConfig2 {
@Bean("person")
public Person person() {
return new Person("张三", 25);
}
}
@Test
public void test2() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
Person bean = applicationContext.getBean(Person.class);
Person bean2 = applicationContext.getBean(Person.class);
System.out.println(bean.hashCode());
System.out.println(bean2.hashCode());
}
ConfigurableBeanFactory.SCOPE_PROTOTYPE,
ConfigurableBeanFactory.SCOPE_SINGLETON,
org.springframework.web.context.WebApplicationContext.SCOPE_REQUEST,
org.springframework.web.context.WebApplicationContext.SCOPE_SESSION, value
后两个需要导入 Spring web 的jar包,且基本不用,只讨论前两个
在ConfigurableBeanFactory
中有两个常量
// 单实例,默认值,在IOC容器启动时就会调用方法创建对象,以后每次获取直接从容器中拿(map.get())
String SCOPE_SINGLETON = "singleton";
// 多实例,在使用getBean()获取bean对象时才会创建
String SCOPE_PROTOTYPE = "prototype";
WebApplicationContext
中的两个常量
// 同一次请求创建一个实例
String SCOPE_REQUEST = "request";
// 同一个session创建一个实例
String SCOPE_SESSION = "session";
@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz")
public class MainConfig2 {
@Bean("person")
@Scope("prototype")
public Person person() {
return new Person("张三", 25);
}
}
@Test
public void test2() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
Person bean = applicationContext.getBean(Person.class);
Person bean2 = applicationContext.getBean(Person.class);
System.out.println(bean.hashCode());
System.out.println(bean2.hashCode());
}
两个 bean 的哈希值不同
1.bean默认是单实例的,只在创建容器时创建一次
单实例,默认值,在IOC容器启动时就会调用方法创建对象,以后每次获取直接从容器中拿(map.get())
2.bean多实例时:ioc容器创建的时候,先不创建实例,当获取实例的时候,再依次创建实例,所以每次创建的实例都不一样
针对单实例设置,因为**单实例是在容器启动时创建对象**
懒加载就是不让单实例在容器启动时创建,在第一次获取Bean时创建对象并初始化。
只需要在方法上添加注解@Lazy
@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz")
public class MainConfig2 {
@Bean("person")
@Lazy
public Person person() {
return new Person("张三", 25);
}
}
按照一定的条件进行判断,满足条件给容器中注册Bean
@Conditional源码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
// 传入一个Condition数组
Class<? extends Condition>[] value();
}
Condition接口源码
@FunctionalInterface
public interface Condition {
/**
* ConditionContext:判断条件能使用的上下文环境
* AnnotatedTypeMetadata:注释信息
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
ConditionContext:判断条件能使用的上下文环境
AnnotatedTypeMetadata:注释信息
如果是linux系统则注册linus,如果是windows系统则注册bill Gates
编写windows条件,需要实现接口Condition,重写matches方法
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 判断是否是Windows系统
// 1. 获取到IOC使用的beanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 2. 获取类加载器
ClassLoader classLoader = context.getClassLoader();
// 3. 获取运行环境
Environment environment = context.getEnvironment();
// 4. 获取到bean定义的注册类
BeanDefinitionRegistry registry = context.getRegistry();
// 容器中是否包含person,判断容器中的bean注册情况,也可以给容器中注册bean
boolean person = registry.containsBeanDefinition("person");
// 运行环境是否是Windows
String property = environment.getProperty("os.name");
assert property != null;
return property.contains("Windows");
}
}
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String property = environment.getProperty("os.name");
assert property != null;
return property.contains("linux");
}
}
@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz")
public class MainConfig3 {
@Bean("bill")
@Conditional({WindowsCondition.class})
public Person person() {
return new Person("bill gates", 62);
}
@Bean("linus")
@Conditional({LinuxCondition.class})
public Person person2() {
return new Person("linus", 48);
}
}
@Test
public void test3() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig3.class);
Map<String, Person> beansOfType = applicationContext.getBeansOfType(Person.class);
System.out.println(beansOfType);
}
改变运行时的变量
当@Conditional配置在类上时,表示只有满足条件时,这个配置类配置的所有bean才会生效
不仅可以放在方法上,还可以放在类上
1.包扫描 + 组件标注注解(@Controller、@Service、@Repository、@Component),这种方式只能注册自己写的类
2.@Bean,可以注册第三方包的类
3.@Import:快速给容器中导入一个组件,id默认是全类名
ImportSelector:一个接口,返回需要导入的组件的全类名数组
ImportBeanDefinitionRegistrar:一个接口,手动注册bean到容器中
4.使用Spring提供的FactoryBean(工厂Bean)
@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz")
@Import({Color.class, Red.class}) // 导入组件,id默认是组件的全类名
public class MainConfig3 {}
@Test
public void test4() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig3.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
// 自定义逻辑,返回需要导入的组件
public class MyImportSelector implements ImportSelector {
// 返回值就是要导入到容器中的组件的全类名
// AnnotationMetadata:当前标注@Import注解类的所有信息
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.kdz.pojo.Blue", "com.kdz.pojo.Yellow"};
}
}
@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz")
@Import({Color.class, Red.class, MyImportSelector.class}) // 导入组件,id默认是组件的全类名
public class MainConfig3 {}
@Test
public void test4() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig3.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* AnnotationMetadata:当前类的注解信息
* BeanDefinitionRegistry:注册类
* 把所有需要添加到容器中的bean,调用BeanDefinitionRegistry.registerBeanDefinition手动注册
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 查询容器中是否存在red组件
boolean red = registry.containsBeanDefinition("com.kdz.pojo.Red");
if (red) {
// 指定bean的定义信息(bean的类型,bean的scope)
BeanDefinition beanDefinition = new RootBeanDefinition(Purple.class);
// 第一个参数指定bean的id
registry.registerBeanDefinition("purple", beanDefinition);
}
}
}
@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz")
@Import({Color.class, Red.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class MainConfig3 {}
@Test
public void test4() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig3.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
FactoryBean是一个接口,我们需要自己实现
源码分析(自己点源码)
需要获取FactoryBean本身,需要+ &
public class ColorFactoryBean implements FactoryBean<Color> {
// 返回一个Color对象,这个对象会添加到容器中
@Override
public Color getObject() throws Exception {
return new Color();
}
@Override
public Class<?> getObjectType() {
return Color.class;
}
// 返回是否为单例,如果为false,则每次创建时调用getObject()方法
@Override
public boolean isSingleton() {
return true;
}
}
@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz")
public class MainConfig4 {
@Bean
public ColorFactoryBean colorFactoryBean() {
return new ColorFactoryBean();
}
}
@Test
public void test5() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig4.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
// 工厂bean获取的是调用getObject创建的对象
Object colorFactoryBean = applicationContext.getBean("colorFactoryBean");
System.out.println(colorFactoryBean.getClass());
// 如果想要拿到colorFactoryBean对象,则需要在传入的id前加一个&标识
Object colorFactoryBean2 = applicationContext.getBean("&colorFactoryBean");
System.out.println(colorFactoryBean2.getClass());
}
BeanFactory是接口,提供了IOC容器最基本的形式,给具体的IOC容器的实现提供了规范
FactoryBean也是接口,为IOC容器中Bean的实现提供了更加灵活的方式,FactoryBean在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式
区别:BeanFactory是个Factory,也就是IOC容器或对象工厂,FactoryBean是个Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似
BeanFactory,以Factory结尾,表示它是一个工厂类(接口), 它负责生产和管理bean的一个工厂。在Spring中,BeanFactory是IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。BeanFactory只是个接口,并不是IOC容器的具体实现,但是Spring容器给出了很多种实现,如 DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等,其中*XmlBeanFactory就是常用的一个,该实现将以XML方式描述组成应用的对象及对象间的依赖关系。XmlBeanFactory类将持有此XML配置元数据,并用它来构建一个完全可配置的系统或应用。
都是附加了某种功能的实现。 它为其他具体的IOC容器提供了最基本的规范,例如DefaultListableBeanFactory,XmlBeanFactory,ApplicationContext 等具体的容器都是实现了BeanFactory,再在其基础之上附加了其他的功能。
BeanFactory和ApplicationContext就是spring框架的两个IOC容器,现在一般使用ApplicationnContext,其不但包含了BeanFactory的作用,同时还进行更多的扩展。
BeanFacotry是spring中比较原始的Factory。如XMLBeanFactory就是一种典型的BeanFactory。
原始的BeanFactory无法支持spring的许多插件,如AOP功能、Web应用等。ApplicationContext接口,它由BeanFactory接口派生而来,
ApplicationContext包含BeanFactory的所有功能,通常建议比BeanFactory优先
ApplicationContext以一种更向面向框架的方式工作以及对上下文进行分层和实现继承,ApplicationContext包还提供了以下的功能:
• MessageSource, 提供国际化的消息访问
• 资源访问,如URL和文件
• 事件传播
• 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层;
在不使用spring框架之前,我们的service层中要使用dao层的对象,不得不在service层中new一个对象。存在的问题:层与层之间的依赖。
service层要用dao层对象需要配置到xml配置文件中,至于对象是怎么创建的,关系是怎么组合的都交给了spring框架去实现。
一般情况下,Spring通过反射机制利用的class属性指定实现类实例化Bean,在某些情况下,实例化Bean过程比较复杂,如果按照传统的方式,则需要在中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。从Spring3.0开始,FactoryBean开始支持泛型,即接口声明改为**FactoryBean**的形式
以Bean结尾,表示它是一个Bean,不同于普通Bean的是:它是实现了FactoryBean接口的Bean,根据该Bean的ID从BeanFactory中获取的实际上是FactoryBean的getObject()返回的对象,而不是FactoryBean本身,如果要获取FactoryBean对象,请在id前面加一个&符号来获取。
例如自己实现一个FactoryBean,功能:用来代理一个对象,对该对象的所有方法做一个拦截,在调用前后都输出一行LOG,模仿ProxyFactoryBean的功能。