Spring

如何理解SpringBoot的Starter机制

  • SpringBoot Starters是一组预配置的依赖项集合,用于启动特定类型的功能。例如,可以用spring-boot-starter-web启用web应用程序相关的功能,包括内嵌的Web服务器、SpringMVC、Jackson等,这样只需引入这个Starter,而无需单独处理每个依赖项的版本和配置;
  • SpringBoot 在启动的时候,按照约定去读取 SpringBoot Starter 的配置信息,再根据配置信息对资源进行初始化,并注入到 Spring 容器中。这样 SpringBoot 启动完毕后,就已经准备好了一切资源,使用过程中直接注入对应 Bean 资源即可;

如何理解SpringBoot自动配置

  1. SpringBoot启动的时候通过@EnableAutoConfiguration注解去依赖的 Starter 包中寻找 resources/META-INF/spring.factories配置文件中所有的自动配置类,并对其进行加载;
  2. 而这些自动配置类的类名都是以AutoConfiguration结尾来命名的,它实际上就是一个javaConfig形式的Spring容器配置类,它们都有一个@EnableConfigurationPerperties的注解,通过这个注解启动XXXProperties命名的类去加载全局配置中的属性,如server.port;
  3. 而XXXProperties通过@ConfigurationProperties注解将全局配置文件中的属性与自己的属性进行绑定;

自定义一个Starter

  1. 创建 Starter 项目,定义 Starter 需要的配置(Properties)类,比如数据库的连接信息;
  2. 编写自动配置类,自动配置类就是获取配置,根据配置来自动装配 Bean;
  3. 编写 spring.factories 文件加载自动配置类,Spring 启动的时候会扫描 spring.factories 文件;
  4. 在项目中引入自定义 Starter 的 Maven 依赖,增加配置值后即可使用。

Spring依赖注入类型

字段注入、构造器注入、Setter方法注入。

  1. 字段注入
public class ClientService {
    @Autowired
    private HealthRecordService healthRecordService;
    public void recordUserHealthData() {
        healthRecordService.recordUserHealthData();
    }
}

字段注入的实现方式非常简单而直接,代码的可读性也很高,

但不建议使用。三个原因:
(1)对象的外部可见性,ClientService类中,通过定义一个私有变量healthRecordService来注入该接口的实例。显然,这个实例只能在ClientService类中被访问,脱离了容器环境就无法进行访问。

ClientService clientService = new ClientService();
clientService.recordUserHealthData();

执行这段代码的结果就是抛出一个NullPointerException空指针异常,原因是无法在ClientService的外部实例化HealthRecordService对象。采用字段注入,类与容器的耦合度过高,我们无法脱离容器来使用目标对象。

(2)导致潜在的循环依赖。

public class ClassA {
    @Autowired
    private ClassB classB;
}
public class ClassB {
    @Autowired
    private ClassA classA;
}

这里的ClassA和ClassB发生了循环依赖。上述代码在Spring中是合法的,容器启动时并不会报任何错误,而只有在使用到具体某个ClassA或ClassB时才会报错。

(3)无法设置需要注入的对象为final,也无法注入那些不可变对象,这是因为字段必须在类实例化时进行实例化。

  1. 构造器注入
public class ClientService {
    private HealthRecordService healthRecordService;
    @Autowired
    public ClientService(HealthRecordService healthRecordService) {
        this.healthRecordService = healthRecordService;
    }
    public void recordUserHealthData() {
        healthRecordService.recordUserHealthData();
    }
}

通过类的构造函数来完成对象的注入。
(1)可以看到构造器注入能解决对象外部可见性的问题,因为HealthRecordService是通过ClientService构造函数进行注入的,所以势必可以脱离ClientService而独立存在。
(2)构造器注入能够保证注入的组件不可变,并且确保需要的依赖不为空。这里的组件不可变就意味着我们可以使用final关键词来修饰所依赖的对象,而依赖不为空是指所传入的依赖对象肯定是一个实例对象,从而避免出现空指针异常。
(3)对循环依赖问题,在Spring项目启动的时候,就会抛出一个循环依赖异常,从而提醒你避免使用循环依赖。

