1.技术体系

技术架构包括单一架构(一个项目包含了所有的业务逻辑,全部集中在一个项目中)和分布式架构(将一个项目拆分为多个模块,每个模块是一个 IDEA 中的一个 module。每一个工程都是运行在自己的 Tomcat 上。模块之间可以互相调用。每一个模块内部可以看成是一个单一架构的应用。)

1.1 单一架构

一个项目,一个工程,导出为一个war包,在一个Tomcat上运行。也叫all in one。

Mybatis:是一个作用在持久层上简化数据库操作的框架,是对JDBC一个封装,是简化了数据库的操作。

SpringMVC:框架是作用在表述层(控制层),可以简化与前端的数据交互(简化接受前端发过来的参数,简化与前端响应的数据。)

Spring:框架其实在每一层都有包含,但是其偏爱数据逻辑层,在数据逻辑层简化事务的声明,以及简化AOP代码的编写

单一架构,项目主要应用技术框架为:Spring , SpringMVC , Mybatis。

1.2 分布式架构

分布式架构:将一个项目拆分为多个模块,每个模块是一个 IDEA 中的一个 module。每一个工程都是运行在自己的 Tomcat 上。模块之间可以互相调用。每一个模块内部可以看成是一个单一架构的应用。

在分布式架构中,如果还像单一架构那样只用到SSM技术的话(SSM需要写整个配置),则每一个单独的模块都需要重写整个配置,非常麻烦,因此,SpringBoot(是对SSM整合的一种框架,本质还是SSM,但是可以快速的让我们搭建一个服务,可以提升每一个服务创建的过程)应运而生,并且在分布式架构中,每一个模块之间往往需要相互调用,这时候又用到的SpringCloud框架(维护各个服务之间统一管理,相互调用等事情),同时,微服务还会设计到一些中间件(往往是第三方程序,进行数据缓存和数据传递),可以在程序之间,把数据缓存到中间件之间,进行数据的传递。

分布式架构,项目主要应用技术框架:SpringBoot (SSM), SpringCloud , 中间件等。

2.框架结构和理解

2.1 框架概念

框架( Framework )是一个集成了基本结构、规范、设计模式、编程语言和程序库等基础组件的软件系统(说白了,其实就是一个系统)。

站在文件结构角度理解框架,可以将框架总结:框架=jar包+配置文件(大部分框架功能都是可以经过配置文件进行定制化修改的)。如果一个东西只有jar包,不支持配置文件的定制化,不叫框架,只有包含jar,并支持配置文件的定制化才能叫框架。

2.2 框架作用

作用:它可以用来构建更高级别的应用程序。框架的设计和实现旨在解决特定领域中的常见问题,帮助开发人员更高效、更稳定地实现软件开发目标。

2.3 框架优点

1.提高开发效率

2.降低开发成本

3.提高应用程序稳定性(做框架的人都是java行业金字塔顶端的人物,相比普通程序员做的系统,稳定性高点没意见吧)

4.提供标准的解决方案

2.4 框架缺点

1.学习成本高

2.可能存在局限性

3.版本变更和兼容性问题

4.架构风险(目前学习的框架非常成熟,几乎不存在这种问题)

3.SpringFramework介绍

3.1 Spring和SpringFramework概念

广义上Spring就是指以Spring Framework为基础的Spring技术栈。

经过十多年的发展,Spring 已经不再是一个单纯的应用框架,而是逐渐发展成为一个由多个不同子项目(模块)组成的成熟技术,例如 Spring Framework、Spring MVC、SpringBoot、Spring Cloud、Spring Data、Spring Security 等,其中 Spring Framework 是其他子项目的基础。

这些子项目涵盖了从企业级应用开发到云计算等各方面的内容,能够帮助开发人员解决软件发展过程中不断产生的各种实际问题,给开发人员带来了更好的开发体验。

狭义的Spring:等同于Spring Framework(基础框架)(这种说法现在来看,不太准确)。

Spring Framework(Spring框架)是一个开源的应用程序框架,由SpringSource公司开发,最初是为了解决企业级开发中各种常见问题而创建的。它提供了很多功能,例如:依赖注入(Dependency Injection)、面向切面编程(AOP)、声明式事务管理(TX)等。其主要目标是使企业级应用程序的开发变得更加简单和快速,并且Spring框架被广泛应用于Java企业开发领域。

Spring全家桶的其他框架都是以SpringFramework框架为基础!

3.2 SpringFramework框架

 

功能模块功能介绍Core Container(不需要程序员去实例化对象(new对象))核心容器,在 Spring 环境下使用任何功能都必须基于 IOC 容器。AOP&Aspects面向切面编程TX声明式事务管理。Spring MVC提供了面向Web应用程序的集成功能。

3.3 Spring IOC容器和核心概念

容器说白了就是存放东西,这个东西就是组件。而组件说白了就是对象,只不过是可以复用的java对象。因此,主键一定是对象,但对象不一定是组件。

IOC的容器是由接口加实现类组成

3.3.1 组件和组件管理概念

 期待

- 有人替我们创建组件的对象 - 有人帮我们保存组件的对象 - 有人帮助我们自动组装 - 有人替我们管理事务 - 有人协助我们整合其他框架

Spring可以充当组件管理角色(IoC)

组件可以完全交给Spring 框架进行管理,Spring框架替代了程序员原有的new对象和对象属性赋值动作等!

Spring具体的组件管理动作包含:

- 组件对象实例化 - 组件属性属性赋值 - 组件对象之间引用 - 组件对象存活周期管理

 我们只需要编写元数据(配置文件)告知Spring 管理哪些类组件和他们的关系即可!

Spring 充当一个组件容器,创建、管理、存储组件,减少了我们的编码压力,让我们更加专注进行业务编写!

 组件交给Spring管理的优势:

1. 降低了组件之间的耦合性:Spring IoC容器通过依赖注入机制,将组件之间的依赖关系削弱,减少了程序组件之间的耦合性,使得组件更加松散地耦合。 2. 提高了代码的可重用性和可维护性:将组件的实例化过程、依赖关系的管理等功能交给Spring IoC容器处理,使得组件代码更加模块化、可重用、更易于维护。 3. 方便了配置和管理:Spring IoC容器通过XML文件或者注解,轻松的对组件进行配置和管理,使得组件的切换、替换等操作更加的方便和快捷。4. 交给Spring管理的对象(组件),方可享受Spring框架的其他功能(AOP,声明事务管理)等

3.3.2容器介绍

普通容器:

程序中的普通容器

- 数组 - 集合:List - 集合:Set

 复杂容器

程序中的复杂容器

Servlet 容器能够管理 Servlet(init,service,destroy)、Filter、Listener 这样的组件的一生,所以它是一个复杂容器。

名称时机次数创建对象默认情况:接收到第一次请求 修改启动顺序后:Web应用启动过程中一次初始化操作创建对象之后一次处理请求接收到请求多次销毁操作Web应用卸载之前一次

SpringIoC 容器也是一个复杂容器。

IOC的容器是由接口加实现类组成,最大接口Beanfactory扩展接口是ApplicationContext

实现类:可直接通过构造函数实例化

1.ClassPathXmlApplicationContext

2.FileSystemXmlApplicationContext

3.AnnotationConfigApplicationContext

4.WebApplicationContext

它们不仅要负责创建组件的对象、存储组件的对象,还要负责调用组件的方法让它们工作,最终在特定情况下销毁组件。

3.3.3 Spring容器具体接口和实现类

SpringIOC容器接口:

接口名称作用BeanFactory提供了一种高级配置机制,能够管理任何类型的对象,它是SpringIoC容器标准化超接口ApplicationContext 是 BeanFactory 的子接口。它扩展了以下功能: 1.更容易与 Spring 的 AOP 功能集成 2.消息资源处理(用于国际化) 3.特定于应用程序给予此接口实现,例如Web 应用程序的 `WebApplicationContext`

简而言之, `BeanFactory` 提供了配置框架和基本功能,而 `ApplicationContext` 添加了更多特定于企业的功能。 `ApplicationContext` 是 `BeanFactory` 的完整超集!

 常用的Spring实现类(在实际开发中只需选择一个就行)

类型名简介使用条件ClassPathXmlApplicationContext通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象,读取的是编译后的classes文件 1.配置文件是xml格式。 2.配置文件放在项目在类路径下例如放到resources下。 FileSystemXmlApplicationContext通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象 1.配置文件是xml格式。 2.配置文件存储到项目外,某个盘符下d:\xx\xx.xml AnnotationConfigApplicationContext通过读取Java配置类创建 IOC 容器对象配置文件使用的是java类。WebApplicationContext专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。web项目对应的IOC容器。

3.3.4 SpringIoC容器管理配置方式

Spring框架提供了多种配置方式:XML配置方式、注解方式和Java配置类方式

1. XML配置方式:是Spring框架最早的配置方式之一,通过在XML文件中定义Bean及其依赖关系、Bean的作用域等信息,让Spring IoC容器来管理Bean之间的依赖关系。该方式从Spring框架的第一版开始提供支持。 2. 注解方式:从Spring 2.5版本开始提供支持,可以通过在Bean类上使用注解来代替XML配置文件中的配置信息。通过在Bean类上加上相应的注解(如@Component, @Service, @Autowired等),将Bean注册到Spring IoC容器中,这样Spring IoC容器就可以管理这些Bean之间的依赖关系。 3. **Java配置类**方式:从Spring 3.0版本开始提供支持,通过Java类来定义Bean、Bean之间的依赖关系和配置信息,从而代替XML配置文件的方式。Java配置类是一种使用Java编写配置信息的方式,通过@Configuration、@Bean等注解来实现Bean和依赖关系的配置。

为了迎合当下开发环境,我们将以**配置类+注解方式**为主进行讲解!

3.3.5 SpringIoC容器的功能、

1.IoC(Inversion of Control)容器是控制反转对象。

IoC 主要是针对对象的创建和调用控制而言的,也就是说,当应用程序需要使用一个对象时,不再是应用程序直接创建该对象,而是由 IoC 容器来创建和管理,即控制权由应用程序转移到 IoC 容器中,也就是“反转”了控制权。这种方式基本上是通过依赖查找的方式来实现的,即 IoC 容器维护着构成应用程序的对象,并负责创建这些对象。 

2.DI (Dependency Injection) 依赖注入

 DI 是指在组件之间传递依赖关系的过程中,将依赖关系在容器内部进行处理,这样就不必在应用程序代码中硬编码对象之间的依赖关系,实现了对象之间的解耦合。在 Spring 中,DI 是通过 XML 配置文件或注解的方式实现的。它提供了三种形式的依赖注入:构造函数注入、Setter 方法注入和接口注入。

 4.IoC实践基本步骤

下面从代码角度来分析三个步骤:

1.配置元数据(配置)

配置元数据,既是编写交给SpringIoC容器管理组件的信息,配置方式有三种。

基于 XML 的配置元数据的基本结构:

       

         

         

Spring IoC 容器管理一个或多个组件。这些 组件是使用你提供给容器的配置元数据(例如,以 XML `` 定义的形式)创建的。

标签 == 组件信息声明

- `id` 属性是标识单个 Bean 定义的字符串。 - `class` 属性定义 Bean 的类型并使用完全限定的类名。

2.实例化IoC容器

