Spring框架

Spring框架
作用:主要解决了创建对象和管理对象的问题
在使用Spring框架后,当需要某个对象时,直接通过Spring获取对象即可。
Spring可以保证类的对象的唯一性,是的各组件出现依赖关系时,不会创建多个相同的对象。
由于Spring会创建很多对象并管理起来,开发者需要时直接获取即可,所以Spring通常被称为:“Spring容器”。

通过Spring创建并管理对象 - 组件扫描
Spring提供了组件扫描机制,它可以扫描指定的包(包含子孙包),并查找是否存在组件类,判定组件类的标准是类上是否有
组件注解,基础的组件注解有:

  • @Component
  • @Controller
  • @Service
  • @Repository
    在Spring框架的解释中,以上4个注解完全等效,只是语义不同。

所以,当某个类需要被Spring框架创建对象是,需要:

  • 确保这个类在组件扫描的范围之内
  • 确保这个类添加了组件注解
    提示:在Spring Boot项目中,默认就已经配置好了组件扫描,扫描的根包就是创建项目已存在的包

通过Spring创建并管理对象 - @Bean方法
在Spring框架使用中,如果某个类添加了@Configuration注解,则此类表示“配置类”。(也需在组件扫描范围内)
在配置类中,可以自行添加一些方法,方法上面个添加@Bean注解,则此方法会被Spring自动调用,且方法返回的对象也会
被Spring管理。(在同一个配置中,允许有多个@Bean方法存在)
关于方法的声明:
1.访问权限:应该是public
2.返回值类型:希望Spring管理的对象类型
3.方法名:自定义
4.方法体:应该正确的返回与声明匹配的对象

如何选择创建并管理对象的方式
1.自定义类型:两种方式都可以,更推荐组件扫描方式。
2.非自定义类型:只能使用@Bean方式(没有办法在这些类型上添加组件注解)

练习

controller包中创建BrandControllerPictureControllerAlbumController
也都将由Spring来创建对象,并测试是否可以获取到对象

自动装配

当某个属性需要值时,或被Spring调用的方法需要参数值时,Spring框架会自动从容器中查找符合的对象,
自动为属性或方法的参数赋值。

在属性上,添加@Autowired注解,即可使得Spring尝试自动装配。

练习

假设在项目中存在:

  • IBrandRepository
  • BrandRepositoryImpl
  • IBrandService
  • BrandServiceImpl
  • BrandController

其依赖关系是:在Service组件中需要使用到Repository,在Controller组件中需要使用到Service