public class ClassA {
    private ClassB classB;
    @Autowired
    public ClassA(ClassB classB) {
        this.classB = classB;
    }
}
public class ClassB {
    private ClassA classA;
    @Autowired
    public ClassB(ClassA classA) {
        this.classA = classA;
    }
}

(4)但是,构造器注入的显著问题就是当构造函数中存在较多依赖对象时,大量的构造器参数会让代码冗长。这时候就可以引入Setter方法注入。

  1. Setter方法注入
public class ClientService {
    private HealthRecordService healthRecordService;
    @Autowired
    public void setHealthRecordService(HealthRecordService healthRecordService) {
        this.healthRecordService = healthRecordService;
    }
    public void recordUserHealthData() {
        healthRecordService.recordUserHealthData();
    }
}

(1)可以把多个依赖对象分别通过Setter方法逐一进行注入,对于非强制依赖项注入很有用,我们可以有选择地注入一部分依赖对象。
(2)Setter方法可以很好地解决应用程序中的循环依赖问题,前提是ClassA和ClassB的作用域都是Singleton。

public class ClassA {
    private ClassB classB;
    @Autowired
    public void setClassB(ClassB classB) {
        this.classB = classB;
    }
}
public class ClassB {
    private ClassA classA;
    @Autowired
    public void setClassA(ClassA classA) {
        this.classA = classA;
    }
}

(3)通过Setter注入可以对依赖对象进行多次重复注入,这在构造器注入中是无法实现的。

4. 总结

构造器注入适合于强制对象注入;Setter注入适合于可选对象注入;而字段注入是应该被避免使用的,因为对象无法脱离容器而独立运行。

Spring循环依赖问题

Spring框架中的循环依赖问题指的是两个或多个bean相互持有对方的引用,形成了一个闭环,导致无法正常创建这些bean的实例。

在Spring框架中,循环依赖问题主要有三种情况:

  • 通过字段注入或构造方法进行依赖注入时产生的循环依赖问题。这种情况下,当尝试创建对象时,由于循环依赖,可能会导致对象无法正确初始化,从而引发异常。
  • 通过setter方法进行依赖注入且是在多例(prototype)模式下产生的循环依赖问题。由于每次调用getBean()都会创建新的Bean,这可能导致无穷无尽的Bean创建,最终可能导致内存溢出(OOM)问题。
  • 通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。Spring通过三级缓存机制解决了这个问题。

Spring引入了三级缓存机制:

一级缓存:也称为单例池(singletonObjects),用于存放完全初始化后的单例bean。
二级缓存:称为提前曝光对象池(earlySingletonObjects),用于存放原始的bean实例(尚未填充属性的对象)。
三级缓存:称为提前曝光对象工厂(singletonFactories),用于存放能够创建bean实例的工厂对象。

Spring采用了提前暴露对象的方法。整个流程大致如下:

  • A完成初始化第一步并将自己提前曝光出来(通过ObjectFactory将自己提前曝光)。在初始化的时候,发现自己依赖对象B,此时就会去尝试get(B),这个时候发现B还没有被创建出来;
  • B开始创建流程,在B初始化的时候,同样发现自己依赖C,C也没有被创建出来;
  • C开始初始化进程,但是在初始化的过程中发现自己依赖A,由于A已经提前曝光,所以可以通过ObjectFactory#getObject()方法来拿到A对象。C拿到A对象后顺利完成初始化,然后将自己添加到一级缓存中;
  • 回到B,B也可以拿到C对象,完成初始化,A可以顺利拿到B完成初始化。

此外,还可以使用@Lazy注解来解决Spring循环依赖问题。@Lazy注解可以使一个Bean实现延时加载,从而避免在初始化阶段就产生循环依赖的问题。

Spring Cloud