提供给 `ApplicationContext` 构造函数的位置路径是资源字符串地址,允许容器从各种外部资源(如本地文件系统、Java `CLASSPATH` 等)加载配置元数据。

我们应该选择一个合适的容器实现类,进行IoC容器的实例化工作:

//实例化ioc容器,读取外部配置文件,最终会在容器内进行ioc和di动作 ApplicationContext context =             new ClassPathXmlApplicationContext("services.xml", "daos.xml"); 

3.获取Bean组件 

ApplicationContext 是一个高级工厂的接口,能够维护不同 bean 及其依赖项的注册表。通过使用方法 T getBean(String name, Class requiredType) ,您可以检索 bean 的实例。

示例:

//创建ioc容器对象,指定配置文件,ioc也开始实例组件对象 ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");//获取ioc容器的组件对象 PetStoreService service = context.getBean("petStore", PetStoreService.class);//使用组件对象 List userList = service.getUsernameList(); 

package com.example.test;

import com.example.ioc_03.HappyComponent;

import com.example.ioc_04.JavaBean;

import javafx.application.Application;

import org.junit.jupiter.api.Test;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringIoCTest {

/*讲解如何创建IoC容器并且读取配置文件*/

public void createIoC(){

//创建容器 选择合适的容器实现即可,IOC的容器是由接口加实现类组成

//最大接口Beanfactory扩展接口是ApplicationContext

//实现类:可直接通过构造函数实例化 1.ClassPathXmlApplicationContext 2.FileSystemXmlApplicationContext 3.AnnotationConfigApplicationContext

//4.WebApplicationContext

//方式1,直接创建容器并且指定配置文件即可。

//构造函数(String ..配置文件)可以填写一个或多个参数。

ApplicationContext applicationContext= new ClassPathXmlApplicationContext("spring-03");

//方式2:先创建ioc容器,再指定配置文件,再刷新

//源码的配置过程

ClassPathXmlApplicationContext applicationContext1=new ClassPathXmlApplicationContext();

applicationContext1.setConfigLocations("spring-03.xml");//外部配置文件的设置

applicationContext1.refresh();//调用ioc和di的流程

}

@Test

/*讲解如何在IoC容器中获取组件Bean*/

public void getBeanFromIoC(){

//读取之前要创建IoC容器

ClassPathXmlApplicationContext applicationContext= new ClassPathXmlApplicationContext();

applicationContext.setConfigLocations("spring-03.xml");

applicationContext.refresh();

//读取ioc容器的组件

//方案一:直接根据beanId读取即可,返回值类型是Object,需要强转【不推荐】

HappyComponent happyComponent=(HappyComponent) applicationContext.getBean("happyComponent");

//方案二:根据beanId 同时指定bean的类型Class

HappyComponent happyComponent1=applicationContext.getBean("happyComponent",HappyComponent.class);

//方案三:直接根据类型获取

HappyComponent happyComponent2=applicationContext.getBean(HappyComponent.class);

happyComponent2.doWork();

System.out.println(happyComponent == happyComponent1);

System.out.println(happyComponent2 == happyComponent1);

}

5.基于XML配置方式组件管理

5.1 实验一:组件(Bean)信息声明配置(IoC) 

1.目标:

Spring IoC 容器管理一个或多个 bean。这些 Bean 是使用您提供给容器的配置元数据创建的(例如,以 XML `` 定义的形式)。

我们学习,如何通过定义XML配置文件,声明组件类信息,交给 Spring 的 IoC 容器进行组件管理!

2.思路:

 组件类有三种配置文件形式,本次实验采用的是XML形式。

补充;在不考虑反射的情况下,自己应该如何实例化对象?

 

5.2 实验二:组件(Bean)依赖注入配置(DI)

1. 目标

    通过配置文件,实现IoC容器中Bean之间的引用(依赖注入DI配置)。

    主要涉及注入场景:基于构造函数的依赖注入和基于 Setter 的依赖注入。

5.4 实验四:spring-ioc-扩展组件周期方法

我们可以在组件类中定义方法,然后当IoC容器实例化和销毁组件对象的时候进行调用!这两个方法我们成为生命周期方法!

类似于Servlet的init/destroy方法,我们可以在周期方法完成初始化和释放资源等工作。

 我们可以在组件类中定义方法,然后当IoC容器实例化和销毁组件对象的时候进行调用!这两个方法我们成为生命周期方法!

方法必须时public,返回类型必须是void。必须是无参数的,但方法名是任意的。

 //测试IOC配置和销毁方法的触发。

@Test public void test_04(){

//创建IoC容器

ClassPathXmlApplicationContext applicationContext= new ClassPathXmlApplicationContext();

applicationContext.setConfigLocations("spring-04.xml");

applicationContext.refresh();

//读取IoC容器组件

JavaBean javaBean=applicationContext.getBean("javaBean",JavaBean.class); } }

 2.组件作用域配置

1. Bean作用域概念

    `

    在IoC容器中,这些`

    这意味着,`BeanDefinition`与`类`概念一样,SpringIoC容器可以可以根据`BeanDefinition`对象反射创建多个Bean对象实例。

    具体创建多少个Bean的实例对象,由Bean的作用域Scope属性指定!

取值含义创建对象的时机默认值singleton在 IOC 容器中,这个 bean 的对象始终为单实例IOC 容器初始化时是prototype这个 bean 在 IOC 容器中有多个实例获取 bean 时否

如果是在WebApplicationContext环境下还会有另外两个作用域(但不常用):

取值含义创建对象的时机默认值request请求范围内有效的实例每次请求否session会话范围内有效的实例每次会话否

5.5 实验五:spring-ioc-扩展factoryBean使用

FactoryBean:首先,要知道这是一个接口,用于配置复杂的Bean对象,把对象的创建过程存储在FactoryBean 的getObject方法!

这个接口提供了三种方法:

1.T getObject():  返回此工厂创建的对象的实例。该返回值会被存储到IoC容器! 2.boolean isSingleton(): 如果此 FactoryBean 返回单例,则返回 true ,否则返回 false 。此方法的默认实现返回 true(注意,lombok插件使用,可能影响效果)。 3.Class getObjectType(): 返回 getObject() 方法返回的对象类型,如果事先不知道类型,则返回 null。

FactoryBean使用场景 1. 代理类的创建  2. 第三方框架整合 3. 复杂对象实例化等。

应用案例:

组件类:

package com.example.ioc_05;

public class JavaBean {

private String name;

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

@Override

public String toString() {

return "JavaBean{" +

"name='" + name + '\'' +

'}';

}

}

FactoryBean实现类

package com.example.ioc_05;

import org.springframework.beans.factory.FactoryBean;

//制造JavaBean的工厂bean对象

//步骤:1.实现FactoryBean接口<返回值泛型>

public class JavaBeanFactoryBean implements FactoryBean {

private String value;

public String getValue() {

return value;

}

public void setValue(String value) {

this.value = value;

}

@Override

public JavaBean getObject() throws Exception {

//使用你自己的方式实例化对象就可以了

JavaBean javaBean=new JavaBean();

javaBean.setName(value);

return javaBean;

}

@Override

public Class getObjectType() {

return JavaBean.class;

}

}

配置文件

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

高频面试题:

BeanFactory和FactoryBean的区别?

这两个都是接口,BeanFactory是IoC容器最大的接口,说白了就是IoC容器。FactoryBean是标准化组件工厂的接口,说白了就是组件。

FactoryBean是 Spring 中一种特殊的 bean,可以在 getObject() 工厂方法自定义的逻辑创建Bean!是一种能够生产其他 Bean 的 Bean。FactoryBean 在容器启动时被创建,而在实际使用时则是通过调用 getObject() 方法来得到其所生产的 Bean。因此,FactoryBean 可以自定义任何所需的初始化逻辑,生产出一些定制化的 bean。

一般情况下,整合第三方框架,都是通过定义FactoryBean实现!!!

BeanFactory 是 Spring 框架的基础,其作为一个顶级接口定义了容器的基本行为,例如管理 bean 的生命周期、配置文件的加载和解析、bean 的装配和依赖注入等。BeanFactory 接口提供了访问 bean 的方式,例如 getBean() 方法获取指定的 bean 实例。它可以从不同的来源(例如 Mysql 数据库、XML 文件、Java 配置类等)获取 bean 定义,并将其转换为 bean 实例。同时,BeanFactory 还包含很多子类(例如,ApplicationContext 接口)提供了额外的强大功能。

总的来说,FactoryBean 和 BeanFactory 的区别主要在于前者是用于创建 bean 的接口,它提供了更加灵活的初始化定制功能,而后者是用于管理 bean 的框架基础接口,提供了基本的容器功能和 bean 生命周期管理。

5.6 基于xml配置的案例实践

1.项目要求

搭建一个三层架构案例,模拟查询全部学生(学生表)信息,持久层使用JdbcTemplate和Druid技术,使用XML方式进行组件管理!

2.数据库建表语句

create database studb;

use studb;

CREATE TABLE students (   id INT PRIMARY KEY,   name VARCHAR(50) NOT NULL,   gender VARCHAR(10) NOT NULL,   age INT,   class VARCHAR(50) );

INSERT INTO students (id, name, gender, age, class) VALUES   (1, '张三', '男', 20, '高中一班'),   (2, '李四', '男', 19, '高中二班'),   (3, '王五', '女', 18, '高中一班'),   (4, '赵六', '女', 20, '高中三班'),   (5, '刘七', '男', 19, '高中二班'),   (6, '陈八', '女', 18, '高中一班'),   (7, '杨九', '男', 20, '高中三班'),   (8, '吴十', '男', 19, '高中二班');  

3.依赖导入

                            org.springframework           spring-context           6.0.6      

                      mysql           mysql-connector-java           8.0.25      

                com.alibaba           druid           1.2.8      

                      org.springframework           spring-jdbc           6.0.6      

 

4.实体类准备

public class Student {

    private Integer id;     private String name;     private String gender;     private Integer age;     private String classes;

    public Integer getId() {         return id;     }

    public void setId(Integer id) {         this.id = id;     }

    public String getName() {         return name;     }

    public void setName(String name) {         this.name = name;     }

    public String getGender() {         return gender;     }

    public void setGender(String gender) {         this.gender = gender;     }

    public Integer getAge() {         return age;     }

    public void setAge(Integer age) {         this.age = age;     }

    public String getClasses() {         return classes;     }

    public void setClasses(String classes) {         this.classes = classes;     }

    @Override     public String toString() {         return "Student{" +                 "id=" + id +                 ", name='" + name + '\'' +                 ", gender='" + gender + '\'' +                 ", age=" + age +                 ", classes='" + classes + '\'' +                 '}';     } }  

5.持久层代码

//接口 public interface StudentDao {

    /**      * 查询全部学生数据      * @return      */     List queryAll(); }

//实现类 public class StudentDaoImpl implements StudentDao {

    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {         this.jdbcTemplate = jdbcTemplate;     }

    /**      * 查询全部学生数据      * @return      */     @Override     public List queryAll() {

        String sql = "select id , name , age , gender , class as classes from students ;";

        /*           query可以返回集合!           BeanPropertyRowMapper就是封装好RowMapper的实现,要求属性名和列名相同即可          */         List studentList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Student.class));

        return studentList;    } }  

6.业务层代码

