欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

深入理解Spring源码系列(值得一看的上篇)

最编程 2024-02-22 16:09:32
...

概念篇

下面是本文章关于Spring底层原理的章节


Bean的创建的生命周期


类-》推断构造方法-》根据构造方法创建普通对象-》依赖注入(@Autowired等进行属性注入)-》初始化前(@PostConstruct)->初始化(InitializingBean)-》初始化后(AOP)-》代理对象(没有开启AOP就会把普通对象放入单例池)-》放入单例池-》Bean对象

依赖注入

在Spring容器中创建了一个普通对象后,如果这个对象有类似于@Autowired注解的属性,如何给这个属性赋值呢?这里利用的是反射的机制,在创建完一个普通对象后,利用反射机制看有没有@Autowird注解的属性,如果依赖注入的bean为单例,首先从单例池中寻找,找到就赋值注入,找不到就创建然后注入属性,如果这个bean为多例,就会直接new 一个对象出来然后赋值。这个具体可以看下面的模拟代码进行深入理解。


推断构造方法

在Spring容器中使用构造方法创建对象的时候默认采用无参构造方法。在Spring容器中创建对象是通过反射根据构造方法进行创建的,至于具体根据哪个构造方法进行创建对象,内容如下:


1.只有一个构造方法,那么实例化就只能使用这个构造方法了。有参的话(前提是根据参数类型或者名字可以找到唯一的bean。

2.有多个构造方法,不管构造方法参数是一个还是多个,那么Spring会去找默认的无参的构造方法,找不到则报错。

3.多个构造方法,并且开发者指定了想使用的构造方法,那么就用这个构造方法

通过@Autowired注解,@Autowired注解可以写在构造方法上,所以哪个构造方法上写了@Autowired注解,表示开发者想使用哪个构造方法。通过@Autowired注解的方式,需要Spring通过byType+byName的方式去找到符合条件的bean作为构造方法的参数值,当然找不到是要报错的。通过byType找如果只有一个就使用该Bean对象,如果有多个再根据名字去找,Spring容器在寻找过程中是根据参数名作为名字去寻找的,找不到则报错。这个类似于@Autowired注解,一开始根据类型去寻找,如果有多个,再根据属性名去找对应的是该名字的Bean对象。


@PostConstruct

如果想要在对象初始化之前执行该对象中的一些方法,可以在该对象方法上加上@PostConstruct注解。在Spring容器中初始化之前执行有该注解的方法。


初始化

Spring容器中对于对象的初始化可以通过继承 InitializingBean 接口重写 afterPropertiesSet() 方法,在此方法里面执行自己的初始化的业务逻辑。有关代码如下:

@Component("test")
public class Test implements InitializingBean {
    public  void hello(){
        System.out.println("执行方法");
    }
 
    @PostConstruct
    public void go(){
        System.out.println("初始化之前");
    }
 
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("初始化");
    }
}

执行结果如下图:

AOP

AOP简介

这里先对AOP进行简单的介绍

AOP (Aspect Orient Programming),直译过来就是 面向切面编程,AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向切面编程,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。AOP可以拦截指定的方法并且对方法增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离,比如Spring的事务,通过事务的注解配置,Spring会自动在业务方法中开启、提交业务,并且在业务处理失败时,执行相应的回滚策略。AOP可以拦截指定的方法并且对方法增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离,比如Spring的事务,通过事务的注解配置,Spring会自动在业务方法中开启、提交业务,并且在业务处理失败时,执行相应的回滚策略。


这里采用SpringBoot整合AOP实例代码如下:


导入依赖


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>


导入配置类

@Configuration
@ComponentScan("com.example.demo.test")
@EnableAspectJAutoProxy
public class AspectConfiguration {
 
}

导入切面类

@Aspect
@Component
public class MyAspect {
 
    @Pointcut("execution(* com.example.demo.test.*.*(..))")
    public void myPointCut(){
 
    }
    //前置通知
    @Before("myPointCut()")
    public void before(JoinPoint joinPoint){
        System.out.println("前置通知");
        System.out.println("目标类对象"+joinPoint.getTarget()+"被增强的方法"+joinPoint.getSignature().getName());
    }
    //后置返回通知
    @AfterReturning("myPointCut()")
    public void afterreturn(JoinPoint joinPoint){
        System.out.println("后置返回通知");
        System.out.println("目标类对象"+joinPoint.getTarget()+"被增强的方法"+joinPoint.getSignature().getName());
    }
 
      //环绕通知,返回值类型为Object,必须有一参数是ProceedingJoinPoint
    @Around("myPointCut()")
    public Object aroud(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕开始,模拟开启事务");
       //执行当前方法
        Object proceed = proceedingJoinPoint.proceed();
        System.out.println("环绕结束,模拟关闭事务");
        return proceed;
    }
 
    //异常通知
    @AfterThrowing(value = "myPointCut()",throwing = "e")
    public void except(Throwable e){
        System.out.println("异常通知"+e.getMessage());
    }
 
    //后置最终通知
    @After("myPointCut()")
    public void after(JoinPoint joinPoint){
        System.out.println("最终通知");
    }
 
 
 
}

执行代码

public class hello {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext=new AnnotationConfigApplicationContext(AspectConfiguration.class);
        Test test = (Test) annotationConfigApplicationContext.getBean("test");
        test.hello();
    }
}

结果如下图所示

这里对于Spring5通知的执行顺序进行简单的总结,注意Spring4通知的执行顺序和Spring5不一样。

程序执行正常:

1、环绕通知前

2、@Before通知

3、程序逻辑

4、@AfterReturning通知

5、@After通知

6、环绕通知后

程序执行异常:

1、环绕通知前

2、@Before通知

3、@AfterThrowing异常通知

4、@After通知

异常日志

AOP原理


对于上面的代码我们在执行test.hello的时候我们的对象是Test对象吗?还是代理对象?经过debug我们来看一下。如下图证实我们拿到的对象是代理对象。


ce9bdd80fae14c0caeb3943d82b47f94.png


注意在Spring容器中假如真实对象中有类似@Autowired注解进行依赖注入的时候,我们在这里debug拿到的代理对象关于这样的属性实际上是空的,但是直接运行的时候实际上又会获得依赖注入对象,这是什么原因呢?


在Spring容器中代理类其实是真实类的子类,通过extends继承,既然代理类是真实类的子对象,那么他们之间是怎么实现的呢?实现方法之一如下:


public class My extends Test {
    @Override
    public void hello() {
        //执行切面逻辑
        super.hello();
    }
}

ce9bdd80fae14c0caeb3943d82b47f94.png 这样可以正确的实现吗?其实是不行,假入Test类中有依赖注入的属性,然后My代理类执行父类的时候,在执行方法中的有依赖注入的属性其实是空的,因为父类创建了一个对象并为这个属性赋值,它的子类并不会获得该属性的值的。那解决办法呢?那就是在My类中创建一个Test类对象的属性Target,并把真实类赋值给Target属性,然后在执行方法中执行Target.hello方法就可以了。