SpringCloud是一个基于SpringBoot,用来简化分布式开发的一个微服务架构开发工具,五大核心组件为Eureka、Fegin、Ribbon、Hystrix、Gateway(或Zuul)。

五大组件运行流程

在这里插入图片描述

Eureka(注册中心)

Eureka用来注册服务的,其中包括两部分:Eureka Client、Eureka Server

  • Eureka Client:包含服务提供者、服务消费者,主要负责服务注册、心跳续约与健康状况查询;
  • Eureka Server:提供注册服务,各个节点启动后,都会在Eureka Server中注册,可用的节点信息可以在Eureka Server中的服务注册表中找到;

Feign

Feign是一个HTTP请求的轻量级客户端框架。通过 接口+注解 的方式发起HTTP请求的调用,而不是像Java中通过封装HTTP请求报文的方式直接调用。

Feign执行流程:

  • 在主程序上添加@EnableFeignClients注解开启对已经加@FeignClient注解的接口进行扫描加载;
  • 调用接口中的方法时,基于JDK动态代理的方式,通过InvokeHandler调用处理器分发远程调用,生成具体的RequestTemplate;
  • RequestTemplate生成Request请求,结合Ribbon实现服务调用负载均衡策略;

Feign最核心的就是动态代理,同时整合了Ribbon和Hystrix,具备负载均衡、隔离、熔断和降级功能

Ribbon

Ribbon是一个客户端的负载均衡器,他提供对大量的HTTP和TCP客户端的访问控制。
Ribbon负载均衡策略:简单轮询、权重、随机、重试等多种策略。

Hystrix

熔断:简单来说,就是我们生活中的“保险丝”。如果熔断器打开,就会执行短路,直接进行降级;如果熔断器关闭,就会进入隔离逻辑。默认触发熔断器的条件为:最近一个时间窗口(默认为10秒),总请求次数大于等于circuitBreakerRequestVolume Threshold(默认为20次)并且异常请求比例大于circuitBreakerError ThresholdPercentage(默认为50%),此时熔断器触发;

隔离:一般分为两种:线程池隔离和信号量隔离。线程池隔离指的是每个服务对应一个线程池,线程池满了就会降级;信号量隔离是基于tomcat线程池来控制的,当线程达到某个百分比,走降级流程;

降级:作为Hystrix中兜底的策略,当出现业务异常、线程池已满、执行超时等情况,调用fallback方法返回一个错误提示;

Gateway

网关相当于一个网络服务架构的入口,所有网络请求必须通过网关转发到具体的服务。

Gateway是一个在系统框架中起到入口和出口作用的组件,负责接受外部请求并转发给相应的服务,并将服务的相应返回给请求方。

主要功能

  • 请求路由:根据预定义的路由规则将请求发送到对应的服务或者资源;
  • 负载均衡:可以在后端的多个实例服务之间请求分发,提高系统的性能和可靠性;
  • 安全认证和授权:集成各种认证机制,增加系统的安全性;
  • 流量控制:可以限制每个服务的访问速率,防止恶意请求对系统造成太大影响;
  • 缓存和访问优化:可以缓存响应数据以及对请求进行优化;
  • 监控与日志记录:对请求和响应进行监控并记录相关的日志信息;

负载均衡算法

  1. 循环:客户端请求按顺序发送到不同的服务实例。通常要求服务是无状态的。这种算法最简单,但是也无法处理某个节点变慢或者客户端操作有连续性的情况;
  2. 粘性循环:对循环算法的一种改进。如果用户A的第一个请求发送到服务 A,那么接下来的请求也会发送到服务 A。这种负载均衡可以确保一个用户的请求都发往同一个服务节点,适合客户端操作有连续性的情况。有时候该服务节点上会保存该用户的一些状态,避免去后端数据库查询;
  3. 加权循环:管理员可以指定每个服务的权重。权重高的服务会比其他服务处理更多请求;
  4. 散列:该算法对传入请求的 IP 或 URL 应用哈希函数,根据哈希函数的结果将请求路由到相关服务;
  5. 最少连接:新请求会发送到并发连接最少的服务节点;
  6. 响应时间最少:新请求会发送到响应时间最快的服务节点;

Spring事务传播机制

Spring 事务传播机制是为了规定多个事务在传播过程中的行为。比如方法 A 开启了事务,而在执行过程中又调用了开启事务的 B 方法,那么 B 方法的事务是应该加入到 A 事务当中,还是两个事务相互执行互不影响,又或者是将 B 事务嵌套到 A 事务中执行呢?所以这个时候就需要一个机制来规定和约束这两个事务的行为,这就是 Spring 事务传播机制。

Spring 事务传播机制可使用 @Transactional(propagation=Propagation.REQUIRED) 来定义,Spring 事务传播机制的级别包含以下 7 种:

  • Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务;
  • Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行;
  • Propagation.MANDATORY:(mandatory:强制性)如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常;
  • Propagation.REQUIRES_NEW:表示创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰;
  • Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起;
  • Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常;
  • Propagation.NESTED:这种传播行为会创建一个嵌套的事务,如果当前已经存在一个事务,那么它将在这个事务内部创建一个新的保存点(savepoint)。这样,内部事务可以独立于外部事务进行回滚,而不影响外部事务。如果内部事务成功提交,那么外部事务将继续执行。如果内部事务失败并导致异常,那么内部事务会被回滚到最近的保存点,而外部事务可以选择继续或者回滚整个事务。

最典型的事务传播级别为例:支持当前事务的 REQUIRED、不支持当前事务的 REQUIRES_NEW。

下来我们分别来看。

事务传播机制的示例,需要用到以下两张表:

-- 用户表
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
  `password` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
  `createtime` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC;

-- 日志表
CREATE TABLE `log` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `content` text NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

创建一个 Spring Boot 项目,核心业务代码有 3 个:UserController、UserServcie 以及 LogService。在 UserController 里面调用 UserService 添加用户,并调用 LogService 添加日志。

REQUIRED 使用演示

UserController 实现代码如下,其中 save 方法开启了事务:

@RestController
public class UserController {
    @Resource
    private UserService userService;
    @Resource
    private LogService logService;

    @RequestMapping("/save")
    @Transactional
    public Object save(User user) {
        // 插入用户操作
        userService.save(user);
        // 插入日志
        logService.saveLog("用户插入:" + user.getName());
        return true;
    }
}

UserService 实现代码如下:

@Service
public class UserService {
    @Resource
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public int save(User user) {
        return userMapper.save(user);
    }
}

LogService 实现代码如下:

@Service
public class LogService {
    @Resource
    private LogMapper logMapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public int saveLog(String content) {
        // 出现异常
        int i = 10 / 0;
        return logMapper.saveLog(content);
    }
}

执行结果:程序报错,两张表中都没有插入任何数据。

执行流程描述:

  1. 首先 UserService 中的添加用户方法正常执行完成;
  2. LogService 保存日志程序报错,因为使用的是UserController中的全局事务,所以整个事务回滚,插入用户的操作也跟着回滚;
  3. 所以数据库中没有添加任何数据;

REQUIRED_NEW 使用演示

REQUIRED_NEW 不支持当前事务。UserController 实现代码:

@RequestMapping("/save")
@Transactional
public Object save(User user) {
    // 插入用户操作
    userService.save(user);
    // 插入日志
    logService.saveLog("用户插入:" + user.getName());
    return true;
}

UserService 实现代码:

@Service
public class UserService {
    @Resource
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public int save(User user) {
        System.out.println("执行 save 方法.");
        return userMapper.save(user);
    }
}

LogService 实现代码:

@Service
public class LogService {
    @Resource
    private LogMapper logMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public int saveLog(String content) {
        // 出现异常
        int i = 10 / 0;
        return logMapper.saveLog(content);
    }
}

程序执行结果:User 表中成功添加了一条用户数据,Log 表执行失败,没有加入任何数据,但它并没有影响到 UserController 中的事务执行。

通过以上结果可以看出:LogService 中使用的是单独的事务,虽然 LogService 中的事务执行失败了,但并没有影响 UserController 和 UserService 中的事务。

IOC

  1. 控制反转,即把创建对象的权利交给框架,也就是指将对象的创建、对象的存储、对象的管理交给了Spring容器。Spring容器是Spring中的一个核心模块,用于管理对象,底层可以理解为是一个Map集合。
  2. Spring IOC最关键的作用在于解耦,它可以解除对象之间的耦合,让对象和对象之间完全没有联系,这样我们在完成或修改一个对象时不需要考虑其它对象;
  3. 依赖注入:依赖注入(DI)与控制反转(IOC)含义相同,只不过是从两个角度描述同一个概念,Spring容器负责将被依赖对象赋值给调用者的成员变量,这相当于为调用者注入了它依赖的实例,即依赖注入;

AOP

  • AOP(面向切面编程):将那些与业务无关,却为业务模块所共同调用的逻辑或责任(日志记录、权限控制、性能统计等)分开封装起来,这样各司其职,不仅减少系统的重复代码,还降低了业务逻辑和通用功能的耦合度;
  • 保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。AOP 就是代理模式的典型应用。
  • 通知:就是会在目标方法执行前后执行的方法;
  • 切入点:应用通知进行增强的目标方法;
  • 连接点:连接点就是可以应用通知进行增强的方法;
  • 切面:切入点和通知的结合;
  • 织入:就是通过动态代理对目标对象方法进行增强的过程;

示例

1 创建一个UserDao类:

@Repository
// 以下3个方法都是连接点
public class UserDao {
	 public void addUser(){
		 System.out.println("添加用户");
	 }
	 public void updateUser(){
		 System.out.println("修改用户");
	 }
	 public void deleteUser(){
		 System.out.println("删除用户");
	 }
}

2 创建一个切面类:

@Aspect
public class MyAspectLog {
    /**
     * log方法是后置通知,通过在方法上加上@After注解来表示
     * addUser()是切入点
     */
	@After(value="execution(* cn.xh.dao.UserDao.addUser(..))")
    public void log(){
        System.out.println("记录日志");
    }
}

3 在spring配置文件中加入:

<!-- 启动@aspectj的自动代理支持-->
<aop:aspectj-autoproxy />

<!-- 定义aspect类 -->
<bean name="myAspect" class="cn.xh.dao.MyAspectLog"/>

4 当我们创建UserDao的对象userDao调用addUser方法的时候会打印“添加用户”,“记录日志”;

Spring AOP和AspectJ AOP有什么区别?

  1. Spring AOP是属于运行时增强,而AspectJ是编译时增强。Spring AOP基于代理,而AspectJ基于字节码操作;
  2. Spring AOP已经集成了AspectJ,AspectJ应该算得上是Java生态系统中最完整的AOP框架了。AspectJ相比于Spring AOP功能更加强大,但是Spring AOP相对来说更简单;
  3. 如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择AspectJ,它比Spring AOP快很多;

Spring AOP的两种实现方式

  1. 有两种实现方式:基于接口的 JDK 动态代理和基于继承的 CGLIB 动态代理;
  2. JDK 动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理。而 CGLIB 动态代理是利用 ASM 开源包,加载代理对象类的 class 文件,通过修改其字节码生成子类来处理;
  3. JDK 动态代理只能对实现了接口的类生成代理,而不能针对类。CGLIB 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法不能声明成 final 类型;
  4. JDK动态代理特点:1 代理对象必须实现一个或多个接口;2 以接口的形式接收代理实例,而不是代理类;
  5. CGLIB动态代理特点:1 代理对象不能被 final 修饰;2 以类或接口形式接收代理实例;
  6. JDK与CGLIB动态代理的性能比较:生成代理实例性能:JDK > CGLIB;代理实例运行性能:JDK > CGLIB;

JSP——Servlet——Spring MVC

MVC模式