//接口 public interface StudentService {

    /**      * 查询全部学员业务      * @return      */     List findAll();

}

//实现类 public class StudentServiceImpl  implements StudentService {          private StudentDao studentDao;

    public void setStudentDao(StudentDao studentDao) {         this.studentDao = studentDao;     }

    /**      * 查询全部学员业务      * @return      */     @Override     public List findAll() {                  List studentList =  studentDao.queryAll();                  return studentList;     } }  

7.表述层(控制层代码)

public class StudentController {          private StudentService studentService;

    public void setStudentService(StudentService studentService) {         this.studentService = studentService;     }          public void  findAll(){        List studentList =  studentService.findAll();         System.out.println("studentList = " + studentList);     } }

8.三层架构的xml配置

       

                                           

                           

先把持久层对象添加到容器里,再注入jdbcTemplate对象                

先把业务层层对象添加到容器里,再注入studentDao(持久层)对象

               

先把控制层对象添加到容器里,再注入studentService(业务层)对象

               

注意,这里的注入是使用的set方法,因此,在每一层的实现类中,都要构建要调用对象,并构建set方法,只有这样,这里才能通过标签来进行依赖注入

 

6.基于注解的方式管理Bean(组件)(对象) 

6.1 实验一:Bean注解标记和扫描(IoC)

1.类上添加IoC标签(这个操作就相当于在xml文件配置下,通过把类添加到IoC容器中)。

2.告诉springioc容器,都在那些包下添加了ioc注解(这个是写在xml配制文件中)。

和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。

6.1.1 组件添加标记注解

Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。

