Spring笔记

本文最后更新于:2022年6月11日 下午

Spring源码1-3

SpringMVC源码4

第1节

Spring概念和使用

注解方式管理Bean:

1、注解方式 创建对象IOC

导入依赖 aop

1
2
3
4
5
6
@Component    放在类上,用于标记,告诉spring当前类需要由容器实例化bean并放入容器中
该注解有三个子注解
@Controller 用于实例化controller层bean
@Service 用于实例化service层bean
@Repository 用于实例化持久层bean
当不确定是哪一层,就用Component

这几个注解互相混用其实也可以,但是不推荐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 第一步:在applicationContext.xml中配置开启注解扫描
<!--添加注解扫描,扫描指定的包,将包中的所有有注解的类实例化
base-package 后面放要扫描的包
如果有多个包需要扫描,可以使用逗号隔开 com.msb.bean,com.msb.service
或者可以写上一层包路径 com.msb
可以通过注解指定bean的id @Component("user1")
如果不指定,则id默认是 类名首字母小写
-->
<context:component-scan base-package="com.zzflybird.bean"></context:component-scan>

// 第二步:在类上添加注解,让spring容器给我们创建bean实例并存储于容器中
@Component(value = "user1")
public class User {

}

// 单元测试
@Test
public void userTest()
{
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Object user1 = context.getBean("user1");
System.out.println(user1);
}

2、注解方式 依赖注入DI

@Autowired 根据属性数据类型自动装配
@Qualifier 根据属性名称注入依赖
@Resources 可以根据类型,也可以根据名称注入
@Value 注入普通数据类型(8+String)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*
* @Autowired
* 根据类型到容器中去寻找对应的对象,找到后给当前属性赋值
* 不需要依赖 set方法
* 属性类型可以是接口,会自动匹配对应的实现类对象
* @Autowired配合 @Qualifier,可以通过名称指定注入的对象
*
* @Resource 如果不配置name 那么就是根据类型注入
* @Resource(name="userDaoImplB") 配置name,就是根据名称注入
*
*
* @Resource 是JDK中javax包的注解
* @Autowired 和 @Qualifier 是spring中的注解
*
* @Value 可以个普通属性赋值
* @Value 可以使用${}这种表达式获取系统的变量值
* 或者是.properties属性配置文件中的值
*
* */
//@Autowired
//@Qualifier("userDaoImplA")
//@Qualifier("userDaoImplB")
//private UserDao userDao ;
@Resource(name="userDaoImplB")
private UserDao userDao ;
@Value("${username}")
private String sname;
@Value("boy")
private String sgender;
@Value("${age}")
private Integer sage;

3、完全使用注解

创建配置类,替代xml配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@ComponentScan(basePackages = "com.zzflybird") // 配置扫描包
@PropertySource("classpath:aaa.properties")
public class SpringConfig {
}

public class SpringConfigTest {

@Test
public void springConfigTest(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserServiceImpl userServiceImpl = context.getBean("userServiceImpl", UserServiceImpl.class);
userServiceImpl.add();
}

}

JDK Proxy动态代理:面向接口

  • 必须有接口和实现类
  • 只能增强接口中定义的方法
  • 只能读取接口中方法的上注解
  1. 在不修改原有代码的 或者没有办法修改原有代码的情况下 增强对象功能 使用代理对象 代替原来的对象去完成功能
    进而达到拓展功能的目的
  2. JDK Proxy 动态代理面向接口的动态代理 一定要有接口和实现类的存在 代理对象增强的是实现类 在实现接口的方法重写的方法
  • 生成的代理对象只能转换成 接口的不能转换成 被代理类
  • 代理对象只能增强接口中定义的方法 实现类中其他和接口无关的方法是无法增强的
  • 代理对象只能读取到接口中方法上的注解 不能读取到实现类方法上的注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

// Proxy 动态代理 JDK动态代理 面向接口
// 接口
interface Dinner{
void eat(String foodName);
void drink();
}

// 被代理类,实现接口
class Person implements Dinner{

private String name;

public Person(String name) {
this.name = name;
}

@Override
public void eat(String foodName) {
System.out.println(name+":吃"+foodName);
}

@Override
public void drink() {
System.out.println(name + ":喝");
}
}

