【Java进阶篇】spring框架

Spring

创建项目

通过Spring创建向导来创建项目,创建参数:

  • Server URL:https://start.spring.io 或 https://start.springboot.io
  • Name:csmall-server
  • Group:com.unkeer
  • Artifact:csmall-server(手动调整)
  • Package Name:com.unkeer.csmall.server(手动调整)
  • Java版本:81.8

在后续的界面中,可以随意选择某个版本,并且,不勾选任何依赖项。

当项目创建成功后,打开pom.xml,在默认的第8行中,将版本号改为2.5.9

Spring框架的作用

主要解决了创建对象和管理对象的问题。

在使用Spring框架后,当需要某个对象时,直接通过Spring获取此对象即可。

Spring可以保证类的对象的唯一性,使得各组件出现依赖关系时,不会创建多个相同的对象。

由于Spring会创建很多个对象并管理起来,开发者需要时直接获取即可,所以,Spring也通常被称之为“Spring容器”。

通过Spring创建并管理对象–组件扫描

Spring提供了组件扫描的机制,它可以扫描指定的包,并查找此包及其子孙包下是否存在组件类,判定组件类的标准是类上是否存在组件注解,基础的组件注解有:

  • @Component
  • @Controller
  • @Service
  • @Repository

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

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

  1. 确保这个类在组件扫描的范围之内
  2. 确保这个类添加了组件注解

提示:在Spring Boot项目中,默认就已经配置好了组件扫描,扫描的根包就是创建项目时已存在的包。

通过Spring创建并管理对象–@Bean方法

在Spring框架的使用中,如果某个类添加了@Configuration注解,则此类将是“配置类”。

注意:配置类也必须在组件扫描的范围内。

在配置类中,可以自行添加一些方法,在方法上添加@Bean注解,则此方法会被Spring自动调用,且方法返回的对象也会被Spring管理。

在同一个配置类中,允许存在多个@Bean方法。

关于方法的声明:

  • 访问权限:应该是public
  • 返回值类型:你希望Spring管理的对象的类型
  • 方法体:应该正确的返回与声明匹配的对象
  • 方法名称:自定义

所以,当某个类需要被Spring框架管理对象时,需要:

  • 在配置类中添加@Bean方法,使用此方法返回需要被管理的对象

如何选择创建并管理对象的方式

仅自定义的类型可以使用组件扫描的方式,当然,自定义的类型也可以使用@Bean方法的做法,但是不推荐。

非自定义的类型不可以使用组件扫描的方式(因为你没有办法在这些类型上添加组件注解),只能使用@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);
        }
        
    • 【测试】当已经添加@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属性的值,应该会输出有效值

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

DAY01–当日小结

你应该:

  • 使得自定义的类被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元注解

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

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

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

关于@Autowired的装配机制是:

  1. 先根据类型在Spring容器中查找匹配的对象,看看有多少个
  2. 如果匹配类型的对象的数量为0,即没有,取决于@Autowiredrequired属性
    1. 如果required=true,则装配失败,在加载Spring时就会报错
    2. 如果required=false,则放弃自动装配,需要自动装配的属性的值为null,加载Spring时并不会报错,但在后续的使用中,可能会出现NPE
  3. 如果匹配类型的对象有且仅有1个,则直接装配
  4. 如果匹配类型的对象的数量超过1个(2个或更多个),将尝试根据名称来装配
    1. 如果存在名称匹配的对象,则成功装配
    2. 如果不存在名称匹配的对象,则装配失败,在加载Spring时就会报错

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

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

在使用方面,@Autowired@Resource也存在不同:

  • @Resource仅在属性上可以实现自动装配
  • @Autowired可以添加在属性上、方法上、构造方法上,用于实现自动装配的效果

事实上,在专业的开发工具中(例如IntelliJ IDEA),是不建议在属性上使用@Autowired来实现自动装配的,因为它们认为这是不安全的做法,如果Spring没有被正常加载,则此属性将不会被自动装配,则属性的值为null,如果没有使用其它手段解决此问题,在使用过程中就会出现NPE!

  • 通常,并不存在Spring没有被正常加载的情况,则以上问题基本上不会出现,即使出现,也是非常小概率事件
  • 在某些测试中,确实可能不加载Spring环境,则存在以上关心的NPE风险

通常,这些开发工具会建议使用构造方法来注入属性的值,例如,当在属性上使用@Autowired时,IntelliJ IDEA会提示:

Field injection is not recommended 

以上提示的意思是:

字段的注入是不推荐的

当尝试使用构造方法注入时,代码大概是:

@RestController
public class AlbumController {
    
    

    // 注意:此属性上并没有自动装配的注解
    private IAlbumService albumService;
    
    // 注意:此构造方法添加了参数,且参数会用于对属性赋值
    public AlbumController(IAlbumService albumService) {
    
    
        this.albumService = albumService;
    }
    
}

以上做法可以实现与传统在属性上添加@Autowired完全相同的效果!这种做法是推荐的,因为无论是否加载Spring,要想创建此类的对象,就必须调用构造方法,如果以上构造方法是当前类中唯一的构造方法,就必须传入值,则不会出现NPE问题(除非故意传null值)。

以上使用构造方法可以成功为属性赋值,也源自于Spring的自动装配机制,因为自动装配指的是:当某个属性需要值时,或被Spring调用的方法(通常是配置类中的@Bean方法,或构造方法)需要参数值时,Spring框架会自动从容器中查找符合的对象,自动为属性或方法的参数赋值。

但是,使用构造方法为属性注入值是比较麻烦的,特别是需要装配的属性发生变化时,构造方法需要一并调整,甚至,在某些类中,需要注入的属性较多,则构造方法需要较多参数,与常规代码的设计也是不符的!

总的来说,使用@Autowired语法简洁,需要调整代码时也非常方便,但是,存在NPE风险(概率非常低),而使用构造方法则非常安全,不会出现NPE,但是,语法相对麻烦,且调整代码也很麻烦!由于使用@Autowired时出现NPE的概率太低了,所以,在一般的开发实践中,仍是使用@Autowired居多!

另外,关于Spring调用构造方法:

  • 如果类中没有显式的定义构造方法,则JAVA编译器会在编译期添加默认构造方法,且Spring会自动调用
  • 如果类中有且仅有1个构造方法,无论此构造方法是有参数的,还是无参数的,Spring都会自动尝试调用
  • 如果类中有多个构造方法,Spring会尝试调用带@Autowired注解的构造方法(应该只有1个构造方法添加了此注解),如果所有构造方法都没有@Autowired注解,则Spring会尝试调用默认的构造方法(即无参数的构造方法),如果所有构造方法都没有@Autowired注解且都有参数,则Spring无法调用

自动装配的名称

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

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

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

关于名称,还可以使用注解来指定名称,例如:

// 通过以下@Resource注解的name指定名称后,将会通过Spring容器中名为categoryServiceImpl的Bean来装配
@Resource(name = "categoryServiceImpl")
private ICategoryService categoryService;

或者:

// 通过以下@Qualifier注解指定名称后,将会通过Spring容器中名为categoryServiceImpl的Bean来装配
// @Qualifier是配合Spring框架的自动装配机制使用的,专门用于指定Bean的名称的
@Qualifier("categoryServiceImpl")
@Autowired
private ICategoryService categoryService;

Spring Bean的作用域

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

单例:单一实例,具体表现为:在某一时间点,此类的对象最多只有1个。

需要注意:Spring Bean的特性与通过设计模式中的单例模式创建的对象相同,但是,Spring框架并没有使用单例模式,所以,在描述时,把Spring Bean描述为单例模式是不对的。

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

Spring Bean的生命周期

在自定义的、被Spring管理的类中,可以自定义方法,在方法上添加@PostConstruct注解后,此方法将变为Spring Bean生命周期中的初始化方法,则会在创建此类的对象之后,被Spring自动调用,还可以自定义方法,在方法上添加@PreDestroy注解后,此方法将变为Spring Bean生命周期中的销毁方法,则会在销毁对象的前一刻,被Spring自动调用。

Spring IoC与DI

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

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

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

Spring AOP

AOP:面向切面的编程

注意:AOP并不是Spring特有的技术,只是Spring对AOP提供了很好的支持

在项目中,处理的处理流程大致是:

注册 --(请求)--> Controller --> Service.reg() --> Mapper

登录 --(请求)--> Controller --> Service.login() --> Mapper

改密 --(请求)--> Controller --> Service.changePassword() --> Mapper

假设存在某个需求:需要统计每个业务方法的执行耗时。

要想实现此目标,可以自定义一个切面类:

package com.unkeer.csmall.server.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 * 统计各Service方法的执行耗时的切面类
 *
 * @author yunnuo
 * @version 0.0.1
 */