注解说明@Component该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。@Repository该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。@Service该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。@Controller该注解通常作用在控制层(如SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

通过查看源码我们得知,@Controller、@Service、@Repository这三个注解只是在@Component注解的基础上起了三个新的名字。

对于Spring使用IOC容器管理这些组件来说没有区别,也就是语法层面没有区别。所以@Controller、@Service、@Repository这三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。

注意:虽然它们本质上一样,但是为了代码的可读性、程序结构严谨!我们肯定不能随便胡乱标记。

 

这个添加注解,默认的命名是首字母小写,上图添加@Component相当于在xml文件中写。如果要自定义命名,在注解后面加上(value="自定义的命名")单个value可以省略,直接在注解后加("自定义命名")。

6.2 实验二:组件的作用域和周期方法注解

1. 周期方法概念

    我们可以在组件类中定义方法,然后当IoC容器实例化和销毁组件对象的时候进行调用!       这两个方法我们成为生命周期方法!

    类似于Servlet的init/destroy方法,我们可以在周期方法完成初始化和释放资源等工作。

1. 周期方法声明 public class BeanOne {

  //周期方法要求: 方法命名随意,但是要求方法必须是 public void 无形参列表   @PostConstruct  //注解制指定初始化方法   public void init() {     // 初始化逻辑   } }

public class BeanTwo {      @PreDestroy //注解指定销毁方法   public void cleanup() {     // 释放资源逻辑   } } ```

1. 组件作用域配置     1. Bean作用域概念

       标签声明Bean,只是将Bean的信息配置给SpringIoC容器!

        在IoC容器中,这些`标签对应的信息转成Spring内部 `BeanDefinition` 对象,`BeanDefinition` 对象内,包含定义的信息(id,class,属性等等)!

        这意味着,`BeanDefinition`与`类`概念一样,SpringIoC容器可以可以根据`BeanDefinition`对象反射创建多个Bean对象实例。

        具体创建多少个Bean的实例对象,由Bean的作用域Scope属性指定!     2. 作用域可选值

取值含义创建对象的时机默认值singleton在 IOC 容器中,这个 bean 的对象始终为单实例IOC 容器初始化时是prototype这个 bean 在 IOC 容器中有多个实例获取 bean 时否 如果是在WebApplicationContext环境下还会有另外两个作用域(但不常用):                                

取值含义创建对象的时机默认值request请求范围内有效的实例每次请求否session会话范围内有效的实例每次会话否

作用域配置                                                                                                                                     

@Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON) //单例,默认值 @Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) //多例  二选

6.3 实验三:Bean属性赋值:引用类型自动装配(DI)

这里要重点区别用注解来实现引用类型自动装配(DI)和XML文件形式来实现的方式。首先,我们可以通过一个案例分别用上述两种方法分别来实现对比学习:

案例:XxxController 需要 XxxService

方法一:通过xml配置文件形式来实现:

第一步:首先要找到,两个组件类所在的位置:

第二部:因为此时是XxxController要调用XxxService对象,因此此时要在XxxController类中创建一个XxxService类型的属性,并且要写出set构造方法:实现图如下:

第三部:编写xml配置文件:

第四步:使用组件

在具体的实现场景中创建IoC容器对象,然后调用里面的组件。

方法二:使用注解方式来实现

使用注解方式非常简单,只需要在对应的属性上面添加注解@Autowired,有这个注解就不需要set方法。

就拿案例:XxxController 需要 XxxService来说:

第一:先把XxxController和XxxService通过注解添加到容器中。

第二步:在XxxController组件类中,创建XxxService属性,不过这里只需要在属性类上面添加注解@Autowired,不需要构建对应的set方法。

第三:告诉springioc容器,都在那些包下添加了ioc注解(这个是写在xml配制文件中)

首先,我们要先查看配置文件所在的文件包

xml配置:

注解配置的工作流程

     通过上述流程图可以看出,如果是通过注解的形式进行自动装配(DI),首先会在容器中找到类型为@Autowired标记的类型,比如在上述例子中在 private XxxService xxxService;属性上面添加了注解@Autowired,那么会在容器中找类型为XxxService的组件对象,如果找到了并且这个组件对象唯一,就执行装配,如果没有找到就抛出异常,但是,如果找到了对应类型的组件对象,但是组件对象不唯一的话,应该需要根据bean的id查找,但是因为这里是注解进行查找,并没有在xml文件中进行的配置,但是可以通过其他方法进行相同功能的实现。方法如下图所示:

注意:在上述的案例中,因为我们的属性是一个组件类,在一个组件类上定义注解@Autowired的时候,肯定在容器中只有一个对应的类型,但是如果我们注解@Autowired是一个接口的时候,如果这个接口对应的实现类不止一个,此时按照类型查找就会报错比如,比如说,我们此时有一个接口UserService,实现类有UserServiceImp和NewUserServiceImp两个实现类,此时如果把注解@Autowired放到接口UserService上面,此时会报错,显示有两个UserService对象,不知道对应哪一个,具体是实现过程如下:

1.定义接口UserService

2.定义两个实现类UserServiceImp和NewUserServiceImp

3.在UserController里面调用UserService里的方法

注意:此时的@Autowired是写在接口上面的

4.在测试类里面进行测试:

测试结果:

通过结果看以看到,此时接口UserService有两个实现类,此时不知道应该调用哪一个实现类。

解决方法1:修改UserController类中,接口UserService类型属性的名称。因为在所需类型匹配的 bean 不止一个,并且没有 @Qualifier 注解此时是根据 @Autowired 标记位置成员变量的变量名作为 bean 的 id 进行匹配,因此,此时把变量名改成实现类的名称就可以(注意:这里变量名首字母要小写)

1.先把变量名改成UserServiceImp

在测试类进行测试:

测试结果:

此时。调用的是UserServiceImp实现类中out方法。

2.把变量名改成NewUserServiceImp

(注意:这里应该是newUserServiceImp,因为在把组件放入容器中时,默认是首字母小写)

在测试类中进行测试:

测试结果:

解决方法2:添加 @Qualifier 注解。原因是在所需类型匹配的 bean 不止一个时,使用 @Qualifier 注解:根据 @Qualifier 注解中指定的名称作为 bean 的id进行匹配。

在测试类中进行测试:

测试结果:

这里时注解Autowired与 qualifier配置使用,其实也可以用注解resource(name="")来替代,其中注意两者区别:

- @Autowired注解是Spring框架自己的。 - **@Resource注解默认根据Bean名称装配,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型装配。** - **@Autowired注解默认根据类型装配,如果想根据名称装配,需要配合@Qualifier注解一起用。** - @Resource注解用在属性上、setter方法上。 - @Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上。

@Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖:【**高于JDK11或低于JDK8需要引入以下依赖**】

```XML     jakarta.annotation     jakarta.annotation-api     2.1.1 ```

注意:注解@Autowired除了写在属性上面,还能写在构造方法和setXxx方法上

1.写在构造方法上:此时会自动在容器中查找SoldierService类型的组件,传递给自己的属性SoldierService。

@Controller(value = "tianDog") public class SoldierController {          private SoldierService soldierService;          @Autowired     public SoldierController(SoldierService soldierService) {         this.soldierService = soldierService;     }     ……

2.写在setXxx方法上,此时会自动在容器中找到SoliderService类型的组件,并传递给自己的属性soliderService。

@Controller(value = "tianDog") public class SoldierController {

    private SoldierService soldierService;

    @Autowired     public void setSoldierService(SoldierService soldierService) {         this.soldierService = soldierService;     }     ……

6.4 @Value注解的使用

在跟一个常量属性进行赋值的时候,有两种方法,一种是直接赋值,一种是使用注解@Value属性进行赋值。具体例子如下图

从这里看到,跟常量进行注解赋值不如注解赋值方法,因此,使用@Value主要作用是读取外部配置。案例:现在外部有一个配置文件,其里面有mysql的账号和密码,现在需要把账号密码传递给JavaBean中所对应的属性。具体操作如下:

1.创建配置文件:

2.把要用到到组件以及外部文件全部添加到容器中

3.在对应的组件类中使用@Value注解来读取外部文件

编写测试类:

运行结果:

注意:在这里还可以通过注解@Value进行默认值编写,比如说在上面那个例子,如果外部配置文件jdbc.username没有指定值,此时就按照默认值为准,格式是@Value("${jdbc.username:张三}:默认值")。

总结:

 注解+XML IoC方式问题总结     1. 自定义类可以使用注解方式,但是第三方依赖的类依然使用XML方式!(因为第三方插件都是放在jar包中,但不能打开jar包在类上写注解,因此,第三方插件需要用xml文件配置)。     2. XML格式解析效率低!

7.基于配置类方式管理Bean

这里首先要知道在  6.基于注解的方式管理Bean(组件)中,IoC(控制反转)和DI(组件依赖注入)能够通过注解来实现,但是还需要配置xml文件,此时配置xml配置文件主要是完成3个操作:

1.定义扫描组件的范围。

2.把外部配置文件配置到容器中。

3.把第三方类配置到容器中。(第三方类放在jar包中,而jar包是只读模式,所以不能用注解标注)

因此,此时我们还没有完全脱离xml配置文件,这里用配置类的目的就是完全一点都不适用配置类。相对于 XML 配置,完全注解配置具有更强的类型安全性和更好的可读性。

这里构建配置类要求以及注意事项:

1.在配置类的上方需要添加注解@Configuration(使用 @Configuration 注解将一个普通的类标记为 Spring 的配置类。)

用注解@ComponentScan(basePackages = {""})来替代配置类中标签

2.@PropertySource("classpath:配置文件地址") 替代

(将外部文件放入容器中)

3.用@Bean将第三方jar包类,放入到容器中。

这个首先用一个例子来说明:

案例:将Druid连接池对象存储到IoC容器。

如果用xml文件配置,配置形式如下图:

如果我们用配置类来实现,首先我们要创建一个方法,这个方法的返回类型必须是DruidDataSource或者其父类或实现的接口,方法名等同于xml配置的id值。然后再方法上写上注释@Bean。@Bean 注释用于指示方法实例化、配置和初始化要由 Spring IoC 容器管理的新对象。(其作用就是相当于在xml文件中的一个),然后在方法体中进行正常的java创建对象赋值等操作。

//标注当前类是配置类,替代application.xml     @Configuration //引入jdbc.properties文件 @PropertySource({"classpath:application.properties","classpath:jdbc.properties"}) @ComponentScan(basePackages = {"com.atguigu.components"}) public class MyConfiguration {

    //如果第三方类进行IoC管理,无法直接使用@Component相关注解     //解决方案: xml方式可以使用

注意点:在一个@Bean方法中需要调用到另外一个@Bean方法对象:

方法1:在一个 @Bean 方法中直接调用其他 @Bean 方法来获取 Bean 实例,虽然是方法调用,也是通过IoC容器获取对应的Bean。

例子:

@Configuration public class JavaConfig {

    @Bean     public HappyMachine happyMachine(){         return new HappyMachine();     }

    @Bean     public HappyComponent happyComponent(){         HappyComponent happyComponent = new HappyComponent();         //直接调用方法即可!          happyComponent.setHappyMachine(happyMachine());         return happyComponent;     }

}

 方案2:

参数引用法:通过方法参数传递 Bean 实例的引用来解决 Bean 实例之间的依赖关系,例如:

package com.atguigu.config;

import com.atguigu.ioc.HappyComponent; import com.atguigu.ioc.HappyMachine; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;

/**  * projectName: com.atguigu.config  * description: 配置HappyComponent和HappyMachine关系  */

@Configuration public class JavaConfig {

    @Bean     public HappyMachine happyMachine(){         return new HappyMachine();     }

    /**      * 可以直接在形参列表接收IoC容器中的Bean!      *    情况1: 直接指定类型即可      *    情况2: 如果有多个bean,(HappyMachine 名称 ) 形参名称等于要指定的bean名称!      *           例如:      *               @Bean      *               public Foo foo1(){      *                   return new Foo();      *               }      *               @Bean      *               public Foo foo2(){      *                   return new Foo()      *               }      *               @Bean      *               public Component component(Foo foo1 / foo2 通过此处指定引入的bean)      */     @Bean     public HappyComponent happyComponent(HappyMachine happyMachine){         HappyComponent happyComponent = new HappyComponent();         //赋值         happyComponent.setHappyMachine(happyMachine);         return happyComponent;     }

}

注意点:指定@Bean名称

@Configuration public class AppConfig {

  @Bean("myThing") //指定名称   public Thing thing() {     return new Thing();   } }

7.1 @Import注解

@Import 注释允许从另一个配置类加载 @Bean 定义,这个注解最大作用在于,当项目中有配置类A,B时,不用全部记住这两个配置类,只需要在A中导入配置类B,这样在读取配置类的时候,只需要传入配置类A就能一同配置B传入。

public static void main(String[] args) {   ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

  // now both beans A and B will be available...   A a = ctx.getBean(A.class);   B b = ctx.getBean(B.class); }

     注解+配置类 IoC方式总结     1. 完全摒弃了XML配置文件     2. 自定义类使用IoC和DI注解标记     3. 第三方类使用配置类声明方法+@Bean方式处理     4. 完全注解方式(配置类+注解)是现在主流配置方式 

8.AOP面向切面编程

想知道AOP是什么东西,就要先知道代理的概念。

代理产生的背景:在实际的业务开发中,往往需要写大量重复的非核心业务代码,效率太低。

代理概念:将非核心逻辑剥离出来,封装这些非核心逻辑的类,对象,方法(中介)。

代理实现的方式:静态代理和动态代理。

在学习代理之前,要知道几个词的含义:

目标类:就是写核心逻辑代码的类。

代理类:就是封装非核心,重复业务的类。

代理实现方式一:静态代理

静态代理的实现逻辑是,在容器中放置的是代理类,代理类会实现非核心业务,然后调用目标类中的方法实现核心业务(目标类会返回对应的结果值)。

案例:原本有一个业务类B,其功能就是实现两个常数相加,减。现在需要添加一个功能,在进行运算前,要输出字段:开始运算。

原本的业务类B:

添加功能后的业务B:

静态代理就是将这个功能单独提取成一个代理类(创建代理类C)

此时的核心类B就可以只写核心业务。

注意:最后在容器中存放的是代理类对象。

其实现过程就是,代理类实现其本身的功能,并调用目标类的方法来实现核心代码。

缺点:静态代理虽然实现了解耦的目标,但是代码都是写死的,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。

提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。(一句话来说,静态代理就是往往需要多个代理类,而动态代理只需要写一个代理类就行,系统根据配置情况,自动进行调用对应的方法)

动态代理:

动态代理分为两种:

1.JDK动态代理:JDK原生的实现方式,需要被代理的目标类必须实现接口!他会根据目标类的接口动态生成一个代理对象!代理对象和目标对象有相同的接口!(拜把子)

2.cglib:通过继承被代理的目标类实现代理,所以不需要目标类实现接口!(认干爹)

下面举例使用JDK动态代理实现:

1.生成代理对象

public class ProxyFactory {

    private Object target;

    public ProxyFactory(Object target) {         this.target = target;     }

    public Object getProxy(){

        /**          * newProxyInstance():创建一个代理实例          * 其中有三个参数:          * 1、classLoader:加载动态生成的代理类的类加载器          * 2、interfaces:目标对象实现的所有接口的class对象所组成的数组          * 3、invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接口中的抽象方法          */         ClassLoader classLoader = target.getClass().getClassLoader();         Class[] interfaces = target.getClass().getInterfaces();         InvocationHandler invocationHandler = new InvocationHandler() {             @Override             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                 /**                  * proxy:代理对象                  * method:代理对象需要实现的方法,即其中需要重写的方法                  * args:method所对应方法的参数                  */                 Object result = null;                 try {                     System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args));                     result = method.invoke(target, args);                     System.out.println("[动态代理][日志] "+method.getName()+",结果:"+ result);                 } catch (Exception e) {                     e.printStackTrace();                     System.out.println("[动态代理][日志] "+method.getName()+",异常:"+e.getMessage());                 } finally {                     System.out.println("[动态代理][日志] "+method.getName()+",方法执行完毕");                 }                 return result;             }         };

        return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);     } }

测试:

@Test public void testDynamicProxy(){     ProxyFactory factory = new ProxyFactory(new CalculatorLogImpl());     Calculator proxy = (Calculator) factory.getProxy();     proxy.div(1,0);     //proxy.div(1,1); } 

上面是动态代理过程,看不懂也没用问题,在实际开发中,我们会使用SpringAOP框架(框架就是jar包+配置文件)来简化动态代理的过程。

AOP是面向切面编程,其本身是对面向对象编程的一种补充,因为,面向对象编程能够通过继承从而从上到下垂直编程,但是不能够进行横向扩展,而面向切面编程刚好就弥补了这一个缺陷,就是说,面向切面编程就是对面向对象编程的一种补充和扩展。(AOP可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。使用AOP,可以在不修改原来代码的基础上添加新功能。) 

9.Spring声明式事务

编程式事务:

最早的事务都是编程式事务,这种编程式事务是指手动编写程序来管理事务,即通过编写代码的方式直接控制事务的提交和回滚。在 Java 中,通常使用事务管理器(如 Spring 中的 `PlatformTransactionManager`)来实现编程式事务。编程式事务的主要优点是灵活性高,可以按照自己的需求来控制事务的粒度、模式等等。但是,编写大量的事务控制代码容易出现问题,对代码的可读性和可维护性有一定影响。

 声明式事务:

声明式事务是指使用注解或 XML 配置的方式来控制事务的提交和回滚。

开发者只需要添加配置即可, 具体事务的实现由第三方框架实现,避免我们直接进行事务操作!

使用声明式事务可以将事务的控制和业务逻辑分离开来,提高代码的可读性和可维护性。

区别:

- 编程式事务需要手动编写代码来管理事务。 - 而声明式事务可以通过配置文件或注解来控制事务。

Spring事务管理器:

事务管理器等于说只是接口,里面存放的是方法,然后具体的方法实现由其他对应的类实现,这样就能满足不同事务操作的代码不同的问题。

如果是用配置类的方法一定要在类中创建事务管理实现对象。并且要在配置类上添加注解@EnableTransactionManagement。

声明事务注解:@Transactional这个注解可以写在方法上,也能够写在类上,写在类上表示 这个类中每一个方法上都使用了 @Transactional 注解。类级别标记的 @Transactional 注解中设置的事务属性也会延续影响到方法执行时的事务属性(事务属性可以在注解Transactional()内添加,比如@Transactional(readOnly = true)表示只读属性)。除非在方法上又设置了 @Transactional 注解。

1. 事务隔离级别

    数据库事务的隔离级别是指在多个事务并发执行时,数据库系统为了保证数据一致性所遵循的规定。常见的隔离级别包括:

    1. 读未提交(Read Uncommitted):事务可以读取未被提交的数据,容易产生脏读、不可重复读和幻读等问题。实现简单但不太安全,一般不用。     2. 读已提交(Read Committed):事务只能读取已经提交的数据,可以避免脏读问题,但可能引发不可重复读和幻读。     3. 可重复读(Repeatable Read):在一个事务中,相同的查询将返回相同的结果集,不管其他事务对数据做了什么修改。可以避免脏读和不可重复读,但仍有幻读的问题。     4. 串行化(Serializable):最高的隔离级别,完全禁止了并发,只允许一个事务执行完毕之后才能执行另一个事务。可以避免以上所有问题,但效率较低,不适用于高并发场景。

    不同的隔离级别适用于不同的场景,需要根据实际业务需求进行选择和调整。

package com.atguigu.service;

import com.atguigu.dao.StudentDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional;

import java.io.FileInputStream; import java.io.FileNotFoundException;

/**  * projectName: com.atguigu.service  */ @Service public class StudentService {

    @Autowired     private StudentDao studentDao;

    /**      * timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间!      * rollbackFor = 指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚!      * noRollbackFor = 指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内!      * isolation = 设置事务的隔离级别,mysql默认是repeatable read!      */     @Transactional(readOnly = false,                    timeout = 3,                    rollbackFor = Exception.class,                    noRollbackFor = FileNotFoundException.class,                    isolation = Isolation.REPEATABLE_READ)     public void changeInfo() throws FileNotFoundException {         studentDao.updateAgeById(100,1);         //主动抛出一个检查异常,测试! 发现不会回滚,因为不在rollbackFor的默认范围内!         new FileInputStream("xxxx");         studentDao.updateNameById("test1",1);     }  

事务传播行为:

propagation 属性的可选值由 org.springframework.transaction.annotation.Propagation 枚举类提供:

名称含义REQUIRED 默认值如果父方法有事务,就加入,如果没有就新建自己独立!REQUIRES_NEW不管父方法是否有事务,我都新建事务,都是独立的!注意:判断父方法是否有事务的依据是看父方法是否被注解@Transactional修饰。

10.Mybatis学习

MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github。

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

为了更好了解Mybatis运行原理,先了解iBatis的运行原理,首先也知道iBatis是Mybatis的前身,iBatis3.x正式更名为MyBatis。因此可以说Mybatis是对iBatis的补充和完善。

Mybatis运行原理: 

MyBatis 框架下,SQL语句编写位置发生改变,从原来的Java类,改成**XML**或者注解定义!

推荐在XML文件中编写SQL语句,让用户能更专注于 SQL 代码,不用关注其他的JDBC代码。

如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码!!

一般编写SQL语句的文件命名:XxxMapper.xml  Xxx一般取表名!!

Mybatis 中的 Mapper 接口相当于以前的 Dao。但是区别在于,Mapper 仅仅只是建接口即可,我们不需要提供实现类,具体的SQL写到对应的Mapper文件,该用法的思路如下图所示:

 

运行和测试;

/**  * projectName: com.atguigu.test  *  * description: 测试类  */ public class MyBatisTest {

    @Test     public void testSelectEmployee() throws IOException {

        // 1.创建SqlSessionFactory对象         // ①声明Mybatis全局配置文件的路径         String mybatisConfigFilePath = "mybatis-config.xml";

        // ②以输入流的形式加载Mybatis配置文件         InputStream inputStream = Resources.getResourceAsStream(mybatisConfigFilePath);

        // ③基于读取Mybatis配置文件的输入流创建SqlSessionFactory对象         SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        // 2.使用SqlSessionFactory对象开启一个会话         SqlSession session = sessionFactory.openSession();

        // 3.根据EmployeeMapper接口的Class对象获取Mapper接口类型的对象(动态代理技术)         EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);

        // 4. 调用代理类方法既可以触发对应的SQL语句         Employee employee = employeeMapper.selectEmployee(1);

        System.out.println("employee = " + employee);

        // 4.关闭SqlSession         session.commit(); //提交事务 [DQL不需要,其他需要]         session.close(); //关闭会话

    } }

10.1 #{}与${}作用和区别:

10.1.1 #{}作用

10.1.2 ${}作用 

结论:实际开发中,能用#{}实现的,肯定不用${}。

特殊情况: 动态的不是值,是列名或者关键字,需要使用${}拼接。

10.2 mybatis日志配置 

mybatis配置文件设计标签和顶层结构如下:

我们可以在mybatis的配置文件使用**settings标签**设置,输出运过程SQL日志!

通过查看日志,我们可以判定#{} 和 ${}的输出效果!

settings设置项:

日志配置:

     

Mybatis总体机制概括:

10.3 单个简单参数

Mapper接口中抽象方法的声明

 Employee selectEmployee(Integer empId);

SQL语句

 

单个简单类型参数,在#{}中可以随意命名,但是没有必要。通常还是使用和接口方法参数同名。 

10.4 实体类型参数

Mapper接口中抽象方法的声明

int insertEmployee(Employee employee);

SQL语句

  insert into t_emp(emp_name,emp_salary) values(#{empName},#{empSalary})

结论

Mybatis会根据#{}中传入的数据,加工成getXxx()方法,通过反射在实体类对象中调用这个方法,从而获取到对应的数据。填充到#{}解析后的问号占位符这个位置。

10.5 零散的多个简单类型数据

零散的多个简单类型参数,如果没有特殊处理,那么Mybatis无法识别自定义名称:

Mapper接口中抽象方法的声明:

int updateEmployee(@Param("empId") Integer empId,@Param("empSalary") Double empSalary); 

SQL语句

  update t_emp set emp_salary=#{empSalary} where emp_id=#{empId}  

10.6 map数据类型

接口中的方法

映射文件

#{}里面的值对应接口Map中的key 

使用场景

有很多零散的参数需要传递,但是没有对应的实体类类型可以使用。使用@Param注解一个一个传入又太麻烦了。所以都封装到Map中。

10.7 数据输出

数据输出分为两种:

第一种:增删改:增删改操作返回的受影响行数,直接使用 int 或 long 类型接收即可。

第二种:查询操作的查询结果。

10.7.1返回单个数值简单类型

这里的返回值类型有好几种,一种是写类型的全名,一种是写别名,这里的别名可以写简称

select标签,通过resultType指定查询返回值类型!

resultType = "全限定符 | 别名 | 如果是返回集合类型,写范型类型即可"

如果是自己自定义的类型,可以通过来自定义一个映射配置文件。

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:

 注意:上面这个代码是写在mybatis配置文件中,不是写在映射文件中。

标签里面还有一种写法

每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子

@Alias("author") public class Author {     ... }

 10.7.2 返回实体类对象

Mapper接口抽象方法

Employee selectEmployee(Integer empId);  

映射文件

 这里也可以不用设置别名的形式(这种形式操作太麻烦)

增加全局配置自动识别对应关系,在 Mybatis 全局配置文件中,做了下面的配置,select语句中可以不给字段设置别名。

           

10.7.3 返回map类型

这个map返回类型,使用范围就是查询结果最后放在一起不属于任何一个实体类类型,此时就用map类型。

//查询薪水和员工名字

Map selectEmpNameAndMaxSalary();

注意:此时已经将mapUnderscoreToCamelCase属性配置为true,表示开启自动映射驼峰式命名规则。

此时map的key就是emp_name,emp_salary,value就是数据库对应的属性值

10.7.4 返回集合类型

使用场景:查询结果返回多个实体类对象,希望把多个实体类对象放在List集合中返回。此时不需要任何特殊处理,在resultType属性中还是设置实体类类型即可。

List selectAll();

映射文件

 

10.7.5 返回自增长类型主键 

int insertEmployee(Employee employee);

 映射文件

keyProperty="empId">   insert into t_emp(emp_name,emp_salary)   values(#{empName},#{empSalary})  

把自增长的主键列的值emp_id赋值给属性列empId

@Test public void testSaveEmp() {   EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);   Employee employee = new Employee();   employee.setEmpName("john");   employee.setEmpSalary(666.66);   employeeMapper.insertEmployee(employee);   log.info("employee.getEmpId() = " + employee.getEmpId()); } 

 注意:

Mybatis是将自增主键的值设置到实体类对象中,而不是以Mapper接口方法返回值的形式返回。

10.7.6 返回非自增长类型主键 

而对于不支持自增型主键的数据库(例如 Oracle)或者字符串类型主键,则可以使用 selectKey 子元素:selectKey 元素将会首先运行,id 会被设置,然后插入语句会被调用!

使用 selectKey 帮助插入UUID作为字符串类型主键示例:

            SELECT UUID() as id         INSERT INTO user (id, username, password)      VALUES (         #{id},         #{username},         #{password}     )  

在上例中,我们定义了一个 `insertUser` 的插入语句来将 `User` 对象插入到 `user` 表中。我们使用 `selectKey` 来查询 UUID 并设置到 `id` 字段中。

通过 `keyProperty` 属性来指定查询到的 UUID 赋值给对象中的 `id` 属性,而 `resultType` 属性指定了 UUID 的类型为 `java.lang.String`。

需要注意的是,我们将 `selectKey` 放在了插入语句的前面,这是因为 MySQL 在 `insert` 语句中只支持一个 `select` 子句,而 `selectKey` 中查询 UUID 的语句就是一个 `select` 子句,因此我们需要将其放在前面。

最后,在将 `User` 对象插入到 `user` 表中时,我们直接使用对象中的 `id` 属性来插入主键值。

使用这种方式,我们可以方便地插入 UUID 作为字符串类型主键。当然,还有其他插入方式可以使用,如使用Java代码生成UUID并在类中显式设置值等。需要根据具体应用场景和需求选择合适的插入方式。

 10.7.7 实体类属性与数据库字段对应关系

1.别名对应

将字段的别名设置成和实体类属性一致。

 关于实体类属性的约定: getXxx()方法、setXxx()方法把方法名中的get或set去掉,首字母小写。

2. 全局配置自动识别驼峰式命名规则

    在Mybatis全局配置文件加入如下配置:

   

使用这种全局配置时候,不需要在sql语句中设置别名

3.使用resultMap 

使用resultMap标签定义对应关系,再在后面的SQL语句中引用这个对应关系

     

   

 

10.8 mybatis多表映射 

实体类设计方案:

多表关系回顾:(双向查看)

- 一对一

    夫妻关系,人和身份证号 - 一对多| 多对一

    用户和用户的订单,锁和钥匙 - 多对多

    老师和学生,部门和员工

实体类设计关系(查询):(单向查看)(关系无非就是两种,一种是对一。一种是对多)

实体类设计:对一关系下,类中只要包含单个对方对象类型属性即可!

例如:

public class Customer {

  private Integer customerId;   private String customerName;

}

public class Order {

  private Integer orderId;   private String orderName;   private Customer customer;// 体现的是对一的关系(站在订单的角度来看,一个订单对应一个客户,因此在Order中定义Customer类型的对象)

}    

对多: 用户对应的订单,讲师对应的学生或者学生对应的讲师都是对多关系:

实体类设计:对多关系下,类中只要包含对方类型集合属性即可!

public class Customer {

  private Integer customerId;   private String customerName;   private List orderList;// 体现的是对多的关系(因为一个客户对应多个订单,因此这里的泛型类型就是Order) }

public class Order {

  private Integer orderId;   private String orderName;   private Customer customer;// 体现的是对一的关系    }

//查询客户和客户对应的订单集合  不要管!

多表结果实体类设计小技巧:

  对一:属性中包含对方对象

  对多:属性中包含对方对象集合

  只有真实发生多表查询时,才需要设计和修改实体类,否则不提前设计和修改实体类!

  无论多少张表联查,实体类设计都是两两考虑!

  在查询映射的时候,只需要关注本次查询相关的属性!例如:查询订单和对应的客户,就不要关注客户中的订单集合!

10.9 多表映射案例

数据库

CREATE TABLE `t_customer` (`customer_id` INT NOT NULL AUTO_INCREMENT, `customer_name` CHAR(100), PRIMARY KEY (`customer_id`) );

CREATE TABLE `t_order` ( `order_id` INT NOT NULL AUTO_INCREMENT, `order_name` CHAR(100), `customer_id` INT, PRIMARY KEY (`order_id`) ); 

INSERT INTO `t_customer` (`customer_name`) VALUES ('c01');

INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o1', '1'); INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o2', '1'); INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o3', '1'); 

实际开发时,一般在开发过程中,不给数据库表设置外键约束。 原因是避免调试不方便。 一般是功能开发完成,再加外键约束检查是否有bug。

实体类设计:

@Data public class Customer {

  private Integer customerId;   private String customerName;   private List orderList;// 体现的是对多的关系    }  

@Data public class Order {   private Integer orderId;   private String orderName;   private Customer customer;// 体现的是对一的关系    }    

 对一映射:

需求:根据ID查询订单,以及订单关联的用户的信息!

OrderMapper接口

public interface OrderMapper {   Order selectOrderWithCustomer(Integer orderId); }

 OrderMapper.xml配置文件

   

 

       

           

 

在“对一”关联关系中,我们的配置比较多,但是关键词就只有:association和javaType 

对多映射: 

需求:

查询客户和客户关联的订单信息!

CustomerMapper接口

public interface CustomerMapper {

  Customer selectCustomerWithOrderList(Integer customerId);

}

CustomerMapper.xml文件

  type="customer">

   

 

       

       

   

 

在“对多”关联关系中,同样有很多配置,但是提炼出来最关键的就是:“collection”和“ofType” 

10.10 多表映射优化

setting属性属性含义可选值默认值autoMappingBehavior指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。NONE, PARTIAL, FULLPARTIAL

 我们可以将autoMappingBehavior设置为full,进行多表resultMap映射的时候,可以省略符合列和属性命名映射规则(前提列名=属性名,或者开启驼峰映射也可以自定映射)的result标签!

修改mybati-sconfig.xml:

修改teacherMapper.xml

                         

总结:

关联关系配置项关键词所在配置文件和具体位置对一association标签/javaType属性/property属性Mapper配置文件中的resultMap标签内对多collection标签/ofType属性/property属性Mapper配置文件中的resultMap标签内

10.11 mybatis动态语句

if和where标签

使用动态 SQL 最常见情景是根据条件包含 where / if 子句的一部分。比如:

在上面的例子中,如果不用标签,只用where的情况下,在有些判断条件下会出现问题。

    select emp_id,emp_name,emp_age,emp_salary,emp_gender     from t_emp                                                  emp_name=#{empName} and                             emp_salary>#{empSalary} and                             emp_age=#{empAge} or                             emp_gender=#{empGender}            

 choose/when/otherwise标签

在多个分支条件中,仅执行一个。

- 从上到下依次执行条件判断 - 遇到的第一个满足条件的分支会被采纳 - 被采纳分支后面的分支都将不被考虑 - 如果所有的when分支都不满足,那么就执行otherwise分支

foreach标签 

用批量插入举例

        (#{emp.empName},#{myIndex},#{emp.empSalary},#{emp.empGender})

**批量更新时需要注意**

上面批量插入的例子本质上是一条SQL语句,而实现批量更新则需要多条SQL语句拼起来,用分号分开。也就是一次性发送多条SQL语句让数据库执行。此时需要在数据库连接信息的URL地址中设置:

atguigu.dev.url=jdbc:mysql:///mybatis-example?allowMultiQueries=true

对应的foreach标签如下:

            update t_emp set emp_name=#{emp.empName} where emp_id=#{emp.empId}      

10.12 sql片段

抽取重复的SQL片段

    select emp_id,emp_name,emp_age,emp_salary,emp_gender from t_emp

 抽取重复的SQL片段

10.13 Mybatis高级扩展 

批量映射优化:根据之前学到的知识可知,在每次创建一个映射文件之后,都需要在Mybatis总配置文件中写下映射文件的位置,必须说

这样写太麻烦,因此,我们可以之际把映射文件所在的包的路径写在mybatis总配置文件中,这样其包下所有的映射文件都会被扫描到。前提是在resources下创建的映射文件包路径跟对应的Mapper接口的包路径是一样的。

   

此时这个包下的所有 Mapper 配置文件将被自动加载、注册,比较方便。 

注意:在resources下创建包时,如果包需要分级时,应该使用/符号而不是传统的.符号,虽然看起来无差别,但是/创建的包路径在编译后是分级别的,但是.创建的包不是分级别的。

10.14 分页插件PageHelper

MyBatis 对插件进行了标准化的设计,并提供了一套可扩展的插件机制。插件可以在用于语句执行过程中进行拦截,并允许通过自定义处理程序来拦截和修改 SQL 语句、映射语句的结果等。

具体来说,MyBatis 的插件机制包括以下三个组件:

1. `Interceptor`(拦截器):定义一个拦截方法 `intercept`,该方法在执行 SQL 语句、执行查询、查询结果的映射时会被调用。 2. `Invocation`(调用):实际上是对被拦截的方法的封装,封装了 `Object target`、`Method method` 和 `Object[] args` 这三个字段。 3. `InterceptorChain`(拦截器链):对所有的拦截器进行管理,包括将所有的 Interceptor 链接成一条链,并在执行 SQL 语句时按顺序调用。

插件的开发非常简单,只需要实现 Interceptor 接口,并使用注解 `@Intercepts` 来标注需要拦截的对象和方法,然后在 MyBatis 的配置文件中添加插件即可。

PageHelper插件的使用:

pom.xml引入依赖

    com.github.pagehelper     pagehelper     5.1.11  

mybatis-config.xml配置分页插件

在 MyBatis 的配置文件中添加 PageHelper 的插件

                 

其中,com.github.pagehelper.PageInterceptor 是 PageHelper 插件的名称,dialect 属性用于指定数据库类型(支持多种数据库)

@Test public void testTeacherRelationshipToMulti() {

    TeacherMapper teacherMapper = session.getMapper(TeacherMapper.class);

    PageHelper.startPage(1,2);     // 查询Customer对象同时将关联的Order集合查询出来     List allTeachers = teacherMapper.findAllTeachers(); //     PageInfo pageInfo = new PageInfo<>(allTeachers);

    System.out.println("pageInfo = " + pageInfo);     long total = pageInfo.getTotal(); // 获取总记录数     System.out.println("total = " + total);     int pages = pageInfo.getPages();  // 获取总页数     System.out.println("pages = " + pages);     int pageNum = pageInfo.getPageNum(); // 获取当前页码     System.out.println("pageNum = " + pageNum);     int pageSize = pageInfo.getPageSize(); // 获取每页显示记录数     System.out.println("pageSize = " + pageSize);     List teachers = pageInfo.getList(); //获取查询页的数据集合     System.out.println("teachers = " + teachers);     teachers.forEach(System.out::println);

}

10.15 逆向工程和MybatisX插件

ORM思维介绍:

java是面向对象语言,数据库算是面向过程语言,在以往的操作中,我们借助中间的数据库驱动来用java的面向对象语言来对数据库进行操作。

ORM(Object-Relational Mapping,对象-关系映射)是一种将数据库和面向对象编程语言中的对象之间进行转换的技术。它将对象和关系数据库的概念进行映射,最后我们就可以通过方法调用进行数据库操作!! 最终: 让我们可以使用面向对象思维进行数据库操作!!!

ORM 框架通常有半自动和全自动两种方式。

- 半自动 ORM 通常需要程序员手动编写 SQL 语句或者配置文件,将实体类和数据表进行映射,还需要手动将查询的结果集转换成实体对象。 - 全自动 ORM 则是将实体类和数据表进行自动映射,使用 API 进行数据库操作时,ORM 框架会自动执行 SQL 语句并将查询结果转换成实体对象,程序员无需再手动编写 SQL 语句和转换代码。

**下面是半自动和全自动 ORM 框架的区别:**

1. 映射方式:半自动 ORM 框架需要程序员手动指定实体类和数据表之间的映射关系,通常使用 XML 文件或注解方式来指定;全自动 ORM 框架则可以自动进行实体类和数据表的映射,无需手动干预。 2. 查询方式:半自动 ORM 框架通常需要程序员手动编写 SQL 语句并将查询结果集转换成实体对象;全自动 ORM 框架可以自动组装 SQL 语句、执行查询操作,并将查询结果转换成实体对象。 3. 性能:由于半自动 ORM 框架需要手动编写 SQL 语句,因此程序员必须对 SQL 语句和数据库的底层知识有一定的了解,才能编写高效的 SQL 语句;而全自动 ORM 框架通过自动优化生成的 SQL 语句来提高性能,程序员无需进行优化。 4. 学习成本:半自动 ORM 框架需要程序员手动编写 SQL 语句和映射配置,要求程序员具备较高的数据库和 SQL 知识;全自动 ORM 框架可以自动生成 SQL 语句和映射配置,程序员无需了解过多的数据库和 SQL 知识。

常见的半自动 ORM 框架包括 MyBatis 等;常见的全自动 ORM 框架包括 Hibernate、Spring Data JPA、MyBatis-Plus 等。

**注意:逆向工程只能生成单表crud的操作,多表查询依然需要我们自己编写!**

11. SpringMvc学习

介绍:

Spring Web MVC是基于Servlet API构建的原始Web框架,从一开始就包含在Spring Framework中。正式名称“Spring Web MVC”来自其源模块的名称( spring-webmvc ),但它通常被称为“Spring MVC”。

主要作用:

SSM框架构建起单体项目的技术栈需求!其中的SpringMVC负责表述层(控制层)实现简化!

SpringMVC的作用主要覆盖的是表述层,例如:   - 请求映射   - 数据输入   - 视图界面   - 请求分发   - 表单回显   - 会话控制   - 过滤拦截   - 异步交互   - 文件上传   - 文件下载   - 数据校验   - 类型转换   - 等等等

最终总结:   1. 简化前端参数接收( 形参列表 )   2. 简化后端数据响应(返回值)   3. 以及其他......

11.1 核心组件和调用流程理解

Spring MVC与许多其他Web框架一样,是围绕前端控制器模式设计的,其中中央 `Servlet`  `DispatcherServlet` 做整体请求处理调度!

SpringMVC处理请求流程:(非常非常重要)

SpringMVC涉及组件理解:

1.DispatcherServlet : SpringMVC提供,我们需要使用web.xml配置(也可以使用配置类的形式使其生效)使其生效,它是整个流程处理的核心,所有请求都经过它的处理和分发![ CEO ]

2.HandlerMapping : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,它内部缓存handler(controller方法)和handler访问路径数据,被DispatcherServlet调用,用于查找路径对应的handler![秘书]

3.HandlerAdapter : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,它可以处理请求参数和处理响应数据数据,每次DispatcherServlet都是通过handlerAdapter间接调用handler,他是handler和DispatcherServlet之间的适配器![经理]

4.Handler : handler又称处理器,他是Controller类内部的方法简称,是由我们自己定义,用来接收参数,向后调用业务,最终返回响应结果![打工人]

5.ViewResovler : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效!视图解析器主要作用简化模版视图页面查找的,但是需要注意,前后端分离项目,后端只返回JSON数据,不返回页面,那就不需要视图解析器!所以,视图解析器,相对其他的组件不是必须的![财务]

过程详述:在接收到用户的请求的时候,DispatcherServlet(CEO)会首先知道,知道后会在HandlerMapping(秘书)中查找是否有对应的handler(就是对应路径下的方法),如果能查找到对应的方法,DispatcherServlet(CEO)会通知HandlerAadapter(部门经理),告诉HandlerAadapter(部门经理)用户的需求,参数信息,让HandlerAadapter(部门经理)去UserController(对应的方法类,类型根据实际情况定)中去调用对应的Handler(方法)来处理用户的需求,并拿到UserController(对应的方法类,类型根据实际情况定)处理的结果,把结果(这个结果被HandlerAadapter简化,其实HandlerAadapter传给对应HandlerAadapter的参数也已经简化过)返回给DispatcherServlet(CEO)

实践案例:

需求图:

配置分析:     1. DispatcherServlet,设置处理所有请求!     2. HandlerMapping,HandlerAdapter,Handler需要加入到IoC容器,供DS调用!     3. Handler自己声明(Controller)需要配置到HandlerMapping中供DS查找!

导入依赖:

    6.0.6     9.1.0     17     17     UTF-8

                org.springframework         spring-context         ${spring.version}    

                        jakarta.platform         jakarta.jakartaee-web-api         ${servlet.api}         provided    

                org.springframework         spring-webmvc         ${spring.version}    

Controller声明

@Controller public class HelloController {

    //handlers

    /**      * handler就是controller内部的具体方法      * @RequestMapping("/springmvc/hello") 就是用来向handlerMapping中注册的方法注解!(上面可以知道,在handlerMapping中存着对应的handler和对应的路径)      * @ResponseBody 代表向浏览器直接返回数据!      */     @RequestMapping("/springmvc/hello")     @ResponseBody     public String hello(){         System.out.println("HelloController.hello");         return "hello springmvc!!";     } }  

Spring MVC核心组件配置类 

//TODO: SpringMVC对应组件的配置类 [声明SpringMVC需要的组件信息]

//TODO: 导入handlerMapping和handlerAdapter的三种方式  //1.自动导入handlerMapping和handlerAdapter [推荐]  //2.可以不添加,springmvc会检查是否配置handlerMapping和handlerAdapter,没有配置默认加载  //3.使用@Bean方式配置handlerMapper和handlerAdapter @EnableWebMvc      @Configuration(表明这是一个配置类) @ComponentScan(basePackages = "com.atguigu.controller") //TODO: 进行controller扫 //WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法! 前期可以实现 public class SpringMvcConfig implements WebMvcConfigurer {

    @Bean(将第三方jar包类加载到ioc容器中)(handlerMappering秘书)     public HandlerMapping handlerMapping(){         return new RequestMappingHandlerMapping();     }

    @Bean(将第三方jar包类加载到ioc容器中)(HandlerAdapter 经理)     public HandlerAdapter handlerAdapter(){         return new RequestMappingHandlerAdapter();     }     }

SpringMVC环境搭建

对于使用基于 Java 的 Spring 配置的应用程序,建议这样做,如以下示例所示:

//TODO: SpringMVC提供的接口,是替代web.xml的方案,更方便实现完全注解方式ssm处理! //TODO: Springmvc框架会自动检查当前类的实现类,会自动加载 getRootConfigClasses / getServletConfigClasses 提供的配置类 //TODO: getServletMappings 返回的地址 设置DispatherServlet对应处理的地址 public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

  /**    * 指定service / mapper层的配置类    */   @Override   protected Class[] getRootConfigClasses() {     return null;   }

  /**    * 指定springmvc的配置类    * @return    */   @Override(下面这个类会加载mvc的配置类)   protected Class[] getServletConfigClasses() {     return new Class[] { SpringMvcConfig.class };   }

  /**   * 设置dispatcherServlet(CEO)的处理路径!    * 一般情况下为 / 代表处理所有请求!    */   @Override(下面这个类会返回CEO的处理路径)   protected String[] getServletMappings() {     return new String[] { "/" };   } }

启动测试: 

11.2 SpringMVC接受数据

11.2.1 访问路径设置

@RequestMapping注解的作用就是将请求的 URL 地址和处理请求的方式(handler方法)关联起来,建立映射关系。

SpringMVC 接收到指定的请求,就会来找到在映射关系中对应的方法来处理这个请求。

1.精准路径匹配

在@RequestMapping注解指定 URL 地址时,不使用任何通配符,按照请求地址进行精确匹配。

@Controller public class UserController {

    /**      * 精准设置访问地址 /user/login      */     @RequestMapping(value = {"/user/login"})     @ResponseBody(代表向浏览器直接返回数据!)     public String login(){         System.out.println("UserController.login");         return "login success!!";     }

    /**      * 精准设置访问地址 /user/register      */     @RequestMapping(value = {"/user/register"})     @ResponseBody     public String register(){         System.out.println("UserController.register");         return "register success!!";     }      }  

 2.模糊路径匹配

在@RequestMapping注解指定 URL 地址时,通过使用通配符,匹配多个类似的地址。

@Controller public class ProductController {

    /**      *  路径设置为 /product/*        *    /* 为单层任意字符串  /product/a  /product/aaa 可以访问此handler        *    /product/a/a 不可以      *  路径设置为 /product/**       *   /** 为任意层任意字符串  /product/a  /product/aaa 可以访问此handler        *   /product/a/a 也可以访问      */     @RequestMapping("/product/*")     @ResponseBody     public String show(){         System.out.println("ProductController.show");         return "product show!";     } }

单层匹配和多层匹配:  /*:只能匹配URL地址中的一层,如果想准确匹配两层,那么就写“/*/*”以此类推。   /**:可以匹配URL地址中的多层。 其中所谓的一层或多层是指一个URL地址字符串被“/”划分出来的各个层次 这个知识点虽然对于@RequestMapping注解来说实用性不大,但是将来配置拦截器的时候也遵循这个规则。 

3.类和方法级别区别 

`@RequestMapping` 注解可以用于类级别和方法级别,它们之间的区别如下:

1. 设置到类级别:`@RequestMapping` 注解可以设置在控制器类上,用于映射整个控制器的通用请求路径。这样,如果控制器中的多个方法都需要映射同一请求路径,就不需要在每个方法上都添加映射路径。 2. 设置到方法级别:`@RequestMapping` 注解也可以单独设置在控制器方法上,用于更细粒度地映射请求路径和处理方法。当多个方法处理同一个路径的不同操作时,可以使用方法级别的 `@RequestMapping` 注解进行更精细的映射。

//1.标记到handler方法 @RequestMapping("/user/login") @RequestMapping("/user/register") @RequestMapping("/user/logout")

//2.优化标记类+handler方法//类上 @RequestMapping("/user")//handler方法上 @RequestMapping("/login") @RequestMapping("/register") @RequestMapping("/logout")

4.附带请求方式限制 

HTTP 协议定义了八种请求方式,在 SpringMVC 中封装到了下面这个枚举类:

public enum RequestMethod {   GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE }

默认情况下:@RequestMapping("/logout") 任何请求方式都可以访问!

如果需要特定指定:

@Controller public class UserController {

    /**      * 精准设置访问地址 /user/login      * method = RequestMethod.POST 可以指定单个或者多个请求方式!      * 注意:违背请求方式会出现405异常!      */     @RequestMapping(value = {"/user/login"} , method = RequestMethod.POST)     @ResponseBody     public String login(){         System.out.println("UserController.login");         return "login success!!";     }

    /**      * 精准设置访问地址 /user/register      */     @RequestMapping(value = {"/user/register"},method = {RequestMethod.POST,RequestMethod.GET})     @ResponseBody     public String register(){         System.out.println("UserController.register");         return "register success!!";     }

}

注意:违背请求方式,会出现405异常!!!

5.进阶注解

还有 `@RequestMapping` 的 HTTP 方法特定快捷方式变体:

- `@GetMapping` - `@PostMapping` - `@PutMapping` - `@DeleteMapping` - `@PatchMapping` 

@RequestMapping(value="/login",method=RequestMethod.GET) || @GetMapping(value="/login")

注意:进阶注解只能添加到handler方法上,无法添加到类上!

6.常见配置问题

出现原因:多个 handler 方法映射了同一个地址,导致 SpringMVC 在接收到这个地址的请求时该找哪个 handler 方法处理。

There is already 'demo03MappingMethodHandler' bean method com.atguigu.mvc.handler.Demo03MappingMethodHandler#empGet() mapped.

11.2.2 接收参数 

1.param和json参数比较

在 HTTP 请求中,我们可以选择不同的参数类型,如 param 类型和 JSON 类型。下面对这两种参数类型进行区别和对比:

1. 参数编码:  

    param 类型的参数会被编码为 ASCII 码。例如,假设 `name=john doe`,则会被编码为 `name=john%20doe`。而 JSON 类型的参数会被编码为 UTF-8。 2. 参数顺序:  

    param 类型的参数没有顺序限制。但是,JSON 类型的参数是有序的。JSON 采用键值对的形式进行传递,其中键值对是有序排列的。 3. 数据类型:  

    param 类型的参数仅支持字符串类型、数值类型和布尔类型等简单数据类型。而 JSON 类型的参数则支持更复杂的数据类型,如数组、对象等。 4. 嵌套性:  

    param 类型的参数不支持嵌套。但是,JSON 类型的参数支持嵌套,可以传递更为复杂的数据结构。 5. 可读性:  

    param 类型的参数格式比 JSON 类型的参数更加简单、易读。但是,JSON 格式在传递嵌套数据结构时更加清晰易懂。

总的来说,param 类型的参数适用于单一的数据传递,而 JSON 类型的参数则更适用于更复杂的数据结构传递。根据具体的业务需求,需要选择合适的参数类型。在实际开发中,常见的做法是:在 GET 请求中采用 param 类型的参数,而在 POST 请求中采用 JSON 类型的参数传递。、

11.2.3 param参数接受

1.直接接值

客户端请求

handler接收参数

只要形参数名和类型与传递参数相同,即可自动接收!

@Controller @RequestMapping("param") public class ParamController {

    /**      * 前端请求: http://localhost:8080/param/value?name=xx&age=18      *      * 可以利用形参列表,直接接收前端传递的param参数!      *    要求: 参数名 = 形参名      *          类型相同      * 出现乱码正常,json接收具体解决!!      * @return 返回前端数据      */     @GetMapping(value="/value")     @ResponseBody     public String setupForm(String name,int age){         System.out.println("name = " + name + ", age = " + age);         return name + age;     } }

@RequestParam注解 

可以使用 @RequestParam 注释将 Servlet 请求参数(即查询参数或表单数据)绑定到控制器中的方法参数。

   @RequestParam`使用场景:(下面是需要使用@RequestParam的情况)   - 指定绑定的请求参数名   - 要求请求参数必须传递   - 为请求参数提供默认值

基本用法:

 /**  * 前端请求: http://localhost:8080/param/data?name=xx&stuAge=18  *   *  使用@RequestParam注解标记handler方法的形参  *  指定形参对应的请求参数@RequestParam(请求参数名称)  */ @GetMapping(value="/data") @ResponseBody public Object paramForm(@RequestParam("name") String name,                          @RequestParam("stuAge") int age){     System.out.println("name = " + name + ", age = " + age);     return name+age; }

默认情况下,使用此批注的方法参数是必需的,但您可以通过将 `@RequestParam` 批注的 `required` 标志设置为 `false`!

如果没有没有设置非必须,也没有传递参数会出现:

 将参数设置非必须,并且设置默认值:

@GetMapping(value="/data") @ResponseBody public Object paramForm(@RequestParam("name") String name,                          @RequestParam(value = "stuAge",required = false,defaultValue = "18") int age){     System.out.println("name = " + name + ", age = " + age);     return name+age; }  

特殊场景接值 

1 一名多值

多选框,提交的数据的时候一个key对应多个值,我们可以使用集合进行接收!

  /**    * 前端请求: http://localhost:8080/param/mul?hbs=吃&hbs=喝    *    *  一名多值,可以使用集合接收即可!但是需要使用@RequestParam注解指定    */   @GetMapping(value="/mul")   @ResponseBody   public Object mulForm(@RequestParam List hbs){       System.out.println("hbs = " + hbs);       return hbs;   }

2.实体接收

Spring MVC 是 Spring 框架提供的 Web 框架,它允许开发者使用实体对象来接收 HTTP 请求中的参数。通过这种方式,可以在方法内部直接使用对象的属性来访问请求参数,而不需要每个参数都写一遍。下面是一个使用实体对象接收参数的示例:

定义一个用于接收参数的实体类:

public class User {

  private String name;

  private int age = 18;

  // getter 和 setter 略 }

在控制器中,使用实体对象接收,示例代码如下:

@Controller @RequestMapping("param") public class ParamController {

    @RequestMapping(value = "/user", method = RequestMethod.POST)     @ResponseBody     public String addUser(User user) {         // 在这里可以使用 user 对象的属性来接收请求参数         System.out.println("user = " + user);         return "success";     } }

 在上述代码中,将请求参数name和age映射到实体类属性上!要求属性名必须等于参数名!否则无法映射!

11.2.4 路径接受参数

路径传递参数是一种在 URL 路径中传递参数的方式。在 RESTful 的 Web 应用程序中,经常使用路径传递参数来表示资源的唯一标识符或更复杂的表示方式。而 Spring MVC 框架提供了 `@PathVariable` 注解来处理路径传递参数。

`@PathVariable` 注解允许将 URL 中的占位符映射到控制器方法中的参数。

例如,如果我们想将 `/user/{id}` 路径下的 `{id}` 映射到控制器方法的一个参数中,则可以使用 `@PathVariable` 注解来实现。

下面是一个使用 `@PathVariable` 注解处理路径传递参数的示例:

 /**  * 动态路径设计: /user/{动态部分}/{动态部分}   动态部分使用{}包含即可! {}内部动态标识!  * 形参列表取值: @PathVariable Long id  如果形参名 = {动态标识} 自动赋值!  *              @PathVariable("动态标识") Long id  如果形参名 != {动态标识} 可以通过指定动态标识赋值! *  * 访问测试:  /param/user/1/root  -> id = 1  uname = root  */ @GetMapping("/user/{id}/{name}") @ResponseBody public String getUser(@PathVariable Long id,                        @PathVariable("name") String uname) {     System.out.println("id = " + id + ", uname = " + uname);     return "user_detail"; }

11.2.5 JSON参数接受

前端传递 JSON 数据时,Spring MVC 框架可以使用 @RequestBody 注解来将 JSON 数据转换为 Java 对象。@RequestBody 注解表示当前方法参数的值应该从请求体中获取,并且需要指定 value 属性来指示请求体应该映射到哪个参数上。其使用方式和示例代码如下:

1.前端发送 JSON 数据的示例:(使用postman测试)

{   "name": "张三",   "age": 18,   "gender": "男" }

2.定义一个用于接收 JSON 数据的 Java 类,例如: 

public class Person {   private String name;   private int age;   private String gender;   // getter 和 setter 略 }

3.在控制器中,使用 @RequestBody 注解来接收 JSON 数据,并将其转换为 Java 对象,例如:

@PostMapping("/person") @ResponseBody public String addPerson(@RequestBody Person person) {

  // 在这里可以使用 person 对象来操作 JSON 数据中包含的属性   return "success"; }

 在上述代码中,@RequestBody 注解将请求体中的 JSON 数据映射到 Person 类型的 person 参数上,并将其作为一个对象来传递给 addPerson() 方法进行处理。

测试:

问题:

  org.springframework.web.HttpMediaTypeNotSupportedException: Content-Type 'application/json;charset=UTF-8' is not supported]

 

原因:

- 不支持json数据类型处理 - 没有json类型处理的工具(jackson)

解决:

springmvc handlerAdpater配置json转化器(因为SpringMVC自带的是param类型参数,JSON属于外来的,所以需要配置json转化器),配置类需要明确:

//TODO: SpringMVC对应组件的配置类 [声明SpringMVC需要的组件信息]

//TODO: 导入handlerMapping和handlerAdapter的三种方式  //1.自动导入handlerMapping和handlerAdapter [推荐]  //2.可以不添加,springmvc会检查是否配置handlerMapping和handlerAdapter,没有配置默认加载  //3.使用@Bean方式配置handlerMapper和handlerAdapter@EnableWebMvc  //json数据处理,必须使用此注解,因为他会加入json处理器@Configuration @ComponentScan(basePackages = "com.atguigu.controller") //TODO: 进行controller扫描

//WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法! 前期可以实现 public class SpringMvcConfig implements WebMvcConfigurer {

}

 pom.xml 加入jackson依赖

    com.fasterxml.jackson.core     jackson-databind     2.15.0

 @EnableWebMvc注解说明

@EnableWebMvc注解效果等同于在 XML 配置中,可以使用 元素!我们来解析对应的解析工作!

11.2.6 接受Cookie数据

可以使用 @CookieValue 注释将 HTTP Cookie 的值绑定到控制器中的方法参数。

考虑使用以下 cookie 的请求:

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

下面的示例演示如何获取 cookie 值:

@GetMapping("/demo") public void handle(@CookieValue("JSESSIONID") String cookie) {    //... }

11.2.7 接受请求头数据

可以使用 @RequestHeader 批注将请求标头绑定到控制器中的方法参数。

请考虑以下带有标头的请求:

Host                    localhost:8080 Accept                  text/html,application/xhtml+xml,application/xml;q=0.9 Accept-Language         fr,en-gb;q=0.7,en;q=0.3 Accept-Encoding         gzip,deflate Accept-Charset          ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive              300

下面的示例获取 Accept-Encoding 和 Keep-Alive 标头的值:

@GetMapping("/demo") public void handle(     @RequestHeader("Accept-Encoding") String encoding,      @RequestHeader("Keep-Alive") long keepAlive) {    //... }

11.3 原生API操作 

下表描述了支持的控制器方法参数

获取原生对象示例:

/**  * 如果想要获取请求或者响应对象,或者会话等,可以直接在形参列表传入,并且不分先后顺序!  * 注意: 接收原生对象,并不影响参数接收!  */ @GetMapping("api") @ResponseBody public String api(HttpSession session , HttpServletRequest request,                   HttpServletResponse response){     String method = request.getMethod();     System.out.println("method = " + method);     return "api"; } 

11.4共享域对象操作 

11.4.1 属性共享域作用回顾

在 JavaWeb 中,共享域指的是在 Servlet 中存储数据,以便在同一 Web 应用程序的多个组件中进行共享和访问。常见的共享域有四种:ServletContext、HttpSession、HttpServletRequest、PageContext。

1. `ServletContext` 共享域:`ServletContext` 对象可以在整个 Web 应用程序中共享数据,是最大的共享域。一般可以用于保存整个 Web 应用程序的全局配置信息,以及所有用户都共享的数据。在 `ServletContext` 中保存的数据是线程安全的。 2. `HttpSession` 共享域:`HttpSession` 对象可以在同一用户发出的多个请求之间共享数据,但只能在同一个会话中使用。比如,可以将用户登录状态保存在 `HttpSession` 中,让用户在多个页面间保持登录状态。 3. `HttpServletRequest` 共享域:`HttpServletRequest` 对象可以在同一个请求的多个处理器方法之间共享数据。比如,可以将请求的参数和属性存储在 `HttpServletRequest` 中,让处理器方法之间可以访问这些数据。 4. `PageContext` 共享域:`PageContext` 对象是在 JSP 页面Servlet 创建时自动创建的。它可以在 JSP 的各个作用域中共享数据,包括`pageScope`、`requestScope`、`sessionScope`、`applicationScope` 等作用域。

共享域的作用是提供了方便实用的方式在同一 Web 应用程序的多个组件之间传递数据,并且可以将数据保存在不同的共享域中,根据需要进行选择和使用。

11.4.2 request级别属性(共享)域

1.使用 Model 类型的形参

@RequestMapping("/attr/request/model") @ResponseBody public String testAttrRequestModel(              // 在形参位置声明Model类型变量,用于存储模型数据         Model model) {          // 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域     // 存入请求域这个动作也被称为暴露到请求域     model.addAttribute("requestScopeMessageModel","i am very happy[model]");          return "target"; }

 2. 使用 ModelMap 类型的形参

@RequestMapping("/attr/request/model/map") @ResponseBody public String testAttrRequestModelMap(              // 在形参位置声明ModelMap类型变量,用于存储模型数据         ModelMap modelMap) {          // 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域     // 存入请求域这个动作也被称为暴露到请求域     modelMap.addAttribute("requestScopeMessageModelMap","i am very happy[model map]");          return "target"; }

3 使用 Map 类型的形参

@RequestMapping("/attr/request/map") @ResponseBody public String testAttrRequestMap(              // 在形参位置声明Map类型变量,用于存储模型数据         Map map) {          // 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域     // 存入请求域这个动作也被称为暴露到请求域     map.put("requestScopeMessageMap", "i am very happy[map]");          return "target"; }

4.使用原生 request 对象 

@RequestMapping("/attr/request/original") @ResponseBody public String testAttrOriginalRequest(              // 拿到原生对象,就可以调用原生方法执行各种操作         HttpServletRequest request) {          request.setAttribute("requestScopeMessageOriginal", "i am very happy[original]");          return "target"; }

5.使用 ModelAndView 对象 

@RequestMapping("/attr/request/mav") public ModelAndView testAttrByModelAndView() {          // 1.创建ModelAndView对象     ModelAndView modelAndView = new ModelAndView();     // 2.存入模型数据     modelAndView.addObject("requestScopeMessageMAV", "i am very happy[mav]");     // 3.设置视图名称     modelAndView.setViewName("target");          return modelAndView; }

ModelAndView构造方法可以指定返回的页面名称,

也可以通过setViewName()方法跳转到指定的页面 ,

ModelAndView. addObject (‘1.从页面找到读取的属性名’,2.后端数据集合);

11.4.3 session级别属性(共享)域

@RequestMapping("/attr/session") @ResponseBody public String testAttrSession(HttpSession session) {     //直接对session对象操作,即对会话范围操作!     return "target"; }

11.4.4 Application级别属性(共享)域 

解释:springmvc会在初始化容器的时候,将servletContext对象存储到ioc容器中!

@Autowired private ServletContext servletContext;

@RequestMapping("/attr/application") @ResponseBody public String attrApplication() {          servletContext.setAttribute("appScopeMsg", "i am hungry...");          return "target"; }

 11.5 SpringMVC响应数据

11.5.1 handler方法分析

理解handler方法的作用和组成:

/**  * TODO: 一个controller的方法是控制层的一个处理器,我们称为handler  * TODO: handler需要使用@RequestMapping/@GetMapping系列,声明路径,在HandlerMapping中注册,供DS查找!  * TODO: handler作用总结:  *       1.接收请求参数(param,json,pathVariable,共享域等)   *       2.调用业务逻辑   *       3.响应前端数据(页面(不讲解模版页面跳转),json,转发和重定向等)  * TODO: handler如何处理呢  *       1.接收参数: handler(形参列表: 主要的作用就是用来接收参数)  *       2.调用业务: { 方法体  可以向后调用业务方法 service.xx() }  *       3.响应数据: return 返回结果,可以快速响应前端数据  */ @GetMapping public Object handler(简化请求参数接收){     调用业务方法     返回的结果 (页面跳转,返回数据(json))     return 简化响应前端数据; }

总结: 请求数据接收,我们都是通过handler的形参列表

             前端数据响应,我们都是通过handler的return关键字快速处理!

            springmvc简化了参数接收和响应!

 

注意:1.上面图中handler(@PathVariable(动态路径key) 类型 参数名)这里面的动态路径key可写可不写。

 2.从上出图中可以看到只有param可以选择直选择直接接受。

精彩文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: