Spring基础原理总结

117 阅读8分钟

“我正在参加「掘金·启航计划」”

1. spring 入口:看以下两个几行代码:

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        ClassPathXmlApplicationContext context1 = new ClassPathXmlApplicationContext("spring.xml");

        CompanyService companyService = (CompanyService)context.getBean("companyService");
        
        companyService.test();
    }
}

AnnotationConfigApplicationContext 和 ClassPathXmlApplicationContext 两者都是创建spring容器,只不过是加载配置的方式不一样,AnnotationConfigApplicationContext 是在spring3.0版本增加的一个类。

AnnotationConfigApplicationContext :通过加载配置类,在配置类上配置扫描路径,将扫描路径下的bean加载到spring容器当种。如下:

@ComponentScan("com.example.demo")
public class AppConfig {}

ClassPathXmlApplicationContext:通过加载配置文件,在spring.xml文件中配置扫描路径,将路径下的bean加载到spring容器当中。如下:

<context:component-scan base-package="com.example.demo"></context:component-scan>

因为springboot 中用的是AnnotationConfigApplicationContext,所以我们这里以AnnotationConfigApplicationContext为主来分析spring的核心原理。 然后,我们可以通过getBean("companyServcie")获取bean对象。

2. spring创建的bean和我们自己创建的对象有什么区别?

我们经常说spring帮我们管理bean,不用我们自己去创建bean对象,spring会帮我们创建bean对象,我们只需要获取bean对象就行。那么我们看下面这个简单的示例:

@Component
public class CompanyService {

    @Autowired
    private PersonService personService;

    public  void  test(){
        System.out.println("company test");
    }
}

@Component
public class PersonService {
}
 
public class Test {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        CompanyService companyService = (CompanyService)context.getBean("companyService");
        CompanyService companyService1 = new CompanyService();
        companyService.test();

    }
}

有两个类,CompanyService , PersonService 。PersonService 是CompanyService 里的属性。且加了@Autowired注解。我们打debug执行:

image.png 可以看到,companyService里的personService属性是有值的,companyService1里的personService属性是没有值的,这也是自己创建bean和spring帮我们创建bean的不同之处。(只是其中一点,方便理解。)

由此,我们可以得出,spring在帮我们创建bean之后肯定做了其他处理,给personService属性赋值。

3.spring创建bean的生命周期

spring创建bean的过程大致可以概括为以下:

companyService --> 构造方法-->普通对象-->依赖注入-->初始化前-->初始化-->初始化后-->代理对象-->bean

4. spring 通过构造方法创建普通对象(推断构造方法)。

例如,要创建一个CompanyService对象,一般情况下,我们肯定是要用这个类的构造方法。spring 在去创建的时候是分这么几种情况:

  1. 当bean 中 没有构造方法的时候,如下代码: 会使用bean 默认自带的无参构造方法。
@Component
public class CompanyService {
   private PersonService personService;
}
  1. 当bean 中 同时有无参构造方法和 有参构造方法,如下代码:会默认使用无参构造方法
@Component
public class CompanyService {

    private PersonService personService;

    public CompanyService() {
    }

    public CompanyService(PersonService personService) {
        this.personService = personService;
    }
    
}
  1. 当bean 中同时有两个有参构造方法(没有指定使用哪一个的时候),如下代码: 执行报错。

    解决办法:可以在想用的方法上面加@Autowired注解。

@Component
public class CompanyService {

   private PersonService personService;

   private String user;

   public CompanyService(PersonService personService) {
       this.personService = personService;
   }

   public CompanyService(PersonService personService, String user) {
       this.personService = personService;
       this.user = user;
   }
}

我们通过上面的三种情况可以得知:spring会去推断使用哪个构造方法来创建对象,也就是所谓的spring推断构造方法。

5.普通对象-->依赖注入

我们都知道一个bean中的属性加上@Autowired注解spring就会自动赋值。看下面这段代码

@Component
public class PersonService {
}

@Component
public class CompanyService {

   private PersonService personService;

   public CompanyService(PersonService personService) {
       this.personService = personService;
   }
}

@Component
public class CompanyService {
   @Autowired
   private PersonService personService;
}

上面这段代码,其实很简单,但是我还是想说明下: 加@Autowired注解 和 有参的构造方法 这两种写法,在spring 加载后都可以给 personService 属性赋值。

那我们看以下几种情况。

  1. 第一种情况
@ComponentScan("com.example.demo")
public class AppConfig {

   @Bean
   public PersonService personService1(){
       return  new PersonService();
   }
   @Bean
   public PersonService personService2(){
       return  new PersonService();
   }
}

@Component
public class PersonService {
}

@Component
public class CompanyService {

   private PersonService personService;

   public CompanyService(PersonService personService) {
       this.personService = personService;
   }
}

spring在去加载的时候会创建三个 PersonService 类型的bean, 那么在赋值属性的时候会选择 名称为personService 的bean给CompanyService的 personService 属性赋值。

  1. 第二种情况
@ComponentScan("com.example.demo")
public class AppConfig {

    @Bean
    public PersonService personService1(){
        return  new PersonService();
    }
}

public class PersonService {
}

@Component
public class CompanyService {

    private PersonService personService;

    public CompanyService(PersonService personService) {
        this.personService = personService;
    }
}

spring 创建了 一个PersonService类型的bean,名字是:personService1 ,但是成功赋值给CompanyService 类中的personService属性。 3. 第三种情况

@ComponentScan("com.example.demo")
public class AppConfig {

   @Bean
   public PersonService personService1(){
       return  new PersonService();
   }

   @Bean
   public PersonService personService2(){
       return  new PersonService();
   }
}

public class PersonService {
}

@Component
public class CompanyService {

   private PersonService personService;

   public CompanyService(PersonService personService) {
       this.personService = personService;
   }
}

这种执行时会直接报错。 由上述三种情况可以得出,spring 在依赖注入的时候会先通过类型匹配,然后再通过名字。

6.初始化前(@PostConstruct)

我们再开发一些功能的时候,可能会遇到这样的问题:

在项目启动的时候,有些类的属性需要从数据库拿些数据给属性赋值,那这时候我们改怎么做呢? 亦或者是,我们想在项目启动成功的时候,将数据库的数据加载到缓存中。

首先,肯定是定义一个方法,这个方法的功能就是将加载数据,现在的问题就是,这个方法要在项目启动完成前执行。可以用 @PostConstruct 这个注解。将注解放到方法上面就可以了。为了形象点,看下面这个代码:

@Component
public class JopType {

   // 将数据库中的工种缓存起来
   public void  getTypes(){
       // 1. 查数据库获取工种
       // 2. 加入到缓存中。
   }
}

需求:在项目启动完成前将数据库中的工种加入到缓存中。

只需要这个样子就行:

@Component
public class JopType {

   // 将数据库中的工种缓存起来
   @PostConstruct
   public void  getTypes(){
       // 1. 查数据库获取工种
       // 2. 加入到缓存中。
   }
}

spring创建bean初始化前,就是检查bean中有没有加@PostConstruct这个注解,如果加了,执行这个加了@PostConstruct注解的方法。

初始化前当然不止这么多东西,还有很多,这个只是简单的举例。我们看到这里就该想spring是怎么知道这些方法有没有加注解,是怎么判断的?我想大部分应该都是知道怎么判断的。

7.初始化(InitializingBean)

初始化和初始化前要实现的作用基本是一致的,这个是实现了一个接口:InitializingBean

@Component
public class JopType implements InitializingBean {


   @Override
   public void afterPropertiesSet() throws Exception {

   }
}

实现 InitializingBean 接口,重写 afterPropertiesSet 方法。

spring在会判断这个类有没有实现这个接口,如果有实现这个接口,就会执行重写的方法。在初始化前描述的问题放到这里一样可以解决。

8.初始化后(AOP)到代理对象。

AOP切面:使用的是cglib代理。实现就是:创建一个代理类继承之前的类。在使用代理类创建代理对象。

public class CompanyServiceProxy extends CompanyService {

   private CompanyService target;

   public void  test(){
       target.test();
   }

}

添加AOP

@Aspect
@Component
public class LyAspect {

   @Before("execution(public void com.example.demo.service.CompanyService.test())")
   public void  before(JoinPoint joinPoint){

   }

}

@ComponentScan("com.example.demo")
@EnableAspectJAutoProxy
public class AppConfig {

   @Bean
   public PersonService personService1(){
       return  new PersonService();
   }

}

image.png

开启aop后执行,getBean 拿出来的Bean就是代理Bean了,我们可以看代理bean里面有target属性personServie 是有值的。而下面的personService是没有值的。

