Day09-Spring框架开始

168 阅读9分钟

Spring框架

关于Spring框架

Spring框架主要解决了创建对象、管理对象的相关问题。

由于Spring会保管(管理)所创建的对象,当需要时,可以通过Spring获取这些对象,所以,Spring框架也被称之为“Spring容器”。

Spring框架的基础依赖项是spring-context

通过Spring创建对象 -- 1. 组件扫描

在配置类上通过@ComponentScan注解,可以配置“组件扫描”,则加载此配置类时,Spring框架就会开启组件扫描,过程中,会扫描对应的包及其子孙包,如果这些包中存在“组件类”,则Spring会自动创建这些组件类的对象。

在Spring Boot项目中,启动类上添加了@SpringBootApplication注解,此注解的元注解中就包含@SpringBootConfiguration@ComponentScan,并且,@SpringBootConfiguration的元注解中包含@Configuration,即:

@SpringBootApplication
-- @SpringBootConfiguration
-- -- @Configuration
-- @ComponentScan

所以,在默认情况下,Spring Boot启用时就会执行组件扫描!

@ComponentScan注解上可以配置扫描的根包(basePackage),如果没有配置此属性,则默认为当前配置类所在的包!

在Spring Boot项目中,可以配置@SpringBootApplicationscanBasePackages属性来指定组件扫描的根包。

仅当添加了组件注解的类才被视为“组件类”,典型的组件注解是@Component,另外,还有其它组件注解:

  • @Controller:添加在控制器类上的注解
  • @Service:添加在业务类上的注解
  • @Repository:添加在数据访问类上的注解

在Spring框架的作用范围内,以上3个注解与@Component注解的作用、用法是完全等效的,只是语义不同!

在Spring框架中,另外还有@Configuration注解也是组件注解的一种,但是,Spring框架会使用“CGLib代理模式”来处理!

在Spring MVC框架中,又加入了一些新的组件注解,包括:

  • @RestController
  • @ControllerAdvice
  • @RestControllerAdvice

通过Spring创建对象 -- 2. @Bean方法

在配置类中,可以自定义方法,方法的返回值类型就是你希望Spring创建对象的类型,并且,在方法体中,自行编写可以创建出此类型对象的代码,最终,在方法上添加@Bean注解,例如:

@Configuration
public class BeanConfiguration {
    
    @Bean
    public ICategoryService categoryService() {
        return new CategoryServiceImpl();
    }
    
    @Bean
    public CategoryController categoryController(ICategoryService categoryService) {
        // 假设CategoryController中存在带参数的构造方法
        return new CategoryController(categoryService); 
    }

    @Bean
    public AlbumController albumController() {
        return new AlbumController();
    }

}

当加载此配置类时,Spring框架会自动调用这些@Bean方法,则Spring框架会得到这些方法返回的对象!

关于通过Spring创建对象的做法的选取

如果需要创建对象的类是自定义的,可以优先考虑使用组件扫描的做法!

如果需要创建对象的类不是自定义的,则只能使用@Bean方法的做法!

关于Bean Name

被Spring管理的对象都可以称之为:Spring Bean。

每个Spring Bean都有Name,即名称。

如果是通过组件扫描创建的Spring Bean,默认的名称取决于类型的名称,如果类名的第1个字母是大写的,且第2个字母是小写的,则名称是将类名的首字母改为小写,例如CategoryServiceImpl的Bean名称就是categoryServiceImpl,如果不符合首字母大写且第2个字母小写,则Bean名称就是类名。另外,还可以在配置组件注解的属性来指定某个名称,例如:

@Service("categoryService")
public class CategoryServiceImpl implements ICategoryService {
}

如果是通过@Bean方法创建的Spring Bean,默认名称就是@Bean方法的名称,或者,可以配置@Bean注解的参数来指定某个名称,例如:

@Configuration
public class BeanConfiguration {
    
    @Bean("categoryService")
    public ICategoryService categoryService() {
        return new CategoryServiceImpl();
    }
    
}

关于自动装配

**自动装配:**当组件类的属性需要值时,或被Spring调用的方法的参数需要值时,Spring框架会自动从容器找到合适的对象,用于为此属性 / 此参数赋值!

合适的对象:名称匹配的,类型匹配的

例如,在属性上添加@Autowired注解,即可表示“需要自动装配”的意义,则Spring框架会尝试为此属性执行自动装配:

@RestController
public class CategoryController {

    @Autowired
    private ICategoryService categoryService;

}

或:

@Service
public class CategoryServiceImpl implements ICategoryService {

    @Autowired
    private CategoryMapper categoryMapper;
    
}

在处理自动装配时,可用的注解有@Resource@Autowired,它们的区分主要在于:

  • @Resourcejavax.annotation包中的注解,@Autowired是Spring注解

    • 如果你不使用Spring框架,而是使用其它框架实现自动装配,@Resource注解均可使用,而仅当使用Spring时才可以使用@Autowired

      • 由于Spring框架普及程度非常高,所以使用@Autowired是完全没有问题的
  • @Resource是优先根据名称来匹配的,如果无匹配,则按照类型来匹配,而@Autowired是优先按照类型查找匹配的对象,如果存在多个类别匹配的,则按照名称来匹配

关于名称的匹配,需要Spring Bean名称与被装配的属性 / 参数的名称保持完全一致。

当使用@Resource时,可以配置此注解的name属性,用于指定Spring Bean的名称,例如:

@Resource(name = "categoryServiceImpl")
private ICategoryService categoryService;