要实现以上目标,需要:

  • 以上各类和接口都应该在组件扫描的根包或其子孙包下

  • IBrandRepositoryIBrandService不需要添加注解,而BrandRepositoryImpl应该添加@Repository注解,
    BrandServiceImpl添加@Service注解,BrandController添加@Controller注解

  • BrandServiceImpl中声明BrandRepositoryImpl类型的变量(暂时声明为public

    • 【测试】在没有添加@Autowired之前,此变量的值为null

      • 可以在测试类中自动装配BrandServiceImpl,并在测试方法中输出BrandRepositoryImpl变量的值,例如:

      @Autowired
      BrandServiceImpl brandService;

      @Test
      public void testBrandAutowired() {
      System.out.println(brandService.brandRepositoryImpl);
      }

      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

      - 【测试】当已经添加`@Autowired`之后,此变量的值为`BrandRepositoryImpl`类型的对象

      - 使得`BrandRepositoryImpl`实现`IBrandRepository`接口

      - 【测试】将`BrandServiceImpl`中,原本声明为`BrandRepositoryImpl`类型的变量,改为`IBrandRepository`类型,再次观察,此变量的值不变

      - 在`BrandController`中声明`BrandServiceImpl`类型的变量

      - 【测试】在没有添加`@Autowired`之前,此变量的值为`null`
      - 【测试】当已经添加`@Autowired`之后,此变量的值为`BrandServiceImpl`类型的对象

      - 使得`BrandServiceImpl`实现`IBrandService`接口

      - 【测试】将`BrandController`中,原本声明为`BrandServiceImpl`类型的变量,改为`IBrandService`类型,再次观察,此变量的值不变



      ## 练习

      假设在项目中存在:

      - `IAlbumRepository`
      - `AlbumRepositoryImpl`
      - `IAlbumService`
      - `AlbumServiceImpl`
      - `AlbumController`

      其依赖关系是:在`Service`组件中需要使用到`Repository`,在`Controller`组件中需要使用到`Service`。

      案例目标:

      - Spring能自动创建`AlbumRepositoryImpl`类的对象

      - 在`repo.impl`包下创建此类,并且,在此类上添加`@Repository`

      - 检验方式:添加构造方法并输出;在测试类中,自动装配此类型的对象,并输出

      - Spring能自动创建`AlbumServiceImpl`类的对象

      - 同上,此类应该在`service.impl`包下(`impl`包不存在,需创建)

      - 在`AlbumServiceImpl`类有`AlbumRepositoryImpl`的属性,并自动装配

      - 在`AlbumServiceImpl`内部,声明`AlbumRepositoryImpl`类型的变量,并添加`@Autowired`注解

      - 检查方式:在测试类中,输出`AlbumServiceImpl`对象的`AlbumRepositoryImpl`属性,应该是有值的

      - 创建`IAlbumRepository`接口,使得`AlbumRepositoryImpl`实现`IAlbumRepository`接口,并且,在`AlbumServiceImpl`应该是基于接口编程的,所以,原本声明为`AlbumRepositoryImpl`的变量,改为`IAlbumRepository`接口类型的声明

      - 实现在`Controller`组件中需要使用到`Service`,则需要先创建`AlbumController`类,并添加`@Controller`注解,然后,在`AlbumController`内部,声明`public AlbumServiceImpl albumService;`,并在此属性上添加`@Autowired`,则此属性将被Spring自动装配值,如果要调整为基于接口的编程,还需要创建`IAlbumService`接口,并使得`AlbumServiceImpl`实现此接口,最后,在`AlbumController`中,原本的声明`public AlbumServiceImpl albumService;`改为`public IAlbumService albumService;`即可

      - 检查方式:在测试类中自动装配`AlbumController`,在测试方法中输出`AlbumController`变量中的`albumService`属性的值,应该会输出有效值

      - ```java
      @Autowired
      AlbumController albumController;

      @Test
      public void testAlbumControllerAutowired() {
      System.out.println(albumController.albumService);
      }

Spring Bean的作用域

Spring管理的对象默认都是单例的,可以使用@Scope("prototype")使之“非单例”。

单例:单一实例,具体表现为:在某一时间点,此类的对象最多只有1个。
需要注意:Spring Bean的特性与通过设计模式中的单例模式创建的对象相同,但是,Spring框架并没有使用单例模式,所以,
在描述时,把Spring Bean描述为单例模式是不对的。

当Spring管理的对象是单例状态时,默认是预加载的(加载Spring环境时就会创建此类的对象,类似于设计模式中的单例模式的饿汉式),也可以使用@Lazy注解将其配置为懒加载的(加载Spring环境时并不会直接创建此类的对象,当第1次尝试获取此对象时,才会创建对象,类似于设计模式中的单例模式的懒汉式)。

Spring IoC与DI

IoC:Inversion of Control,即:控制反转,表现为:将对象的控制权(创建权力、管理权力)交给框架。

DI:Dependency Injection,即:依赖注入,当前类中的代码需要通过另一个类的执行来实现,则当前类依赖于另一个类,例如Controller依赖Service,Service依赖Mapper,Spring可以通过自动装配等机制为依赖项赋值,由于在编写的源代码中并没有使用到赋值符号(=),所以,这个行为叫作“注入”。

Spring框架通过DI完善的实现了IoC,所以,DI是一种实现手段,IoC是需要实现的目标。

小结

你应该:

  • 使得自定义的类被Spring管理对象
  • 确保类在组件扫描的包下,且在类上添加组件注解
  • 认识4个基础的组件注解
  • @Component / @Controller / @Service / @Repository
  • 了解Spring的自动装配
  • 在属性上添加@Autowired,则Spring会自动尝试为此属性赋值,是否成功,取决于Spring容器中是否存在合适的对象
  • 理解组件之间的调用关系
  • Controller >>> Service >>> Repository
  • 理解基于接口的编程的思想

你可能需要知道的

组件扫描

在Spring框架中,通过@ComponentScan注解,即可配置组件扫描

在Spring Boot项目中,默认即存在的类(有main()方法的那个类)上添加了@SpringBootApplication注解,此注解的源代码中包含@ComponentScan

在Spring Boot项目中,无论是执行默认即存在的类的main()方法,还是执行带@SpringBootTest注解的类中的测试方法,都会加载整个项目的配置,所以,组件扫描是已启动的。

@SpringBootApplication的声明上有@ComponentScan,则@SpringBootApplication具有@ComponentScan的效果。

@SpringBootApplication的声明上有@ComponentScan,则@ComponentScan可称之为@SpringBootApplication元注解

Spring AOP

AOP: 面向切面编程(Aspect Oriented Programing) 和 OOP 一样属于程序的一种设计思想。

一句话概括面向切面编程:
就是在不修改程序现有的代码前提下,可以设置某个方法运行之前或运行之后新增额外代码的操作。目标是将横切关注点与业务进行进一步分离,以提高代码的模块化程度,通过现有代码的基础上增加额外的“通知”(Advice)机制,能够对被声明的“切点”(PointCut)的代码快进行统一管理与扩展。

1
2
3
4
5
6
7
8
9
10
11
BrandController{
addNew(){
-----------------切面----------
brandService.insert();
}
}
BrandServiceImpl{
insert(){
//...
}
}

名词解释:
切面(Aspect):是一个可以加入额外代码运行的特定位置,一般指代方法之间的调用,可以在不修改源代码的情况下,
添加新的代码,对现有代码进行升级维护和管理。
织入(weaving):选定一个切面,利用动态代理技术,为原有的方法的持有者生成动态对象,然后将它和切面关联,在运行
原有方法时,就会按照织入之后的流程运行了。
通知(advice):通知是织入的代码的运行时机

  • 前置通知:(before advice)

  • 后置通知:(after advice)

  • 环绕通知:(around advice)

  • 异常通知:(after throwing advice)

定义切面的语法

1
2
3
4
5
6
7
8
9
10
11
12
13
execution(
modifier-pattern,?
ret-type-pattern,
declaring-type-pattern,?
name-pattern(param-pattern),
throws-pattern?
)
带有?是可选属性,不带有?的是必须写的
modifier-pattern - 访问修饰符(可选)
ret-type-pattern - 返回值类型(必须)
declaring-type-pattern - 全路径类名(可选)
name-pattern(param-pattern) - 参数列表(必须)
throws-pattern - 抛出的异常类型(可选)

分析下面切面表达的含义:

1
2
3
4
5
6
execution(* *(..))
匹配全部方法:Spring框架能扫描到的范围内的全部方法
execution(public * cn.tedu.jsd2203.csmall.server.controller.BrandController.*(..))
匹配cn.tedu.jsd2203.csmall.server.controller.BrandController类中所有被public修饰的方法
execution(* cn.tedu.jsd2203.csmall.server.controller.*.*(..))
匹配cn.tedu.jsd2203.csmall.server.controller包中所有类的所有方法

项目中使用AOP

1
2
3
4
5
pom.xml导入依赖项
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

需求: 需要统计每个业务方法的执行耗时
1.定义切面 2.环绕通知 3.耗时:System.currentTimeMills();

AOP主要解决的问题:性能检测、日志监控、事务管理等等。
主流框架都使用了AOP,所以一般不需要手动编写AOP,@Transactional是用AOP实现的。
实际应用场景:”打补丁“,观察程序流程执行和入参是否正确。

关于@Autowired注解与@Resource注解的区别

在使用Spring框架时,在类的属性上使用@Autowired@Resource都可以实现自动装配!

关于这2个注解,@Autowired是Spring框架定义的注解,@Resource注解是javax包中注解!

关于@Autowired的装配机制是:

  1. 先根据类型在Spring容器中查找匹配的对象,看看有多少个

  2. 如果匹配类型的对象的数量为0,即没有,取决于@Autowiredrequired属性
    2.1. 如果required=true,则装配失败,在加载Spring时就会报错
    2.2. 如果required=false,则放弃自动装配,需要自动装配的属性的值为null,加载Spring时并不会报错,
    但在后续的使用中,可能会出现NPE(空指针异常)

  3. 如果匹配类型的对象有且仅有1个,则直接装配

  4. 如果匹配类型的对象的数量超过1个(2个或更多个),将尝试根据名称来装配
    4.1. 如果存在名称匹配的对象,则成功装配
    4.2. 如果不存在名称匹配的对象,则装配失败,在加载Spring时就会报错

关于@Resource的装配机制是先尝试根据名称来装配,如果成功,则装配完成,如果失败,会尝试根据类型来装配,如果仍失败,
则装配失败!

从最终的体验来看,这2个注解都可以实现相同的效果,使用@Autowired能成功装配的,使用@Resource也可以,
使用@Autowired无法装配的,使用@Resource也无法装配。

自动装配的名称

当Spring尝试实现自动装配时,会从Spring容器中查找合适的对象,关于“合适”的判定标准,首先,类型必须匹配,
如果匹配类别的Bean有多个,必须保证名称匹配才可以正确装配。

关于名称匹配:被自动装配的属性名称,与Bean的名称相同。

关于Bean的名称:如果类名的第1个字母是大写的,且第2个字母是小写的,则Bean的名称是将类名首字母改为小写,
例如BrandRepositoryImpl的Bean名称就是brandRepositoryImpl,否则,Bean名称就是类名。