JSP+JavaBean

  1. JSP+JavaBean 中 JSP 用于处理用户请求,JavaBean 用于封装和处理数据。该模式只有视图和模型,一般把控制器的功能交给视图来实现,适合业务流程比较简单的 Web 程序;

在这里插入图片描述

  1. 通过上图可以发现 JSP 从 HTTP Request(请求)中获得所需的数据,并进行业务逻辑的处理,然后将结果通过 HTTP Response(响应)返回给浏览器。从中可见,JSP+JavaBean 模式在一定程度上实现了 MVC,即 JSP 将控制层和视图合二为一,JavaBean 为模型层;
  2. JSP+JavaBean 模式中 JSP 身兼数职,既要负责视图层的数据显示,又要负责业务流程的控制,结构较为混乱,并且也不是我们所希望的松耦合架构模式,所以当业务流程复杂的时候并不推荐使用;

Servlet+JSP+JavaBean

  1. Servlet+JSP+JavaBean 中 Servlet 用于处理用户请求,JSP 用于数据显示,JavaBean 用于数据封装,适合复杂的 Web 程序;

在这里插入图片描述

  1. 相比 JSP+JavaBean 模式来说,Servlet+JSP+JavaBean 模式将控制层单独划分出来负责业务流程的控制,接收请求,创建所需的 JavaBean 实例,并将处理后的数据返回视图层(JSP)进行界面数据展示。
  2. Servlet+JSP+JavaBean 模式的结构清晰,是一个松耦合架构模式,一般情况下,建议使用该模式。

Spring MVC

  1. Spring MVC可以帮助我们进行更简洁的Web层的开发,它天生与 Spring 框架集成;
  2. Spring MVC 下⼀般把后端项目分为 Controller层(控制层,返回数据给前台页面)、Service层(处理业务)、Dao层(数据库操作)、Entity层(实体类);

Spring MVC 工作流程
在这里插入图片描述

  1. 客户端(浏览器)发送请求,请求到DispatcherServlet(前端控制器) ;
  2. DispatcherServlet根据请求信息调用HandlerMapping(处理器映射器),其作用是根据请求的 URL 路径,通过注解或者 XML 配置,寻找匹配的Handler(处理器)信息;
  3. HandlerAdapter(处理器适配器)会根据处理器映射器找到的Handler信息按照特定规则执行相关的Handler;
  4. Handler处理完业务后,会返回⼀个 ModelAndView 对象, Model 是数据对象, View是逻辑上的 View;
  5. ViewResolver(视图解析器)会根据逻辑 View 查找实际的 View (如通过一个 JSP 路径返回一个真正的 JSP 页面);
  6. DispatcherServlet把返回的 Model 传给 View (视图渲染);
  7. 把 View 返回给请求者(浏览器);

Spring MVC 重要组件

1 DispatcherServlet

  • 说明:前端控制器,不需要工程师开发,由 SpringMVC 框架提供。
  • 作用:Spring MVC 的入口。接收请求,响应结果,相当于转发器,中央处理器。DispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,DispatcherServlet 降低了组件之间的耦合度。

2 HandlerMapping

  • 说明:处理器映射器,不需要工程师开发,由 SpringMVC 框架提供。
  • 作用:根据请求的 url 查找 Handler。SpringMVC 提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。

3 HandlerAdapter

  • 说明:处理器适配器。
  • 作用:按照特定规则(HandlerAdapter要求的规则)执行 Handler。通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。

4 Handler

  • 说明:处理器,需要工程师开发。
  • 注意:编写 Handler 时按照 HandlerAdapter 的要求的规则去做,这样适配器才可以去正确执行 Handler, Handler 是后端控制器,在 DispatcherServlet 的控制下 Handler 对具体的用户请求进行处理。 由于 Handler 涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发 Handler。