public class PorxyTest {

@Test
public void proxyTest()
{
Dinner dinner = new Person("张三");

//被代理的对象的类加载器
ClassLoader loader = dinner.getClass().getClassLoader();
//被代理对象所实现的所有接口
Class<?>[] interfaces = dinner.getClass().getInterfaces();

//执行处理器对象,专门用于定义增强的规则
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, // 代理对象
Method method, // 被代理的方法
Object[] args) throws Throwable {
Object res = null;
if (method.getName().equals("eat"))
{
System.out.println("在被代理方法 eat() 前 do something");
// 调用被代理对象的方法
res = method.invoke(dinner, args);
} else
{
res = method.invoke(dinner, args);
}
return res;
}
};

//通过Porxy动态代理获得一个代理对象,在代理对象中,对某个方法进行增强
// 代理对象只能转换成接口,不能转换成 被代理类
Dinner dinnerProxy = (Dinner) Proxy.newProxyInstance(loader, interfaces, invocationHandler);
dinnerProxy.drink();
dinnerProxy.eat("冒菜");
}
}

CGLIB动态代理: 面向父类

  1. 面向父类,和接口没有直接关系
  2. 不仅可增强接口中定义的方法,还可以增强一个类其他的方法
  3. 可以读取父类中方法上的所有注解

image-20220521160949458

AOP

AOP切面编程一般可以帮助我们在不修改现有代码的情况下,对程序的功能进行拓展,往往用于实现 日志处理,权限控制,性能检测,事务控制等
AOP实现的原理就是动态代理,在有接口的情况下,使用JDK动态代理,在没有接口的情况下使用cglib动态代理

AOP中的术语辨析

1 连接点 Joint point:
类里面那些可以被增强的方法,这些方法称之为连接点
表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point

2 切入点 Pointcut:
实际被增强的方法,称之为切入点,”execution(* com.zzflybird.dao..add(..))”
表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方

3 通知 Advice:
实际增强的逻辑部分称为通知 (增加的功能)
Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
通知类型: 1 前置通知 2 后置通知 3 环绕通知 4 异常通知 5 最终通知

4 目标对象 Target:被增强功能的对象(被代理的对象)
织入 Advice 的目标对象

5 切面Aspect: 表现为功能相关的一些advice方法放在一起声明成的一个Java类
Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。

6 织入 Weaving:
创建代理对象并实现功能增强的声明并运行过程
将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程

AOP的实现:基于注解(熟练)

Spring原理笔记

Spring Bean的创建生命周期

根据一个类最终得到一个Bean对象,中间经过的步骤。

UserService.class—>无参构造方法(推断构造方法)—》普通对象—》依赖注入(属性赋值)—》初始化前(@PostConstruct)—–》初始化(InitializingBean)—》初始化后(AOP)—》代理对象—-》Bean

推断构造方法:使用哪个构造方法,构造方法的参数去哪里拿

Bean的销毁的生命周期

推断构造方法:

如果一个类中只有一个有参构造方法,spring就用这个。

如果一个类中有多个有参构造方法,spring不知道用哪一个,就会去找无参构造方法,如果找不到无参构造方法,就会报错。

有参构造方法的参数是一个类A的对象a,spring会先从spring容器中通过类名A找对应类型的Bean,如果找到了1个bean,就用这个bean给构造方法;如果使用类名A找到多个bean,就再用参数名a找与其名称相同的bean,把名称相同的bean给构造方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class UserDaoImpl implements UserDao {

private EmpDao empDao;

// public UserDaoImpl() {
// }

// @Autowired
public UserDaoImpl(EmpDao empDao) {
this.empDao = empDao;
}

@Autowired // 没有默认构造方法,对于两个有参构造方法,想让spring用哪一个,就加上@Autowired注解
public UserDaoImpl(EmpDao empDao, EmpDao empDao1) {
this.empDao = empDao;
}
}

AOP原理

UserService等价于UserDao,OrderService等价于EmpDao

Spring生成Bean的过程:UserService.class—>无参构造方法(推断构造方法)—》普通对象—》依赖注入(属性赋值)—》初始化前(@PostConstruct)—–》初始化(InitializingBean)—》初始化后(AOP)—》代理对象—-》Bean

UserServiceProxy类—->代理对象—->代理对象.target = 普通对象

代理对象.test()方法—>进入到代理类的test方法中,执行target.test()方法

1
2
3
4
5
6
7
8
9
10
class UserServiceProxy extends UserService
{
UserService target;// 普通对象,进行过依赖注入的,其属性是有值的

public void test()
{
// 先执行@Before修饰的方法,执行切面逻辑
target.test();// 普通对象.test()
}
}

而代理对象里面的orderService (empDao)对象是没有赋值的,是null的。

image-20220521215633666

普通对象是进行了依赖注入的,它的属性是有值的。

image-20220521215812470