image.png 当我们进入到test方法里面的时候是有值的。 为什么会这样呢?其实是这样的。

1.创建一个代理类继承之前的类,然后在里面声明一个target属性,这个target属性的类型是父类。

2.再使用代理类创建代理对象,把普通对象赋值给代理对象的target属性。

public class CompanyServiceProxy extends CompanyService {

   private CompanyService target;

   public void  test(){
       @切面方法
       target.test();
   }

   public CompanyService getTarget() {
       return target;
   }

   public void setTarget(CompanyService target) {
       this.target = target;
   }
}


CompanyServiceProxy companyServiceProxy = new CompanyServiceProxy();
companyServiceProxy.setTarget("普通的companyService对象");

大致的流程就是这样,这个 "普通的companyService对象" 是spring前面已经创建过的。

spring判断bena是否被切(AOP)

1.spring会遍历所有的bean,找出所有的切面bean,带有@Aspect注解的类。

2.再遍历这些类

3.再遍历这些类的方法

4.找出与当前bean有关系的切面方法,缓存起来,以便后续执行。

9.spring 事物

看下面测试代码

@ComponentScan("com.example.demo")
@EnableTransactionManagement
@Configuration
public class AppConfig {

   @Bean
   public PersonService personService1(){
       return  new PersonService();
   }

   @Bean
   public JdbcTemplate jdbcTemplate(){
       return  new JdbcTemplate(dataSource());
   }
   @Bean
   public PlatformTransactionManager transactionManager(){
       DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
       transactionManager.setDataSource(dataSource());
       return transactionManager;
   }

   @Bean
   public DataSource dataSource(){
       DriverManagerDataSource dataSource = new DriverManagerDataSource();
       dataSource.setUrl("jdbc:mysql://localhost:3306/chy?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8");
       dataSource.setUsername("root");
       dataSource.setPassword("123456");
       dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
       return dataSource;
   }
}

@Component
public class CompanyService {

   @Autowired
   private  JdbcTemplate jdbcTemplate;

   @Transactional
   public void test() {
       jdbcTemplate.execute("INSERT INTO `chy`.`ceshi`(`e`, `w`, `c`) VALUES (1, 1, 1);");
       throw new NullPointerException();
   }
}

以上代码我们可以测试下:

1.如上代码没做什么改动,执行后数据库表中是不会添加数据的。

2.将AppConfig配置类上的@Configuration注释了,执行后数据会插入到表中。

spring事物也是基于aop的 ,也就是说 事物会生成一个代理对象,那么这个代理对象的逻辑是这样的;

1.会判断有没有加@Transactional注解

2.创建一个数据库链接,这个数据库连接时 事物管理器创建的

3.修改属性comit 属性,自己手动控制提交。

4.执行业务方法

5.提交或者回滚。

加上事物获取bean,获取的是spring事物创建的代理对象

image.png

为什么加了 @Configuration 会生效不加事物就不会生效。

image.png

dataSource() 这里是个方法,如果加上@Configuration ,在执行dataSource()方法时回去缓存中拿DataSource对象,如果有就直接使用,没有就会创建。

我们再用事物的时候也会发现事物失效。

//事物失效
@Component
public class CompanyService {

    @Autowired
    private  JdbcTemplate jdbcTemplate;

    @Transactional
    public void test() {
        jdbcTemplate.execute("INSERT INTO `chy`.`ceshi`(`e`, `w`, `c`) VALUES (1, 1, 1);");
        //throw new NullPointerException();
        getId();
    }
    @Transactional(propagation = Propagation.NEVER)
    public void  getId(){

    }
}
//解决方法
 
@Component
public class CompanyService {

    @Autowired
    private  JdbcTemplate jdbcTemplate;

    @Autowired
    private CompanyService companyService;

    @Transactional
    public void test() {
        jdbcTemplate.execute("INSERT INTO `chy`.`ceshi`(`e`, `w`, `c`) VALUES (1, 1, 1);");
        //throw new NullPointerException();
        companyService.getId();
    }
    @Transactional(propagation = Propagation.NEVER)
    public void  getId(){
        System.out.println("===");
    }
}

上面这种写法事物是失效的。

事物失效问题:我们在使用事物的时候要考虑是不是事物的代理对象执行该方法,上面这种直接getId()相当于是普通对象调用getId(),而不是事物创建的代理对象调用。

image.png

image.png

以上个人见解,如有不对还请指正。