5 ViewResolver

  • 说明:视图解析器,不需要工程师开发,由 SpringMVC 框架提供。
  • 作用:进行视图解析,根据逻辑视图名解析成真正的视图。ViewResolver 负责将处理结果生成 View 视图, ViewResolver 首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成 View 视图对象,最后对View 进行渲染将处理结果通过页面展示给用户。 SpringMVC 框架提供了很多的 View 视图类型,包括:jstlView、freemarkerView、pdfView等。 一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要工程师根据业务需求开发具体的页面。

6 View

  • 说明:视图 View,需要工程师开发。
  • 作用:View 是一个接口,实现类支持不同的 View类型(jsp、freemarker、pdf…)。

在 Spring MVC 框架中,Controller 替换 Servlet 来担负控制器的职责,用于接收请求,调用相应的 Model 进行处理,处理器完成业务处理后返回处理结果。Controller 调用相应的 View 并对处理结果进行视图渲染,最终客户端得到响应信息。

比于之前的servlet,它一定程度上简化了开发人员的工作,使用servlet的话需要每个请求都去在web.xml中配置一个servlet,而Spring 中的DispatcherServlet会拦截所有的请求,进一步去查找有没有合适的处理器,一个前端控制器就可以。

Spring——Spring Boot

Spring

1 方便解耦,简化开发

通过Spring提供的IoC容器,我们可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。

2 AOP编程的支持

提供了面向切面编程实现,提供比如日志记录、权限控制、性能统计等通用功能和业务逻辑分离的技术,并且能动态的把这些功能添加到需要的代码中,这样各司其职,降低业务逻辑和通用功能的耦合。

保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。AOP 就是代理模式的典型应用。

3 方便集成各种优秀框架

Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如MyBatis 等)的直接支持。

4 方便程序的测试

Spring 支持 JUnit4,可以通过注解方便地测试 Spring 程序。

5 声明式事务的支持

只需要通过配置就可以完成对事务的管理,而无须手动编程。

Spring Boot

Spring Boot 具有 Spring 一切优秀特性,Spring 能做的事,Spring Boot 都可以做,而且使用更加简单,功能更加丰富,性能更加稳定而健壮。

1 独立运行的 Spring 项目

Spring Boot 可以以 jar 包的形式独立运行,Spring Boot 项目只需通过命令“ java–jar xx.jar” 即可运行。

2 内嵌 Servlet 容器

Spring Boot 使用嵌入式的 Servlet 容器(例如 Tomcat、Jetty 或者 Undertow 等),应用无需打成 WAR 包 。

3 提供 starter 简化 Maven 配置

Spring Boot 提供了一系列的“starter”项目对象模型(POMS)来简化 Maven 配置。

4 提供了大量的自动配置

Spring Boot 提供了大量的默认自动配置,来简化项目的开发,开发人员也通过配置文件修改默认配置。

5 无代码生成和 xml 配置

Spring Boot 不需要任何 xml 配置即可实现 Spring 的所有配置。

Spring框架中用到了哪些设计模式?

  1. 工厂模式:Spring使用工厂模式通过BeanFactory和ApplicationContext创建Bean对象;
  2. 代理模式:Spring AOP的实现;
  3. 单例模式:Spring中的bean默认是单例的;
  4. 适配器模式:Spring MVC中用到了适配器模式适配Controller;

BeanFactory和ApplicationContext的区别

  1. BeanFactory:提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能;
  2. ApplicationContext:应用上下文,继承BeanFactory接口,它是Spring的一各更高级的容器,提供了更多的有用的功能:(1)国际化;(2)访问资源,如URL和文件;(3)载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层 ;(4)消息发送、响应机制;(5)AOP(拦截器);
  3. 两者装载bean的区别:BeanFactory在启动的时候不会去实例化Bean,当从容器中拿Bean的时候才会去实例化;ApplicationContext在启动的时候就把所有的Bean全部实例化了,它还可以为Bean配置lazy-init=true来让Bean延迟实例化;
  4. 延迟实例化的优点(BeanFactory):应用启动的时候占用资源很少,对资源要求较高的应用,比较有优势;
  5. 不延迟实例化的优点(ApplicationContext): (1)所有的Bean在启动的时候都加载,系统运行速度快; (2)在启动的时候所有的Bean都加载了,我们就能在系统启动的时候,尽早发现系统中的配置问题;(3)建议web应用,在启动的时候就把所有的Bean都加载了(把费时的操作放到系统启动中完成)。