Spring生成Bean的过程:UserService.class—>无参构造方法(推断构造方法)—》普通对象—》依赖注入(属性赋值)—》初始化前(@PostConstruct)—–》初始化(InitializingBean)—》初始化后(AOP)—》代理对象—-》Bean

初始化后(AOP)这一步,判断UserService要不要进行AOP,根据UserService中的方法是否被切了来判断。那么如何判断UserService中的有没有方法被切了呢?

1、找出所有的切面Bean

2、遍历所有的切面Bean

3、对于每一个切面Bean,遍历其所有方法

4、如果 某切面Bean中方法beforeMethod()的注解表达式中的某个方法test() == UserService中的某个方法test(),就把方法beforeMethod()放入map中缓存下来。存下来的方法beforeMethod()后面在test()方法前执行的时候直接去map中找方法来执行。map<UserService.class, method list>

Spring 事务原理

1
2
// 想要使用自动注解打开事务,结果发现没有注解,事务需要配置额外的包

2小时打卡,AOP原理,Spring中的Bean(代理对象)与普通对象的区别?

Spring中的Bean就是一个代理对象。

加不加注解@Configuration对事务的作用:

两个不同的dataSource对象,一个是JdbcTemplate中自己的,另一个是Spring事务管理器创建的dataSource对象,创建连接conn,并关闭conn的自动提交。

JdbcTemplate和Spring事务管理器中的dataSource要是同一个dataSource,事务才会有效。

加了事务注解的方法中,调用该方法的对象是普通对象还是代理对象,如果是代理对象那么事务注解就有用。

第2节

手写Spring

Spring生成Bean的过程:

UserService.class—>无参构造方法(推断构造方法)—》普通对象—》依赖注入(属性赋值)—》初始化前(@PostConstruct)—–》初始化(InitializingBean)—》初始化后(AOP)—》代理对象—-》Bean

创建ConfigAppcontext类

创建Config配置类

创建@配置注解

实现Appcontext的构造方法

扫描方法

实例化对象

BeanDefination

beanDefinationMap

singletonObjectsMap

实现依赖注入—-》

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 进行依赖注入, 得到Bean对象的类中的所有属性,判断是否有Autowired注解
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println(field);

if (field.isAnnotationPresent(Autowired.class)) {

// 在访问 属性 域时,Java进行了访问检查,发现该域是private修饰的,不能直接访问,因此抛出异常。
// 解决方法: 关闭安全检查, 使可以访问私有属性, 反射的对象在使用时应该取消 Java 语言访问检查
field.setAccessible(true);

// 先根据类型去找Bean,如果找到多个Bean,在根据名字去找
// System.out.println(field.getType());

// 根据field.getName()名字去找Bean
Object fieldBean = getBean(field.getName());
// 为 instance 对象的 field 属性,设置值 = getBean(field.getName())
field.set(instance, fieldBean);
}
}

进行初始化—-》UserService类 实现 InitializingBean接口来进行初始化,先自己定义InitializingBean接口,在UserService类中重写InitializingBean接口的方法。

1
2
3
4
5
6
// 底层源码实现:
// 在依赖注入后,进行初始化: 判断 instance 是否实现了 InitializingBean 接口
if (instance instanceof InitializingBean)
{
((InitializingBean) instance).afterPropertiesSet(); // 调用初始化方法
}

BeanPostProcessor(最重要的东西),用于实现 初始化前,初始化后

实现 初始化前,初始化后 的功能——-》创建BeanPostProcessor接口,然后定义2个抽象方法:

  1. postProcessBeforeInitialization() 初始化前
  2. postProcessAfterInitialization() 初始化后

用自己创建的ZzflybirdBeanPostProcessor类实现BeanPostProcessor接口,并标注注解,设置为Bean,使得spring可以扫描到。

在spring扫描的过程中,将 实现了BeanPostProcessor接口的类 进行实例化,得到对象,并保存到beanPostProcessorList中。

方便在后面createBean()中,

在 【依赖注入】–》【初始化前】——–》【初始化】———–》【初始化后】中的初始化前和初始化后中调用beanPostProcessorList中的对象的两个重写的方法。

每一个Bean都会调用postProcessBeforeInitialization()和postProcessAfterInitialization()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 初始化前
* @param bean 传入的 bean 实例, 可以对其进行操作
* @param beanName 传入的beanName
* @return
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println("初始化前" + beanName);
return bean;
}