@Slf4j
@Component
@Aspect
public class TimerAspect {
    
    

    // 切面的通知(Advice),可以是:Around(环绕)、Before、After、AfterThrowing、AfterReturning
    // 其它通知的执行例如:
    // Advice >>> Before
    // try {
    
    
    //      pjp.proceed();
    //      Advice >>> AfterReturning
    // } catch (Throwable e) {
    
    
    //      Advice >>> AfterThrowing
    // } finally {
    
    
    //      Advice >>> After
    // }
    @Around("execution(* cn.tedu.csmall.server.service.impl.*.*(..))")
    public Object timer(ProceedingJoinPoint pjp) throws Throwable {
    
    
        log.debug("执行了切面方法……");

        long start = System.currentTimeMillis();

        // 调用此方法,将执行业务方法(因为通知中配置的表达式匹配的是业务方法)
        // 注意:调用的proceed()方法是抛出异常的,【必须】在当前切面方法上声明抛出(不能try...catch)
        // 调用proceed()方法的返回值就是业务方法的返回值,【必须】作为当前切面方法的返回值
        Object result = pjp.proceed();

        long end = System.currentTimeMillis();
        log.debug("当前切面匹配到的组件类:{}", pjp.getTarget());
        log.debug("当前切面匹配到的方法:{}", pjp.getSignature());
        log.debug("当前切面匹配到的方法的参数列表:{}", pjp.getArgs());
        log.debug("当前业务方法的执行耗时:{}ms", (end - start));

        // 【必须】返回调用proceed()方法返回的结果
        return result;
    }

}

AOP主要解决的问题:性能监测、日志跟踪、事务管理等等。

事实上,在Java开发领域中,有许多主流的框架都使用到了AOP,所以,一般不需要手动编写AOP。

练习

使得Spring框架创建以下类型的对象(在以下各类的构造方法中添加输出语句):

  • com.unkeer.csmall.server.repo.impl.AttributeRepositoryImpl
  • com.unkeer.csmall.server.repo.impl.AttributeTemplateRepositoryImpl
  • com.unkeer.csmall.server.repo.impl.SkuRepositoryImpl
  • com.unkeer.csmall.server.repo.impl.SpuRepositoryImpl
  • com.unkeer.csmall.server.service.impl.AttributeServiceImpl
  • com.unkeer.csmall.server.service.impl.AttributeTemplateServiceImpl
  • com.unkeer.csmall.server.service.impl.SkuServiceImpl
  • com.unkeer.csmall.server.service.impl.SpuServiceImpl
  • com.unkeer.csmall.server.controller.AttributeController
  • com.unkeer.csmall.server.controller.AttributeTemplateController
  • com.unkeer.csmall.server.controller.SkuController
  • com.unkeer.csmall.server.controller.SpuController

并创建repo下对应的各接口:

  • com.unkeer.csmall.server.repo.IAttributeRepository
  • com.unkeer.csmall.server.repo.IAttributeTemplateRepository
  • com.unkeer.csmall.server.repo.ISkuRepository
  • com.unkeer.csmall.server.repo.ISpuRepository

实现各Service中通过接口声明Repository组件,并实现自动装配。

service下创建对应的各接口:

  • com.unkeer.csmall.server.service.IAttributeService
  • com.unkeer.csmall.server.service.IAttributeTemplateService
  • com.unkeer.csmall.server.service.ISkuService
  • com.unkeer.csmall.server.service.ISpuService

实现各Controller中通过接口声明Service组件,并实现自动装配。

关于Spring框架的异常

NoSuchBeanDefinitionException

无此Bean的异常

在异常信息中会提示到底是缺少哪个Bean,例如以下提示的com.unkeer.csmall.server.controller.CategoryController,然后,再结合你的配置来检查!

创建对象的方式只有2种,要么是组件扫描,要么是@Bean方法,检查对应的代码即可。

如果提示的类型并不是自已配置的,且应该是正常的可用的,可考虑为依赖项错误的问题,则参考《前置技术清单中第3条来解决》。

org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.unkeer.csmall.server.controller.CategoryController' available

NoUniqueBeanDefinitionException

不唯一的Bean的异常

当尝试自动装配时,匹配类型的Bean超过1个

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.unkeer.csmall.server.repo.IBrandRepository' available: expected single matching bean but found 2: brandRepositoryImpl,brandRepositoryImpl2

猜你喜欢

转载自blog.csdn.net/u013488276/article/details/125900888