Spring中的Bean的作用域有哪些?

  1. singleton:默认单例模式,表示在Spring容器中只有一个Bean实例,Bean以单例的方式存在;
  2. prototype:每次通过Spring容器获取Bean时,容器都会创建一个新的Bean实例;
  3. request:每次HTTP请求都会创建一个新的Bean,该作用域仅在当前HTTP Request内有效;
  4. session:同一个HTTP Session共享一个Bean实例,不同的Session使用不同的Bean实例。该作用域仅在当前HTTP Session内有效;

Spring中的单例Bean的线程安全问题

单例Bean存在线程安全问题,当多个线程操作同一个对象的时候,对这个对象的成员变量的写操作会存在线程安全问题。

解决方案:在类中定义ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中。

常用的SpringBoot注解,及其实现

  1. @SpringBootApplication:标识了一个SpringBoot工程,实际上是另外3个注解的组合:

    1. @SpringBootConfiguration:这个注解实际上就是一个@Configuration,表示启动类也是一个配置类;
    2. @EnableAutoConfiguration:向Spring容器中导入了一个Selector,用来加载ClassPath下SpringFactories中定义的自动配置类,将这些自动加载为配置Bean;
    3. @ComponentScan:标识扫描路径,因为默认没有配置实际扫描路径,所以Spring Boot扫描的路径是启动类所在的当前目录;
  2. @Bean注解:用来定义Bean,类似于XML中的<bean>标签,Spring在启动时,会对加了@Bean注解的方法进行解析,将方法的名字作为BeanName,并通过执行方法得到Bean对象;

@RestController 和 @Controller

  • Controller 返回⼀个页面:单独使用 @Controller 不加 @ResponseBody 的话⼀般使用在要返回⼀个视图的情况,这种情况属于比较传统的Spring MVC 的应用,对应于前后端不分离的情况;

在这里插入图片描述

  • @RestController 返回JSON 或 XML 形式数据:但 @RestController 只返回对象,对象数据直接以 JSON 或 XML 形式写⼊ HTTP 响应(Response)中,属于 RESTful Web服务,这也是⽬前⽇常开发所接触的最常⽤的情况(前后端分离);

在这里插入图片描述

Maven下各种包的含义

  1. Config:用于存放相关配置类,包括启动类;
  2. Controller:所有请求的入口,前后端交互的入口;
  3. Service:负责所有的业务逻辑;
  4. Mapper:或称Dao(数据库访问对象),持久层,负责Java和数据库交互,包括interface和xml两类文件;((View\Web)表示层调用控制层(Controller),控制层调用业务层(Service),业务层调用数据访问层(Dao)。)
  5. Domain:或称Po(persistent object,持久对象),用Java类来映射数据库表,类名就相当于表名,类的属性就相当于表的字段;
  6. Dto(Data Transfer Object):数据传输对象,用于前后端数据交互;
  7. Domain类的属性完全和表的字段一致;Dto类的属性一般和表字段一致,但会根据不同的业务场景适当增加或减少属性;
  8. Domain用于Java数据和数据库表记录的映射,用在Service和Mapper;Dto用于前后端数据传输,用在Service和Controller;Service介于Controller和Mapper之间,也是Domain和Dto的转换层;

Idea快捷键

  • Ctrl + F:当前文件查找
  • Ctrl + R:当前文件替换
  • Ctrl + Shift + F:全局查找
  • Ctrl + Shift + R:全局替换
  • Ctrl + Shift + N:按文件名查找文件
  • Ctrl + Shift + A:查找所有的菜单或操作
  • 连按两次Shift:查找文件、菜单、操作等,但不能查找文件内容
  • 复制历史:Ctrl+Shift+V
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hellosc01

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值