/**
* 初始化后
* @param bean 传入的 bean 实例, 可以对其进行操作
* @param beanName 传入的beanName
* @return
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("初始化后" + beanName);
return bean;
}

Spring中AOP的底层就是通过BeanPostProcessor接口的两个方法实现的,因为这两个方法传入bean,然后可以在bean调用方法前,进行一些操作,这就是AOP。

基于 BeanPostProcessor 这个机制,传入bean,再传出bean,可以对bean做很多事情。

Aware回调功能:

在bean中获取beanName;

创建BeanNameAware接口,setBeanName()方法;

1
2
3
4
5
6
7
/**
* 用于 让一个bean 知道自己的名字,设置 beanName
*/
public interface BeanNameAware {

void setBeanName(String beanName);
}

让一个Bean类实现这个BeanNameAware接口,重写setBeanName方法,Spring底层会自动调用setBeanName方法,为属性赋值。

1
2
3
4
5
6
private String beanName;

@Override
public void setBeanName(String beanName) {
this.beanName = beanName;
}

Aware回调功能是在【依赖注入】后,【初始化前】之前执行的。

如何实现:

  1. 判断bean对象是否实现了BeanNameAware接口,即 bean instanceof BeanNameAware
  2. 然后将bean强制类型转换为BeanNameAware
  3. 再调用bean.setBeanName(beanName)方法即可,就设置了beanName。
  4. 注意:setBeanName(beanName)方法的内容是要自己写的哦。
1
2
3
4
5
6
// Aware回调:设置beanName
if (instance instanceof BeanNameAware)
{
// 设置beanName
((BeanNameAware) instance).setBeanName(beanName);
}

总结:过一遍ZzflybirdApplicationContext类的初始化过程,和getBean的过程;

着重理解BeanPostProcessor 机制的实现原理。

第3节

4h48m, 03-Spring之底层架构核心概念解析

课程内容
1、BeanDefinition以及 BeanDefinitionReader
2、BeanFactory与ApplicationContext
3、Spring中的类型转化组件
4、Spring中的比较器OrderComparator
5、BeanPostProcessor与 BeanFactoryPostProcessor
6、Spring中特殊的Bean之FactoryBean
7、ExcludeFilter和IncludeFilter
8、Spring中类元数据读取 器之MetadataReader

创建BeanDefinition的方法有两种:

声明式定义Bean;声明式有:@Component、@Bean、xml声明Bean。spring底层会为这些Bean自动创建BeanDefinition对象,然后去读取Bean的各种信息和属性放入BeanDefinition对象中。

编程式定义Bean;

1、Spring的底层要使用编程式的方法创建BeanDefinition对象。如下图方框中的代码,在源码中很多使用这种方式创建BeanDefinition的。

image-20220527224307025

注册Bean的:

AnotatedBeanDefinitionReader,传Bean.class

XmlBeanDefinitionReader,传sping.xml

扫描器:

ClassPathBeanDefinitionScanner,传一个string扫描路径。

总结:

AnotatedBeanDefinitionReader是用来注册BeanDefinition的,得到的是AnotatedGenricBeanDefinition

ClassPathBeanDefinitionScanner是用来扫描BeanDefinition的,得到的是ScanedGenericBeanDefinition

这两个BeanDefinition都是一个抽象类AbstractGenericBeanDefinition的子类。

所以BeanDefinition整体看来是有一个,但是在底层是细分为2种不同的BeanDefinition的。

BeanFactory & AppcationContext

Sping中,BeanFactory是一个接口,AppcationContext也是一个接口

1
public interface BeanFactory 

AppcationContext接口是继承了BeanFactory接口的,

什么意思呢?

AppcationContext就是一个BeanFactory,BeanFactory接口有的方法它都有。

例如: context.getBean()方法。

1
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver

ApplicationContext除了继承了BeanFactory接口,还继承了其他的接口,这些其他的接口是BeanFactory所没有的功能;

EnvironmentCapable:获取环境变量的功能

MessageSource:进行国际化的功能

ApplicationEventPublisher:事件发布器

ResourcePatternResolver:拥有直接解析某些资源的功能

DefaultListableBeanFactory是BeanFactory一个非常强大的实现类。

BeanFactory可以用来注册beanDefinition对象。

ApplicationContext的getBean()方法的底层是通过创建一个DefaultListableBeanFactory作为beanFactory来调用beanFactory的getBean()方法的。

DefaultListableBeanFactory涉及到的接口:

HierarchicalBeanFactory:这个接口可以获得子BeanFactory的父BeanFactory

ListableBeanFactory:Listable展示Bean的名字、数量、统计信息,得到某一个类型的Bean信息

事件发布器;

事件监听器;

类型转换器:

ComponentScan扫描可以排除,也可以包含;