当使用@Autowired时,只能匹配使用@Qualifier注解来指定Spring Bean的名称,例如:

@Autowired
@Qualifier("categoryServiceImpl")
private ICategoryService categoryService;

如果某个方法的参数需要注入值,与参数匹配的Spring Bean超过1个,且各Spring Bean与参数名称都不匹配,使用@Resource是无解的,只能通过在各参数上添加@Qualifier来配置Spring Bean的名称!

关于@Autowired的装配过程,首先,会查找匹配类型的Spring Bean的数量,然后:

  • 0个:取决于@Autowiredrequired属性的值

    • true:无法装配,在启动时报错,例如:

      Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'cn.tedu.csmall.product.service.ICategoryService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
      
    • false:放弃装配,在启用时不会报错,后续使用过程中可能会出现NPE

  • 1个:直接装配,且成功

  • 多个:尝试按照名称进行匹配,如果可以匹配,则成功装配,否则,启动时报错,例如:

    • Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'cn.tedu.csmall.product.service.ICategoryService' available: expected single matching bean but found 2: categoryServiceImpl,categoryServiceImpl2
      

关于为属性注入值的做法

当组件类的属性需要值时,有3种可行的做法:

  • **字段注入:**在属性上添加@Autowired,例如:

    @Service
    public class CategoryServiceImpl implements ICategoryService {
    
        @Autowired
        private CategoryMapper categoryMapper;
        
    }
    
  • **Setter注入:**在方法上添加@Autowired,并在方法体中为属性赋值,例如:

    @Service
    public class CategoryServiceImpl implements ICategoryService {
    
        private CategoryMapper categoryMapper;
    
        @Autowired
        public void setCategoryMapper(CategoryMapper categoryMapper) {
            this.categoryMapper = categoryMapper;
        }
        
    }
    
  • **构造方法注入:**在被Spring调用的构造方法上添加参数,并通过此参数为属性赋值,例如:

    @Service
    public class CategoryServiceImpl implements ICategoryService {
    
        private CategoryMapper categoryMapper;
    
        public CategoryServiceImpl(CategoryMapper categoryMapper) {
            this.categoryMapper = categoryMapper;
        }
        
    }
    

**注意:**理论上并不推荐字段注入,所以,在IntelliJ IDEA中也有相关提示:

image.png 理论上推荐的是构造方法注入,但是,构造方法注入对可能出现的增减属性并不友好,并且,当需要注入值的属性较多时,构造方法的参数列表也会很长,这是不合适的!

**注意:**在属性上使用@Resource注解也可以实现自动装配,并且,IntelliJ IDEA并不会提示灰色的波浪线,但是,并不表示“推荐使用@Resource取代@Autowired”,只是IntelliJ IDEA并不检查@Resource注解,并且,@Resource注解不支持其它注入方式。

关于Spring自动调用构造方法

使用组件扫描来创建Spring Bean时,Spring框架会自动调用组件类的构造方法,规则如下:

  • 如果类没有显式的添加构造方法,则会自动调用默认的构造方式
  • 如果类中仅有1个构造方法,无论是否有参数,Spring都会尝试自动调用
  • 如果类中有多个构造方法,在默认情况下,Spring将自动调用无参数的构造方法(如果存在的话),如果你希望Spring调用特定的构造方法,则需要在此构造方法上添加@Autowired

关于Spring Bean的作用域

所有Spring Bean默认都是单例的,如果希望某个Spring Bean不是单例的,可以通过@Scope注解进行配置。

  • 如果是通过组件扫描创建的Spring Bean,则在组件类上添加@Scope("prototype")
  • 如果是通过@Bean方法创建的Spring Bean,则在@Bean方法上添加@Scope("prototype")
  • **注意:**Spring Bean的表现特征可能是单例的,但Spring并不是通过单例模式来实现的

所有单例的Spring Bean默认都是预加载(相当于单例的饿汉式)的,如果希望某个Spring Bean是懒加载的,可以通过@Lazy注解进行配置。

  • 如果是通过组件扫描创建的Spring Bean,则在组件类上添加@Lazy
  • 如果是通过@Bean方法创建的Spring Bean,则在@Bean方法上添加@Lazy

关于Spring Bean的生命周期

在Spring Bean归属的类中,可以自定义“初始化”和“销毁”的生命方法,则Spring框架会在创建对象之后自动调用“初始化”的生命周期方法,并在销毁对象之前调用“销毁”的生命周期方法。

关于自定义的方法:

  • 应该是公有的

  • 应该是void返回值类型

    • 销毁方法可以使用布尔类型的返回值
  • 方法名称是自定义的

  • 参数列表应该为空

仅当添加了@PostConstruct注解的方法,才是“初始化”的生命周期方法,此方法会在构造方法之后自动执行。

仅当添加了@PreDestroy注解的方法,才是“销毁”的生命周期方法,此方法会在销毁当前类的对象之前自动执行。

例如:

@PostConstruct
public void init() {
    log.debug("执行生命周期方法:CategoryController的初始化方法");
}

@PreDestroy
public void destroy() {
    log.debug("执行生命周期方法:CategoryController的销毁方法");
}

关于IoC与DI

**IoC(Inversion of Control):**控制反转,即将各对象的控制权交给了Spring框架

DI(Dependency Injection) :依赖注入,即为各依赖项注入值

Spring框架负责的创建对象、管理对象都是Spring IoC的表现。

Spring框架通过DI完成了IoC,即DI是实现手段,IoC是需要实现的目标。

关于Spring AOP

再议