Spring

spring是一个开源的轻量级Java开发应用框架,可以简化企业级应用开发。Spring解决了开发者在Java…

Spring 框架概述

spring是一个开源的轻量级Java开发应用框架,可以简化企业级应用开发。Spring解决了开发者在JavaEE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及WebMVC等功能。是当前企业中Java开发几乎不能 缺少的框架之一。Spring的生态及其完善,不管是Spring哪个领域的解决方案都是依附于在Spring Framework基础框架的。

基本使用

引入Spring依赖

xml
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context</artifactId>
	<version>6.1.11</version>
</dependency>

配置beans.xml

xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       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">
    <bean id="userService" class="fun.maluyao.service.impl.UserServiceImpl"/>
</beans>

创建BeanFactory

java
public class Main {
    public static void main(String[] args) {
        //创建工厂对象
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        //创建一个读取器(xml文件)
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        //读取配置文件给工厂
        reader.loadBeanDefinitions("beans.xml");
        //根据 id 获取Bean实例对象
        UserService userService = (UserService) beanFactory.getBean("userService");
    }
}

配置依赖

xml
<bean id="userDao" class="fun.maluyao.Dao.impl.UserDaoImpl"/>
    <bean id="userService" class="fun.maluyao.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
    </bean>

备注

实际上,spring 获取属性是根据 getter,只要有类似getAbc,就能注入属性为abc

ApplicationContext快速入门

ApplicationContext称为Spring容器,内部封装了BeanFactory,比BeanFactory功能更丰富更强大,使用 ApplicationContext进行开发时,xml配置文件的名称习惯写成applicationContext.xml

使用方式

java
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        //根据 id 获取Bean实例对象
        UserService userService = (UserService) context.getBean("userService");
        System.out.println(userService);
    }
}

备注

想要直接得到beans的类型而不是object,不需要转类型的方式

java
//根据 id 获取Bean实例对象
UserService userService =context.getBean("userService", UserService.class);

只需要在获取之后加上字节码(class)对象

BeanFactory与ApplicationContext的关系

  • BeanFactory是Spring的早期接口,称为Spring的Bean工厂,ApplicationContext是后期更高级接口,称之为 Spring 容器;
  • ApplicationContext在BeanFactory基础上对功能进行了扩展,例如:监听功能、国际化功能等。BeanFactory的 API更偏向底层,ApplicationContext的APl大多数是对这些底层APl的封装;
  • Bean创建的主要逻辑和功能都被封装在BeanFactory中,ApplicationContext不仅继承了BeanFactory,而 ApplicationContext内部还维护着BeanFactory的引用,所以,ApplicationContext与BeanFactory既有继承关系,又有融合关系。
  • Bean的初始化时机不同,原始BeanFactory是在首次调用getBean时才进行Bean的创建,而ApplicationContext则是配置文件加载,容器一创建就将Bean都实例化并初始化好。
BeanFactory的继承体系

BeanFactory是核心接口,项目运行过程中肯定有具体实现参与,这个具体实现就是DefaultListableBeanFactory,而ApplicationContext内部维护的Beanfactory的实现类也是它

ApplicationContext的继承体系

只在Spring基础环境下,即只导入spring-context坐标时,此时ApplicationContext的继承体系

只在Spring基础环境下,常用的三个ApplicationContext作用如下:

实现类功能描述
FileSystemXmlApplicationContext加载磁盘路径下的xml配置的ApplicationContext
AnnotationConfigApplicationContext加载注解配置类的ApplicationContext
ClassPathXmlApplicationContext加载类路径下的xml配置的Applicationontext

基于xml的Spring应用

SpringBean的配置详解

Spring开发中主要是对Bean的配置,Bean的常用配置一览如下:

Bean的基础配置

例如:配置UserDaolmpl由Spring容器负责管理

<beanid="userDao"class="com.itheima.dao.impl.UserDaoImpl"/>

此时存储到Spring容器(singleObjects单例池)中的Bean的beanName是userDao,值是UserDaolmp 以根据beanName获取Bean实例

applicationContext.getBean("userDao");

如果不配置id,则Spring会把当前Bean实例的全限定名作为beanName

applicationContext.getBean("com.itheima.dao.impl.UserDaoImpl");
Bean的别名配置

可以为当前Bean指定多个别名,根据别名也可以获得Bean对象

<beanid="userDao"name="aaa,bbb"class="com.itheima.dao.impl.UserDaoImpl"/> 

此时多个名称都可以获得UserDaolmpl实例对象

applicationContext.getBean("userDao");
applicationContext.getBean("aaa"); 
applicationContext.getBean("bbb");
Bean的范围配置

默认情况下,单纯的Spring环境Bean的作用范围有两个:Singleton和Prototype

  • singleton:单例,默认值,Spring容器创建的时候,就会进行Bean的实例化,并存储到容器内部的单例池中,每次getBean时都是从单例池中获取相同的Bean实例
  • prototype:原型,Spring容器初始化时不会创建Bean实例,当调用getBean时才会实例化Bean,每次 getBean都会创建一个新的Bean实例。
Bean的延迟加载

当lazy-init设置为true时为延迟加载,也就是当Spring容器创建的时候,不会立即创建Bean实例,等待用到时在创建Bean实例并存储到单例池中去,后续在使用该Bean直接从单例池获取即可,本质上该Bean还是单例的

<bean id="userDao"class="com.itheima.dao.impl.UserDaoImpl" lazy-init="true"/>
Bean的初始化和销毁方法配置

Bean在被实例化后,可以执行指定的初始化方法完成一些初始化的操作,Bean在销毁之前也可以执行指定的销毁方法完成一些操作,初始化方法名称和销毁方法名称通过

<beanid="userDao"class="com.itheima.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"/>
public classUserDao Implimplements UserDao{
	public UserDaoImpl(){System.out.println("UserDaoImpl创建了...");}
	public void init(){System.out.println("初始化方法...");}
	public void destroy(){System.out.println("销毁方法...");}
}

扩展:除此之外,我们还可以通过实现InitializingBean接口,完成一些Bean的初始化操作,如下

public class Test implements InitializingBean
{
    @Override
    public void afterPropertiesSet() throws Exception {
        //配置完依赖执行的操作
        //这个方法执行在init-method之前
    }
}
Bean的实例化配置

Spring的实例化方式主要如下两种:

  • 构造方式实例化:底层通过构造方法对Bean进行实例化
  • 工厂方式实例化:底层通过调用自定义的工厂方法对Bean进行实例化

构造方式实例化Bean又分为无参构造方法实例化和有参构造方法实例化,Spring中配置的<bean>几乎都是无参构造该方式,此处不在赘述。下面讲解有参构造方法实例化

Bean//有参构造方法
public UserDaoImpl(string name){}

有参构造在实例化Bean时,需要参数的注入,通过<constructor-arg>标签,嵌入在<bean>标签内部提供构造参数,如下:

<bean id="userDao"class="com.itheima.dao.impl.UserDaoImpl">
	<constructor-argname="name" value="haohao"/>
</bean>

工厂方式实例化Bean,又分为如下三种!

  • 静态工厂方法实例化Bean

     <bean id="userDaoFromFactory" class="fun.maluyao.factory.UserDaoFactory" factory-method="getUserDao"/>
  • 实例工厂方法实例化Bean

    <bean id="userDaoFactory" class="fun.maluyao.factory.UserDaoFactory"/>
        <bean id="userDaoFromFactoryByInstance" factory-bean="userDaoFactory" factory-method="getUserDaoByInstance"/>

    工厂类:

  • 实现FactoryBean规范延迟实例化Bean 实现这个接口标准之后,就不用标注factory-method属性了,就像直接配置bean一样

    public class UserDaoFactory implements FactoryBean<UserDao> {
        @Override
        public UserDao getObject() throws Exception {
            return new UserDaoImpl();
        }
    
        @Override
        public Class<?> getObjectType() {
            return UserDao.class;
        }
    
        @Override
        public boolean isSingleton() {
            return false;
        }
    }
    <bean id="userDaoByFactoryBean" class="fun.maluyao.factory.UserDaoFactory"/>
Bean的依赖注入配置

Bean的依赖注入有两种方式:

注入方式配置方式
通过Bean的set方法注入<propertyname=“userDao” ref=“userDao”/> <propertyname=“userDao” value=“haohao”/>
通过构造Bean的方法进行注入<constructor-argname=“name” ref=“userDao”/>
<constructor-argname=“name” value=“haohao”/>

其中,ref是reference的缩写形式,翻译为:涉及,参考的意思,用于引用其他Bean的id。value用于注入普通属性值。

依赖注入的数据类型有如下三种

  • 普通数据类型,例如:String、int、boolean等,通过value属性指定

  • 引用数据类型,例如:UserDaolmpl、DataSource等,通过ref属性指定

  • 集合数据类型,例如:List、Map、Properties等。 List:

    <propertyname="stringList">
    	<list>
    		<value>aaa</value> 
    		<value>bbb</value> 
    		<value>ccc</value>
    	</list>
    </property>

    set:

    <set>标签指定

    Map:

    <map>
    	<entry key/key-ref value/value-ref/>
    </map>

    Properties:

    <property name="properties">
    	<props>
    		<prop key="p1">pppl</prop> 
    		<prop key="p1">ppp2</prop>
    	</props> 
    </property>

扩展:自动装配方式 如果被注入的属性类型是Bean引用的话,那么可以在<bean>标签中使用autowire属性去配置自动注入方式,属性值有两个:

  • byName:通过属性名自动装配,即去匹配setXxx与id=“xxx”(name=“xxx”)是否一致;

  • byType:通过Bean的类型从容器中匹配,匹配出多个相同Bean类型时,报错。

<bean id="userService" class="fun.maluyao.service.impl.UserServiceImpl" autowire="byType"/>

找不到能配置的属性,默认就是null,不会报错的

Spring的其他配置标签

Spring的xml标签大体上分为两类,一种是默认标签,一种是自定义标签 默认标签:就是不用额外导入其他命名空间约束的标签,例如标签 . 自定义标签:就是需要额外引入其他命名空间约束,并通过前缀引用的标签,例如<context:property placeholder/>标签

<beans>标签,除了经常用的做为根标签外,还可以嵌套在根标签内,使用profile属性切换开发环境

<!--配置测试环境下,需要加载的Bean实例-->
<beans profile="test"> </beans>
<!--配置开发环境下,需要加载的Bean实例-->
<beansprofile="dev"> </beans>

可以使用以下两种方式指定被激活的环境:

  • 使用命令行动态参数,虚拟机参数位置加载-Dspring.profiles.active=test
  • 使用代码的方式设置环境变量System.setProperty(“spring.profiles.active”,“test”)

<import>标签,用于导入其他配置文件,项目变大后,就会导致一个配置文件内容过多,可以将一个配置文件根据业务某块进行拆分,拆分后,最终通过<import>标签导入到一个主配置文件中,项自加载主配置文件就连后 <import>导入的文件一并加载了

<!--导入用户模块配置文件-->
<import resource="classpath:UserModuleApplicationContext.xml"/>
<!--导入商品模块配置文件-->
<import resource="classpath:ProductModuleApplicationContext.xml"/>

<alias>标签是为某个Bean添加别名,与在<bean>标签上使用name属性添加别名的方式一样,我们为 UserServicelmpl指定四个别名:aaa、bbb、xxx、yyy

<!--配置userservice-->
<bean id="userService"name="aaa,bbb"class="com.itheima.service.impl.UserServiceImpl">
	<propertyname="userDao"ref="userDao"/>
</bean>
<!指定别名-->
<aliasname="userservice"alias="xxx"/> 
<aliasname="userService"alias="yyy"/>

Spring的自定义标签需要引入外部的命名空间,并为外部的命名空间指定前缀,使用<前缀:标签>形式的标签,称之为自定义标签,自定义标签的解析流程也是Springxml扩展点方式之一,在《Spring整合其他框架》章节进行详细介绍

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:ocntext="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-beans.xsd

">
<!--默认标签-->
<beanid="userDao"class="com.itheima.dao.impl.UserDaoImpl"/>
《!-一自定义标签-->
<context:property-placeholder/> 
<mvc:annotation-driven/>
<dubbo:appiicationname="appiication"/>

Spring的get方法

方法定义返回值和参数
getBean(StringbeanName)根据beanName从容器中获取Bean实例,要求容器中Bean唯一,返回值为Obiect,需要强转
getBean(Classtype)根据Class类型从容器中获取Bean实例,要求容器中Bean类型唯一,返回值为Class类型实例无需强转
getBean(StringbeanName,Classtype)根据beanName从容器中获得Bean实例,返回值为Class类型实例,无需强转

Bean实例化的基本流程

Bean实例化的基本流程 Spring容器在进行初始化时,会将xml配置的<bean>的信息封装成一个BeanDefinition对象,所有的 BeanDefinition存储到一个名为beanDefinitionMap的Map集合中去,Spring框架在对该Map进行遍历,使用反射创建Bean实例对象,创建好的Bean对象存储在一个名为singletonObjects的Map集合中,当调用getBean方法时则最终从该Map集合中取出Bean实例对象返回。

Spring框架会取出beanDefinitionMap中的每个BeanDefinition信息,反射构造方法或调用指定的工厂方法生成Bean实例对象,所以只要将BeanDefinition注册到beanDefinitionMap这个Map中,Spring就会进行对应的Bean的实例化操作

Bean实例及单例池singletonObjects,beanDefinitionMap中的BeanDefinition会被转化成对应的Bean实例对象,存储到单例池singletonObjects中去,在DefaultListableBeanFactory的上四级父类

  • 加载xml配置文件,解析获取配置中的每个<bean>的信息,封装成一个个的BeanDefinition对象
  • 将BeanDefinition存储在一个名为beanDefinitionMap的Map<String,BeanDefinition>中
  • ApplicationContext底层遍历beanDefinitionMap,创建Bean实例对象;
  • 创建好的Bean实例对象,被存储到一个名为singletonObjects的Map<String,Object>中
  • 当执行applicationContext.getBean(beanName)时,从singletonObjects去匹配Bean实例返回。

Spring的后处理器

Spring的后处理器是Spring对外开发的重要扩展点,充许我们介入到Bean的整个实例化流程中来,以达到动态注册 BeanDefinition,动态修改BeanDefinition,以及动态修改Bean的作用。Spring主要有两种后处理器:

  • BeanFactoryPostProcessor:Bean工厂后处理器,在BeanDefinitionMap填充完毕,Bean实例化之前执行
  • BeanPostProcessor:Bean后处理器,一般在Bean实例化之后,填充到单例池singletonObjects之前执行。
Bean工厂后处理器-BeanFactoryPostProcessor

BeanFactoryPostProcessor是一个接口规范,实现了该接口的类只要交由Spring容器管理的话,那么Spring就会回调该接口的方法,用于对BeanDefinition注册和修改的功能 BeanFactoryPostProcessor定义如下:

public interface BeanFactoryPostProcessor{
	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory);
}

Spring提供了一个BeanFactoryPostProcessor的子接口BeanDefinitionRegistryPostProcessor专门用于注册 BeanDefinition操作

Bean后处理器-BeanPostProcessor

Bean被实例化后,到最终缓存到名为singletonObjects单例池之前,中间会经过Bean的初始化过程,例如:属性的填充、初始方法init的执行等,其中有一个对外进行扩展的点BeanPostProcessor,我们称为Bean后处理。跟上面的 Bean工厂后处理器相似,它也是一个接口,实现了该接口并被容器管理的BeanPostProcessor,会在流程节点上被 Spring自动调用。

public class Myprosessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object o, String s) throws BeansException {
    //init方法之前执行,而且要早于InitializingBean定义的afterPropertiesSet方法
        return null;
    }

    @Override
    public Object postProcessAfterInitialization(Object o, String s) throws BeansException {
    //init方法之后执行
        return null;
    }
}

SpringBean的生命周期

SpringBean的生命周期是从Bean实例化之后,即通过反射创建出对象之后,到Bean成为一个完整对象,最终存储到单例池中,这个过程被称为SpringBean的生命周期。SpringBean的生命周期大体上分为三个阶段:

  • Bean的实例化阶段:Spring框架会取出BeanDefinition的信息进行判断当前Bean的范围是否是singleton的是否不是延迟加载的,是否不是FactoryBean等,最终将一个普通的singleton的Bean通过反射进行实例化

  • Bean的初始化阶段:Bean创建之后还仅仅是个”半成品”,还需要对Bean实例的属性进行填充、执行一些Aware 接口方法、执行BeanPostProcessor方法、执行lnitializingBean接口的初始化方法、执行自定义初始化init方法等。该阶段是Spring最具技术含量和复杂度的阶段,Aop增强功能,后面要学习的Spring的注解功能等、 spring高频面试题Bean的循环引用问题都是在这个阶段体现的;

  • Bean的完成阶段:经过初始化阶段,Bean就成为了一个完整的SpringBean,被存储到单例池 singletonObjects中去了,即完成了SpringBean的整个生命周期。

由于Bean的初始化阶段的步骤比较复杂,所以着重研究Bean的初始化阶段 SpringBean的初始化过程涉及如下几个过程

  • Bean实例的属性填充
  • Aware接口属性注入
  • BeanPostProcessor的beforeo方法回调
  • InitializingBean接口的初始化方法回调
  • 自定义初始化方法init回调
  • BeanPostProcessor的afterO方法回调
Bean实例属性填充

Spring在进行属性注入时,会分为如下几种情况: 注入普通属性,String、int或存储基本类型的集合时,直接通过set方法的反射设置进去: 注入单向对象引用属性时,从容器中getBean获取后通过set方法反射设置进去,如果容器中没有,则先创建被注入对象Bean实例(完成整个生命周期)后,在进行注入操作 注入双向对象引用属性时,就比较复杂了,涉及了循环引用(循环依赖)问题,下面会详细阐述解决方案

常用的Aware接口

Aware接口是一种框架辅助属性注入的一种思想,其他框架中也可以看到类似的接口。框架具备高度封装性,我们接触到的一般都是业务代码,一个底层功能AP不能轻易的获取到,但是这不意味着永远用不到这些对象,如果用到了,就可以使用框架提供的类似Aware的接口,让框架给我们注入该对象。

Aware接口回调方法作用
ServletContextAwaresetServletContext(ServletContextcontext)Spring框架回调方法注入ServletContext对象, web环境下才生效
BeanFactoryAwaresetBeanFactory(BeanFactoryfactory)Spring框架回调方法注入beanFactory对象
BeanNameAwaresetBeanName(StringbeanName)Spring框架回调方法注入当前Bean在容器中的beanName
ApplicationContextAwaresetApplicationContext(ApplicationContextapplicationContextSpring框架回调方法注入applicationContext 对象

Spring loC整体流程总结

Spring xml方式整合第三方框架

xml整合第三方框架有两种整合方案

  • 不需要自定义名空间,不需要使用Spring的配置文件配置第三方框架本身内容,例如:MyBatis
  • 需要引入第三方框架命名空间,需要使用Spring的配置文件配置第三方框架本身内容,例如:Dubbo
整合Mybatis

配置依赖

xml
<dependency>
	<groupId>com.mysql</groupId>
	<artifactId>mysql-connector-j</artifactId>
	<version>8.3.0</version>
</dependency>
<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis</artifactId>
	<version>3.5.16</version>
</dependency>
<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis-spring</artifactId>
	<version>3.0.3</version>
</dependency>

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-jdbc</artifactId>
	<version>6.1.11</version>
</dependency>
<!--如果使用事务处理的话(在spring-jdbc内部有,不用配置)-->
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-tx</artifactId>
	<version>6.1.11</version>
</dependency>

MyBatis提供了mybatis-spring.jar专门用于两大框架的整合。 Spring整合MyBatis的步骤如下:

  • 导入MyBatis整合Spring的相关坐标;
  • 配置SqlSessionFactoryBean和MapperScannerConfigurer;
  • 编写测试代码
xml
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
	<property name="url" value=""/>
	<property name="driver" value=""/>
	<property name="username" value=""/>
	<property name="password" value=""/>
</bean>
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
	<property name="dataSource" ref="dataSource"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	<property name="basePackage" value="fun.maluyao.mapper"/>
</bean>

配置处理之后,spring可以直接得到mapper对象在spring容器中,可以直接用于注入!

Spring整合其他组件时就不像MyBatis这么简单了,例如Dubbo框架在于Spring进行整合时,要使用Dubbo提供的命名空间的扩展方式,自定义了一些Dubbo的标签

需求:加载外部properties文件,将键值对存储在Spring容器中

配置属性位置解析器
<context:property-placeholder location="classpath:dataSource.properties"/>
使用:
${}

Spring注解开发

启用注解开发

//开启组件扫描
<context:component-scan base-package="fun.maluyao"/>

Spring除了xml配置文件进行配置之外,还可以使用注解方式进行配置,注解方式慢慢成为xml配置的替代方案。我 们有了xmI开发的经验,学习注解开发就方便了许多,注解开发更加快捷方便 Spring提供的注解有三个版本:

  • 2.0时代,Spring开始出现注解
  • 2.5时代,Spring的Bean配置可以使用注解完成
  • 3.0时代,Spring其他配置也可以使用注解完成,我们进入全注解时代

使用@Component注解替代<bean>标签

xml配置注解描述
<beanid=""class=>@Component被该注解标识的类,会在指定扫描范围内被Spring加载并实例化
<!--注解组件扫描:扫描指定的基本包及其子包,识别使用@Component注解-->
<context:component-scan base-package="fun.maluyao"/>
扫描类路径,查找将自动注册为Spring bean的带注释组件。
默认情况下,spring提供的@Component、@Repository、@Service、@Controller、@RestController、@ControllerAdvice和@Configuration构造型会被检测到。
注意:这个标签暗示了“annotation-config”标签的效果,激活组件类中的@Required, @Autowired, @PostConstruct, @PreDestroy, @Resource, @PersistenceContext和@PersistenceUnit注释,这通常是自动检测组件(不需要外部配置)所需要的。
关闭'annotation-config'属性以禁用此默认行为,例如,为了使用自定义BeanPostProcessor定义来处理那些注释。
注意:您可以在包路径中使用占位符,但只能针对系统属性进行解析(类似于资源路径)。
组件扫描导致注册新的bean定义;
Spring的PropertySourcesPlaceholderConfigurer将应用于这些bean定义,就像应用于常规bean定义一样,但它不会应用于组件扫描设置本身。
请参阅javadoc中的org.springframework.context.annotation.ComponentScan,了解基于代码的启动组件扫描的替代方法。

//启用注解
<context:annotation-config/>
激活要在bean类中检测的各种注释:Spring的@Required和@Autowired,以及JSR 250的@PostConstruct, @PreDestroy和@Resource(如果可用),JAX-WS的@WebServiceRef(如果可用),EJB 3的@EJB(如果可用),以及JPA的@PersistenceContext和@PersistenceUnit(如果可用)。
或者,您可以选择为这些注释激活单个BeanPostProcessors。
注意:这个标签不会激活Spring的@Transactional或EJB 3的@TransactionAttribute注释的处理。
考虑使用<tx:annotation-driven>标记来实现这个目的。

由于JavaEE开发是分层的,为了每层Bean标识的注解语义化更加明确,@Component又衍生出如下三个注解

@Component衍生注解描述
@Repository在Dao层类上使用
@Service在Service层类上使用
@Controller在Web层类上使用

Spring主要提供如下注解,用于在Bean内部进行属性注入的:

属性注入注解描述
@Value使用在字段或方法上,用于注入普通数据
@Autowired使用在字段或方法上,用于根据类型(byType)注入引用数据
@Qualifier使用在字段或方法上,结合@Autowired,根据名称注入
@Resource使用在字段或方法上,根据类型或名称进行注入

重要

@Value注解可以使用EL表达式获取spring属性,比如说${jdbc.url}

@Resource注解是javax.annotation包下面的,什么都不写就是Autowired,如果指定属性name,就根据名称注入

@Autowired
public UserDao getUserDao(UserDao userDao) {
	return userDao;
}

@Autowired用于方法上,当匹配不止一个时候,就会根据接受的变量名称进行匹配,即userDao

@Autowired
public UserDao getUserDao(List<UserDao> userDao) {
	return userDao;
}

参数含有集合,有几个就注入多少个

非自定义Bean注解开发

非自定义Bean不能像自定义Bean一样使用@Component进行管理,非自定义Bean要通过工厂的方式进行实例化使用@Bean标注方法即可,@Bean的属性为beanName,如不指定为当前工厂方法名称

//将方法返回值Bean实例以@Bean注解指定的名称存储到spring容器中
@Bean("datasource")
pubiic DataSource dataSource(
	DruidDataSource dataSource=new DruidDataSource();
	dataSource.setDriverclassName("com.mysgl.jdbc.Driver");
	dataSource.setUrl("jdbc:mysgl://iocalhost:33o6/mybatis")
	dataSource.setUsername("root"); 
	dataSource.setPassword("root"); 
	return datasSource;
}

PS:工厂方法所在类必须要被Spring管理

加上@Bean标签的方法会尝试自动注入,他会自己找对象,不然无法完成方法调用

	@Bean
    public UserServiceImpl getUserService(UserDao userDao) {
        return new UserServiceImpl();
    } 

如果找到多个,可以加上@Qualifier注解

配置类注解开发(全注解)

使用配置类来替代配置文件,这样就不用写配置文件了

@Configuration//标注当前类是一个配置类(替代配置文件)+@Component
@ComponentScan//配置组件扫描
@PropertySource("classpath:jdbc.properties")//属性资源文件
@Import(OtherBean.class)//加载其他配置类,相当于<import>

创建application

new AnnotationConfigApplicationContext()

Spring配置其他注解 扩展:@Primary注解用于标注相同类型的Bean优先被使用权,@Primary是Spring3.o引入的,与@Component 和@Bean一起使用,标注该Bean的优先级更高,则在通过类型获取Bean或通过@Autowired根据类型进行注入时会选用优先级更高的

扩展:@Profile注解的作用同于xml配置时学习profile属性,是进行环境切换使用的 <

beansprofile="test">

注解@Profile标注在类或方法上,标注当前产生的Bean从属于哪个环境,只有激活了当前环境,被标注的Bean才能被注册到Spring容器里,不指定环境的Bean,任何环境下都能注册到Spring容器里

@Repository("userDao")
@Profile("test")
public class UserDaoImpl implements UserDao()

@Repository("userDao2")
public class UserDaoImpl2implementsUserDao()

注解方式整合第三方框架

//Mapper的接口扫描
@MapperScan("com.itheima.mapper")
sqlsessionFactoryBean使用@Bean注解配置

Spring与MyBatis注解方式整合有个重要的技术点就是@lmport,第三方框架与Spring整合xml方式很多是凭借自 定义标签完成的,而第三方框架与Spring整合注解方式很多是靠@lmport注解完成的。@Import可以导入如下三种类

  • 普通的配置类
  • 实现ImportSelector接口的类
  • 实现ImportBeanDefinitionRegistrar接口的类

Spring的AOP开发

AOP简介

AoP,AspectOrientedProgramming,面向切面编程,是对面向对象编程ooP的升华。OoP是纵向对一个事物的抽象,一对象包括静态的属性信息,包括动态的方法信息等。而AOP是横可的对不同事物的抽象,属性与属性、方法与方法、对象与对象都可以组成一个切面,而用这种思维去设计编程的方式叫做面向切面编程

基于xml配置的AOP

xml方式配置AOP的步骤:

1、导入AOP相关坐标;

<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjweaver</artifactId>
	<version>1.9.7</version>
</dependency>
//这是实现AOP的包

2、准备目标类、准备增强类,并配置给Spring管理,

<!--配置增强类-->
<bean id="advice" class="fun.maluyao.advice.MyAdvice"/>
<!--配置目标类-->
<bean id="target" class="fun.maluyao.test.AdviceTest"/>

3、配置切点表达式(哪些方法被增强);

<aop:config>
	<!--配置切点表达式,指定哪些方法被增强-->
	<aop:pointcut id="myPointCut" expression="execution(void.fun.maluyao.test.AdviceTest.run())"/>
</aop:config>

4、配置织入(切点被哪些通知方法增强,是前置增强还是后置增强)。

<!--配置织入,指定哪些切点与那些通知来结合的-->
<aop:aspect ref="advice">
	<aop:before method="beforeAdvice" pointcut-ref="myPointCut"/>
</aop:aspect>

创建增强类

//增强类,内部提供增强方法
public class MyAdvice {
    public void beforeAdvice() {
        System.out.println("beforeAdvice");
    }
    public void afterAdvice() {
        System.out.println("afterAdvice");
    }
}

待增强的类

public class AdviceTest {
    public void run(){
        System.out.println("running");
    }
}

xm配置AOP的方式还是比较简单的,下面看一下AOP详细配置的细节:

  • 切点表达式的配置方式
  • 切点表达式的配置语法
  • 通知的类型
  • AOP的配置的两种方式

切点表达式是配置要对哪些连接点(哪些类的哪些方法)进行通知的增强,语法如下

execution([访问修饰符] 返回值类型 包名.类名.方法名(参数))

其中,

  • 访问修饰符可以省略不写;

  • 返回值类型、某一级包名、类名、方法名可以使用*表示任意

  • 包名与类名之间使用单点.表示该包下的类,使用双点..表示该包及其子包下的类

  • 参数列表可以使用两个点表示任意参数。

    下面两个效果一样
    execution(public void fun.maluyao..*(..))
    execution(public void fun.maluyao..*.*(..))

Aspect的通知由以下五种类型

AOP的xml有两种配置方式,如下 使用<advisor>配置切面使用<aspect>配置切面 Spring定义了一个Advice接口,实现了该接口的类都可以作为通知类出现,查看他的子接口就能配置好Advice

public class MyAdvice2 implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("beforeAdvice");
    }
}
<aop:config>
	<aop:pointcut id="myPointCut" expression="execution(public void fun.maluyao..*(..))"/>
	<aop:advisor advice-ref="advice2" pointcut-ref="myPointCut"/>
</aop:config>

好处:不用指定通知类型

AOP配置的两种语法形式不同点

语法形式不同:

  • advisor是通过实现接口来确认通知的类型

  • aspect是通过配置确认通知的类型,更加灵活

可配置的切面数量不同:

  • 一个advisor只能配置一个固定通知和一个切点表达式
  • 一个aspect可以配置多个通知和多个切点表达式任意组合

使用场景不同:

  • 允许随意搭配情况下可以使用aspect进行配置
  • 如果通知类型单一、切面单一的情况下可以使用advisor进行配置 心
  • 在通知类型已经固定,不用人为指定通知类型时,可以使用advisor进行配置,例如后面要学习的Spring 事务控制的配置

基于注解配置的AOP

Spring的AOP也提供了注解方式配置,使用相应的注解替代之前的xml配置,xml配置AOP时,我们主要配置了三部分:目标类被Spring容器管理、通知类被Spring管理、通知与切点的织入(切面),如下:

  • 注解方式AOP基本使用
<!--使用注解配置AOP,需要开启AOP自动代理-->
<aop:aspectj-autoproxy/>

切点表达式的抽取:

@Aspect
public class MyAdvice {
    @Pointcut("execution(void fun..*(..))")
    public void pointCut(){}
    @Before("MyAdvice.pointCut()")
    public void beforeAdvice() {
        System.out.println("beforeAdvice");
    }
}

全注解开发使用注解@EnableAspectJAutoProxy来配置取代<aop:aspectj-autoproxy/>xml配置

@EnableAspectJAutoProxy

基于AOP的声明式事务控制

Spring事务编程概述

事务是开发中必不可少的东西,使用DBc开发时,我们使用connnection对事务进行控制,使用MyBatis时,我们使用SglSession对事务进行控制,缺点显而易见,当我们切换数据库访问技术时,事务控制的方式总会变化 Spring就将这些技术基础上,提供了统一的控制事务的接口。Spring的事务分为:编程式事务控制和声明式事务控制

事务控制方式解释
编程式事务控制Spring提供了事务控制的类和方法,使用编码的方式对业务代码进行事务控制,事务控制代码和业务操作代码耦合到了一起,并发中不使用
声明式事务控制Spring将事务控制的代码封装,对外提供了Xml和注解配置方式,通过配置的方式完成事务的控制可以达到事务控制与业务操作代码解耦合,开发中推荐使用

Spring事务编程相关的类主要有如下三个

事务控制相关类解释
平台事务管理器PlatformTransactionManager是一个接口标准,实现类都具备事务提交、回滚和获得事务对象的功能,不同持久层框架可能会有不同实现方案
事务定义TransactionDefinition封装事务的隔离级别、传播行为、过期时间等属性信息
事务状态TransactionStatus存储当前事务的状态信息,如果事务是否提交、是否回滚、是否有回滚点等

虽然编程式事务控制我们不学习,但是编程式事务控制对应的这些类我们需要了解一下,因为我们在通过配置的方式进行声明式事务控制时也会看到这些类的影子

结合上面我们学习的AOP的技未,很容易就可以想到,可以使用AOP对SerVice的方法进行事务的增强

  • 自标类:自定义的AccountServicelmpl,内部的方法是切点
  • 通知类:Spring提供的,通知方法已经定义好,只需要配置即可

我们分析:

  • 通知类是Spring提供的,需要导入Spring事务的相关的坐标,
  • 配置自标类AccountServicelmpl;
  • 使用advisor标签配置切面。
基于xml声明式事务控制
xml
<bean id="transactionManager" class="org.springframework.jdbc.support.JdbcTransactionManager">
	<property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="transaction" transaction-manager="transactionManager">
	<tx:attributes>
		<tx:method name="*"/>
	</tx:attributes>
</tx:advice>
<aop:config>
	<aop:advisor advice-ref="transaction" pointcut=""/>
</aop:config>

只要事务的增强类增强对应的事务方法就可以达到事务处理了

	<tx:attributes>
		<tx:method name="*"/>
	</tx:attributes>

之间配置需要事务的方法,name属性就是方法名称,可以使用*代表通配符,对于添加操作,比如addMoneyaddBook等就可以使用通配符,add*,在它的内部可以配置属性

    <tx:advice id="transaction" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" isolation="READ_COMMITTED"/>
        </tx:attributes>
    </tx:advice>

isolation属性设置事务隔离级别!

isolation属性:指定事务的隔离级别,事务并发存在三大问题:脏读、不可重复读、幻读/虚读。可以通过设置事务的隔离级别来保证并发问题的出现,常用的是READCOMMITTED和REPEATABLEREAD

isolation属性解释
DEFAULT默认隔离级别,取决于当前数据库隔离级别,例如MySQL默认隔离级别是REPEATABLE_READ
READ_UNCOMMITTEDA事务可以读取到B事务尚未提交的事务记录,不能解决任何并发问题,安全性最低,性能最高
READ_COMMITTEDA事务只能读取到其他事务已经提交的记录,不能读取到未提交的记录。可以解决脏读问题,但是不能解决不可重复读和幻读
REPEATABLE_READA事务多次从数据库读取某条记录结果一致,可以解决不可重复读,不可以解决幻读串行化,
SERIALIZABLE可以解决任何并发问题,安全性最高,但是性能最低
<tx:method name="*" isolation="READ_COMMITTED" timeout="10"/>

timeout属性设置超时,单位是秒,-1默认值,代表不管多久

<tx:method name="ffc" read-only="true"/>

read-only属性设置只读,可以显著提高性能

<tx:method name="ffc" propagation="MANDATORY"/>

propagation:事务的传播行为,解决业务方法调用业务方法(事务嵌套问题)

propagation属性:设置事务的传播行为,主要解决是A方法调用B方法时,事务的传播方式问题的,例如:使用单方的事务,还是A和B都使用自己已的事务等。事务的传播行为有如下七种属性值可配置

基于注解声明式事务控制

@Transactional注解加在类上,那么类上面的所有方法都会使用事务
单独配在方法上就使用,该方法就近取值

使用注解需要开启开关

<!--事务的自动代理(注解驱动)--> 
<tx:annotation-driven transaction-manager="transactionManager"/>

全注解开发,使用@EnableTransactionManagement 注解取代>tx:annotation-driven/>

@EnableTransactionManagement

Spring整合web环境

  • Javaweb三大组件及环境特点
  • Spring整合web环境的思路及实现
  • Spring的web开发组件spring-web

Javaweb三大组件及环境特点

在Java语言范畴内,web层框架都是基于Javaweb基础组件完成的,所以有必要复习一下Javaweb组件的特点

组件作用特点
Servlet服务端小程序,负责接收客户端请求并作出响应的单例对象,默认第一次访问创建,可以通过配置指定服务器启动就创建,Servlet 创建完毕会执行初始化init方法。每人Servlet有一个service方法,每次访问都会执行service方法,但是缺点是一个业务功能就需要配置一个Servlet
Filter过滤器,负责对客户端请求进行过滤操作的单例对象,服务器启动时就创建,对象创建完毕执行init方法,对客户端的请求进行过滤,符合要求的放行,不符合要求的直接响应客户端,执行过滤的核心方法doFilter
Listener监听器,负责对域对象的创建和属性变化进行监听的根据类型和作用不同,又可分为监听域对象创建销毁和域对象属性内容变化的根据监听的域不同,又可以分为监听Request域的,监听Session域的,监听 ServletContext域的

Spring整合web环境的思路及实现

在进行Java开发时要遵循三层架构+MvC,Spring操作最核心的就是Spring容器,web层需要注入Service, service层需要注入Dao(Mapper),web层使用Servlet技术充当的话,需要在Servlet中获得Spring容器

AnnotationConfigApplicationContextapplicationContext:
newAnnotationConfigApplicationContext(ApplicationContextConfig.class);
AccountServiceaccountService=(AccountService)applicationContext.getBean("accountservice"); accountservice.transferMoney("tom","lucy",1oo);

web层代码如果都去编写创建AnnotationConfigApplicationContext的代码,那么配置类重复被加载了, Spring容器也重复被创建了,不能每次想从容器中获得一个Bean都得先创建一次容器,这样肯定是不允许。所以,我们现在的诉求很简单,如下:

  • ApplicationContext创建一次,配置类加载一次
  • 最好web服务器启动时,就执行第1步操作,后续直接从容器中获取Bean使用即可
  • ApplicationContext的引用需要在web层任何位置都可以获取到。

针对以上诉求我们给出解决思路,如下:

  • 在ServletContextListener的contextlnitialized方法中执行ApplicationContext的创建。或在Servlet的init 方法中执行ApplicationContext的创建,并给Servlet的load-on-startup属性一个数字值,确保服务器启动 Servlet就创建
  • 将创建好的ApplicationContext存储到ServletContext域中,这样整个web层任何位置就都可以获取到了

Spring的web开发组件spring-web

到此,就将一开始的诉求都解决了,当然我们能想到的Spring框架自然也会想到,Spring其实已经为我们定义好了一个ContextLoaderListener,使用方式跟我们上面自已定义的大体一样,但是功能要比我们强百倍,所以,遵循Spring“拿来主义”的精神,我们直接使用Spring提供的就可以了,开发如下: 先导入Spring-web的坐标:

<dependency>
	<groupId>org.springframework</groupId> 
	<artifactId>spring-web</artifactid>
	<version>5.3.7</version> 
</dependency>
<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>classpath:beans.xml</param-value>
</context-param>

获取application

@WebServlet("/a")
public class Index extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) {
        WebApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(req.getServletContext());
        System.out.println(applicationContext);
    }
}

SpringMVC

MVC框架思想及其设计思路

原始Javaweb开发中,Servlet充当Controller的角色,Jsp充当View角色,JavaBean充当模型角色,后期Ajax异 步流行后,在加上现在前后端分离开发模式成熟后,View就被原始Html+Vue替代。原始Javaweb开发中 Service充当Controller有很多弊端,显而易见的有如下几个

Servlet作为Controller的问题解决思路和方案
每个业务功能请求都对应一个Servlet根据业务模块去划分Controllel
每个Servlet的业务操作太繁将通用的行为,功能进行抽取封装
Servlet获得Spring容器的组件只能通过客户端代码去获取,不能优雅的整合通过Spring的扩展点,去封装一人框架,从原有的Servlet完全接手过来web层的业务

负责共有行为的Servlet称之为前端控制器,负责业务行为的JavaBean称之为控制器Controllen

分析前端控制器基本功能如下: 1、具备可以映射到业务Bean的能力

2、具备可以解析请求参数、封装实体等共有功能

3、具备响应视图及响应其他数据的功能

SpringMVC简介

SpringMVc是一个基于Spring开发的MVc轻量级框架,Spring3.o后发布的组件,SpringMVc和Spring可以无缝整合,使用DispatcherServlet作为前端控制器,且内部提供了处理器映射器、处理器适配器、视图解析器等组件,可以简化JavaBean封装,Json转化、文件上传等操作。

快速入门

1、导入spring-mvc坐标

xml
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>6.1.11</version>
</dependency>

2、配置前端控制器DispatcherServlet

xml
<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-webmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

3、编写Controller,配置映射路径,并交给SpringMVC容器管理

java
@Controller
public class MainController {
    @RequestMapping("/a")
    @ResponseBody
    public String index(){
        return "Hello World";
    }
}

注意

在这里面同时也配置spring -web的容器,那么spring -web的容器所定义的bean可以被spring-webmvc的容器扫描到,可以直接注入

部署

SpringMvc关键组件浅析

上面已经完成的快速入门的操作,也在不知不觉中完成的Spring和SpringMVC的整合,我们只需要按照规则去定义Controller和业务方法就可以。但是在这个过程中,肯定是很多核心功能类参与到其中,这些核心功能类,一股称为组件。当请求到达服务器时,是哪个组件接收的请求,是哪个组件帮我们找到的Controller,是哪个组件帮我们调用的方法,又是哪个组件最终解析的视图?

75

spring-mvc默认组件

如果spring容器中没有组件,就会使用默认组件,但是如果spring容器中有组件,就会使用容器中的组件,默认组件不读取

SpringMVC的请求处理

请求映射路径的配置

请求数据的接收

接收普通请求数据,当客户端提交的数据是普通键值对形式时,直接使用同名形参接收即可

username=haohao&age=35
java
@GetMapping("/show")
  pubiicstringshow(string username,int age)(
  System.out.println(username+"=="+age); return"/index.jsp";
}

如果参数不匹配,得到的值就是null,要想名字不匹配的时候还获得参数,那么可以使用注解@RequestParam 传入一个复选框内容,可以使用一个数组接受它。如果你使用List接受的话,会报错,因为spring会当成一个Bean,尝试将参数封装为一个对象,在传入。可以加上标志@RequestParam注解,告诉springmvc把请求参数解析完毕之后,封装进入

可以使用Map一次接受所有参数,但是前面也要加上@RequestParam注解,这样才能完成封装

@RequestParam注解文档
注释,指示方法参数应该绑定到web请求参数。
在Spring MVC和Spring WebFlux中支持带注释的处理程序方法如下:在Spring MVC中,“请求参数”映射到多部分请求中的查询参数、表单数据和部件。
这是因为Servlet API将查询参数和表单数据组合到一个称为“参数”的映射中,并且该映射包括对请求体的自动解析。
在Spring WebFlux中,“请求参数”只映射到查询参数。
要处理所有3种数据、查询数据、表单数据和多部分数据,您可以使用数据绑定到带有ModelAttribute注释的命令对象。
如果方法参数类型为Map,并且指定了请求参数名称,那么假定有适当的转换策略,则将请求参数值转换为Map。
如果方法参数为Map<String, String>或MultiValueMap<String, String>且未指定参数名称,则使用所有请求参数名称和值填充Map参数。

@RequestParam还有属性require ,如果打开的话,那么没找到对应的参数就会报错

他还有的属性defaultValue,这个可以提供默认值

基本数据类比如int类型的参数,接收不到就会报错,可以使用包装类 Integer来接受,没有就是null

接收实体JavaBean属性数据,单个JavaBean数据:提交的参数名称只要与Java的属性名一致,就可以进行自动封装

例如实体Student

public class Student {
    String name;
    Integer age;
    Address address;
}
public class Address {
    String street;
    String city;
}

对应参数:

name=zhangsan&age=18&address.street=aaa&street.city=bbb

提交的时候如果是直接就是请求体内容,并想使用String来全部接受,那么你需要加上注解@RequestBody把请求体数据全部封装给字符串

而且把请求参数封装到请求体的x-www-form-unlencoded表单封装数据格式其实就是简单的&拼接

现在我们考虑如果请求体中的数据是一个JSON字符串,我们要怎么把它转化为一个对象呢,一般我们可以使用Jacson来做转化的过程

更简单的方式

配置RequestMappingHandlerAdapter,指定消息转换器,就不用手动转换json格式字符串了

可以在HanderlerAdaptor中配置消息转化器人为地进行转换

接收的时候
String param(@RequestBody Student student){}

最好这样设置,保留原本的消息转换器

xml
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
	<property name="messageConverters">
		<list>
			<bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
			<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
		</list>
	</property>
</bean>
接收Restful风格数据

什么是Rest风格? Rest(RepresentationalStateTransfer)表象化状态转变(表述性状态转变),在20oO年被提出,基于HTTP、UR、xml、JSON等标准和协议,支持轻量级、跨平台、跨语言的架构设计。是Web服务的一种新网络应用程序的设计风格和开发方式。

Restfu风格的请求,常见的规则有如下三点: 用URI表示某个模块资源,资源名称为名词:

用请求方式表示模块具体业务动作,例如:GET表示查询、POST表示插入、PUT表示更新、DELETE表示删除

用HTTP响应状态码表示结果,国内常用的响应包括三部分:状态码、状态信息、响应数据

要接受路径参数,要求:

java
//使用{}包裹路径参数
//使用@PathVariable注解标记接受路径参数
@RequestMapping("/{user}")
public String index(@PathVariable String user){}

接收文件上传的数据,文件上传的表单需要一定的要求,如下: 表单的提交方式必须是POST 表单的enctype属性必须是multipart/form-data 文件上传项需要有name属性

html
<form action="" enctype="multipart/form-data" method="post" >
	<inputtype="file"name="myFile">
</form>

接受文件需要使用

xml
String index(@RequestBody MultipartFile file)
//因为文件在请求体中,所以需要使用注解@RequestBody
//名字需要和表单项一致

只是这样还不够,会爆出异常:由于没有提供multi-part配置,无法处理parts

原因:springmvc默认没有开启文件接收的功能,没有文件接收的解析器

服务器端,由于映射器适配器需要文件上传解析器,而该解析器默认未被注册,所以手动注册

需要一个实现了org.springframework.web.multipart.MultipartResolver的解析器

注意

现在的实现不依赖于上面的了,而是使用自带的StandardServletMultipartResolver来解析文件

xml
<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"/>

这样配置完成还是不行,还是一样的错误,需要在servlet中配置

xml
<servlet>
        <servlet-name>SpringServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-webmvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
        <multipart-config>
            <max-file-size>30</max-file-size>
            <max-request-size>30</max-request-size>
        </multipart-config>
    </servlet>

可能的异常:

解决方案:

1、按照上面讲的,没有指定MultipartFile,并且参数名称信息无法通过反射获得。确保编译器使用’-parameters’标志。需要指定”-parameters“编译器标志

2、人为地指定参数名称

java
String index(@RequestBody @RequestParam("file") MultipartFile file){

获取请求头:

获得客户端携带的Cookie数据

获得转发Request域中数据,在进行资源之间转发时,有时需要将一些参数存储到request域中携带给下一个资源

Javaweb常用对象获取

直接写在参数上就行了,spring会直接注入

请求静态资源

现在请求不到静态资源了,因为覆盖了tomcat的全局配置(DefaultServlet的<url-partten>是/

1、把静态资源交给tomcatDefaultServlet

第一种方案,可以再次激活Tomcat的DefaultServlet,Servlet的url-pattern的匹配优先级是:精确匹配>目录匹配> 扩展名匹配>缺省匹配,所以可以指定某个目录下或某个扩展名的资源使用DefaultServlet进行解析:

xml
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.html</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>/img/*</url-pattern>
</servlet-mapping>

后缀匹配比一般匹配优先级高

2、第二种方式,在spring-mvc.xml中去配置静态资源映射,匹配映射路径的请求到指定的位置去匹配资源

xml
<!--mapping是映射资源路径,location是对应资源所在的位置 -->
<mvc:resources mapping="/img/*"location="/img/"/> 
<mvc:resources mapping="/css/"location="/css/"/> 
<mvc:resources mapping="/css/*"location="/js/"/> 
<mvc:resources mapping="/html/* location="/html/"/>
//这种方式静态资源不能放在根路径下,因为location 不能配置为/
location最好要以/结尾,表示到这个目录中寻找资源

3、第三种方式,在spring-mvc.xml中去配置<mvc:default-servlet-handler>,该方式是注册了一个 DefaultServletHttpReguestHandler处理器,静态资源的访问都由该处理器去处理,这也是开发中使用最多的

xml
<mvc:default-servlet-handler/>

注意

<mvc:default-servlet-handler/>会向spring容器中加入SimpleUrlHandlerMapping( 是HandlerMapping子类),这样就没有默认的HandlerMapping了,包括rerquestHandlerMapping

<mvc:resources mapping="/img/*"location="/img/"/> 标签也会向spring容器中加入SimpleUrlHandlerMapping,使用完之后,我们需要显式配置rerquestHandlerMapping才能正常使用RequestMapping

常见注解解释
@RequestParam
要绑定到的请求参数的名称。
@RequestBody
是否需要正文内容。
Default为true,在没有正文内容的情况下会引发异常。
如果您希望在body内容为空时传递null,则将此值切换为false
@ResponseBody
指示方法返回值的注释应该绑定到web响应体。支持带注释的处理程序方法。
从4.0版本开始,还可以在类型级别添加此注释,在这种情况下,它是继承的,而不需要在方法级别添加。
注解驱动<mvc:annotation-driven>标签

这么复杂繁琐的配置,是不是看上去有点头大?Spring是个”暖男”,将上述配置浓缩成了一个简单的配置标签,那就 是mvc的注解驱动,“该标签内部会帮我们注册RequestMappingHandlerMapping、注册 RequestMappingHandlerAdapter并注入Json消息转换器等,上述配置就可以简化成如下:

xml
 <!--mvc注解驱动-->
<mvc:annotation-driven/>
<!--配置DefaultServletHttpRequestHandler-->
<mvc:default-servlet-handler/>

PS:<mvc:annotation-driven>标签在不同的版本中,帮我们注册的组件不同,Spring3.o.X版本注册是 DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter,由于框架的发展,从Spring3.1.X 开始注册组件变为RequestMappingHandlerMapping和RequestMappingHandlerAdapter

SpringMVC的响应处理

传统同步业务数据响应

Spring的接收请求的部分我们讲完了,下面在看一下Spring怎么给客户端响应数据,响应数据主要分为两大部分: 传统同步方式:准备好模型数据,在跳转到执行页面进行展示,此方式使用越来越少了,基于历史原因,一些日项目还在使用 前后端分离异步方式:前端使用Ajax技术+Restful风格与服务端进行Json格式为主的数据交互,自前市场上几乎都是此种方式了。

传统同步业务在数据响应时,SpringMVC又涉及如下四种形式:

  • 请求资源转发

    java
    @RequestMapping("/index")
    public String index(){
        return "forward:/index.jsp";
    }
    forward可以省略
  • 请求资源重定向

    java
    @RequestMapping("/index")
    public String index(){
        return "redirect:/index.jsp";
    }
  • 响应模型数据;

  • 直接回写数据给客户端

前后端分离异步业务数据响应

其实此处的回写数据,跟上面回写数据给客户端的语法方式一样,只不过有如下一些区别:

  • 同步方式回写数据,是将数据响应给浏览器进行页面展示的,而异步方式回写数据一般是回写给Aax引擎的,即谁访问服务器端,服务器端就将数据响应给谁
  • 同步方式回写的数据,一般就是一些无特定格式的字符串,而异步方式回写的数据大多是Json格式字符串

回写普通数据使用@ResponseBody标注方法,直接返回字符串即可,此处不在说明 回写Json格式的字符串,即将直接拼接Json格式的字符串或使用工具将JavaBean转换成Json格式的字符串回写

配置消息转换器

xml
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
        </list>
    </property>
</bean>

@RestController注解=@ResponsBody+@RequestMapper

SpringMVC的拦截器

-拦截器Interceptor简介 SpringMVC的拦截器Interceptor规范,主要是对Controller资源访问时进行拦截操作的技术,当然拦截后可以进行权限控制,功能增强等都是可以的。拦截器有点类似Javaweb开发中的Filter,拦截器与Filter的区别如下图:

实现了Handlerlnterceptor接口,且被Spring管理的Bean都是拦截器。

拦截器三个方法的执行顺序

SpringMVC的全注解开发

spring-mvc.xml中组件转化为注解形式

<mvc:annotation-driven>、<mvc:default-servlet-handler/>和<mvc:interceptor>怎么办呢?SpringMvC提供了一个注解

@EnableWebMvc,我们看一下源码,内部通过@Import导入了DelegatingWebMvcConfiguration

配置取代default-servlet-handler和拦截器

DispatcherServlet加载核心配置类

现在是使用SpringMVCConfig核心配置类提替代的spring-mvc.xml,怎么加载呢?参照Spring的 ContextLoaderListener加载核心配置类的做法,定义了一个AnnotationConfigWebApplicationContext,通过代码注册核心配置类

消除web.xml
  • Servlet3.O环境中,web容器提供了javax.servlet.ServletContainerlnitializer接口,实现了该接口后,在对应的类加载路径的META-INF/services目录创建一个名为javax.servlet.ServletContainerlnitializer的文件,文件内容指定具体的ServletContainerlnitializer实现类,那么,当web容器启动时就会运行这个初始化器做一些组件内的初始化工作;

  • 基于这个特性,Spring就定义了一个SpringServletContainerlnitializer实现了ServletContainerlnitializer接口

  • 而SpringServletContainerlnitializer会查找实现了WebApplicationlnitializer的类,Spring又提供了一个 WebApplicationlnitializer的

    基础实现类AbstractAnnotationConfigDispatcherServletlnitializer,当我们编写类继承AbstractAnnotationConfigDispatcherServletlnitializer时,容器就会自动发现我们自己的类在该类中我们就可以配置Spring和SpringMVC的入口了。

spring-web依赖实现了这种xml

image-20240914031300696

我们去看看她写的:

image-20240914031410485

image-20240914031519201

我们发现他配置的接口是WebApplicationInitializer,我们只要实现这个接口就能解析,只要这个接口的实现类都可以被他加载

image-20240914031823840

我们随便选一个子类去继承就好了,下面是一套配置:

SpringMVC的异常处理机制

SpringMVC异常的处理流程

异常分为编译时异常和运行时异常,编译时异常我们try-cache进行捕获,捕获后自行处理,而运行时异常是不 可预期的,就需要规范编码来避免,在SpringMVC中,不管是编译异常还是运行时异常,都可以最终由 SpringMVc提供的异常处理器进行统一处理,这样就避免了随时随地捕获处理的繁琐性。 当然除了繁琐之外,我们在进行前后端分离异步开发时,往往返回统一格式的结果给客户端,例如: [“code”:20o,“message”:"""data”:{“username”:“haohao""age”:null}},即使报异常了,也不能把状态码5oo直接扔给客户端丢给用户,需要将异常转换成符合上面格式的数据响应给客户端更友好。

image-20240914032439630

SpringMVC的异常处理方式

SpringMVC提供了以下三种处理异常的方式:

  • 简单异常处理器:使用SpringMVC内置的异常处理器处理SimpleMappingExceptionResolver

image-20240914033351952

把这对象放到spring容器中就好了

或者xml配置

xml
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="defaultErrorView" value="/defaultError.html"/>
    <property name="exceptionMappings">
        <props>
            <prop key="java.lang.RuntimeException">/errPage.html</prop>
        </props>
    </property>
</bean>
  • 自定义异常处理器:实现HandlerExceptionResolver接口,自定义异常进行处理

image-20240914034506588

  • 注解方式:使用@ControllerAdvice+@ExceptionHandler来处理。

image-20240914034656373

SpringMVC常用的异常解析器

image-20240914034848180

如何解决SpringBoot中没有主manifest属性的问题

Solution: SpringBoot has no main manifest attribute 解决方案:SpringBoot没有main manifest属性 问题:SpringBoot打包到jar中运行后,提示没有主manifest属性

Solution: Complete the maven bulid information 解决方案:完成maven bulid信息

<plugin>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-maven-plugin</artifactId>
	<version>2.6.7</version>
	<executions>
		<execution>
			<goals>
				<goal>repackage</goal>
			</goals>
		</execution>
	</executions>
</plugin>

理由:

因为我使用了spring-boot-dependenciesBOM而不是spring-boot-starter-parent父POM(参见13.2了解详细信息)。2.使用Spring靴子而不使用父POM)

导致spring-boot-maven-plugin的配置项丢失,导致打包jar中的MANIFEST.MF文件缺少Main-Class。

PS:当打包类型为jar时,spring-boot-maven-plugin的打包过程与直接使用maven-jar-plugin完全不同。生成的jar目录结构也非常不同。。

最新解决:

配置打包哦插件

xml
<plugins>
    <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>1.4.2.RELEASE</version>
        <configuration>
        <mainClass>top.maluyao.App</mainClass>
            <skip>false</skip>
        </configuration>
        <executions>
            <execution>
                <id>repackage</id>
                <goals>
                    <goal>repackage</goal>
                </goals>
            </execution>
        </executions>
        </plugin>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
    </plugin>
</plugins>

这样需要确保在打包阶段会自动调用spring-boot-maven-plugin这个插件

image-20250405213501599

这样打包结果就不会出错了,而且生成了一个.origin的文件

image-20250405213624267

查看结果:

image-20250405213729103

明显和我们想的不一样

注意

错误排查:遇到maven报错,不光要查看项目中配置的java环境,而且要保证全局的环境变量的java环境,因为maven打包的时候用的其实是全局的java

java -version

spring boot文件上传与下载

在Spring Boot中实现文件上传和下载通常涉及到使用Spring MVCMultipartFile类来处理文件上传,同时配置一个Controller来处理上传和下载请求。

在Spring Boot中实现文件上传和下载通常遵循以下步骤:

配置文件上传位置和大小限制: 首先,需要在Spring Boot应用的配置文件(如application.properties)中配置文件上传的位置和大小限制。这通常涉及设置spring.servlet.multipart相关属性。你可以指定上传文件的最大大小、临时文件路径等。

创建文件上传表单页面: 在前端创建一个包含文件上传表单的页面,通常使用HTML和表单标签来实现。表单中必须有一个input标签的type属性为file,用户通过这个输入框选择要上传的文件。

处理文件上传请求: 在后端创建一个Controller来处理文件上传请求。这个Controller需要能够接收上传的文件,并将文件保存到指定的位置。在Spring Boot中,你可以使用@RequestParam注解来接收MultipartFile对象,然后使用其transferTo()方法将文件保存到目标位置。

提供文件下载接口: 创建另一个Controller来处理文件下载请求。这个Controller需要能够根据文件名或其他标识符找到对应的文件,并将其内容以流的形式返回给客户端。在Spring Boot中,你可以使用ResponseEntity来封装文件的响应,并设置适当的HTTP头部,使浏览器能够下载文件。

二、详细操作步骤

文件上传步骤: 创建上传目录: 在服务器上创建一个目录,用于存储上传的文件。确保这个目录具有足够的权限,以便应用程序可以向其中写入文件。

配置文件上传属性: 在application.properties文件中配置文件上传的属性,包括上传目录的路径、文件大小限制等。

properties
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB

创建文件上传表单: 在前端页面中创建一个表单,允许用户选择要上传的文件。

html
<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="file">
    <button type="submit">Upload</button>
</form>

4.处理文件上传请求: 创建一个Controller来处理文件上传请求,并将文件保存到指定的位置。

import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;

@RestController
public class FileUploadController {
@PostMapping("/upload")
public String handleFileUpload(@RequestParam("file") MultipartFile file) {
    if (file.isEmpty()) {
        return "Please select a file to upload.";
    }
    try {
        byte[] bytes = file.getBytes();
        String uploadDir = "/path/to/upload/directory/";
        File uploadedFile = new File(uploadDir + file.getOriginalFilename());
        file.transferTo(uploadedFile);
        return "File uploaded successfully!";
    } catch (IOException e) {
        e.printStackTrace();
        return "File upload failed!";
    }
}

文件下载步骤: 创建文件下载接口: 创建一个Controller来处理文件下载请求,并根据文件名找到对应的文件,并将其内容以流的形式返回给客户端。

import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;

@Controller
public class FileDownloadController {
private static final String FILE_DIRECTORY = "/path/to/upload/directory/";
 
@GetMapping("/download/{fileName:.+}")
@ResponseBody
public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) {
    Path filePath = Paths.get(FILE_DIRECTORY).resolve(fileName).normalize();
    try {
        Resource resource = new org.springframework.core.io.UrlResource(filePath.toUri());
        if (resource.exists() || resource.isReadable()) {
            return ResponseEntity.ok()
                    .contentType(MediaType.APPLICATION_OCTET_STREAM)
                    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
                    .body(resource);
        } else {
            return ResponseEntity.notFound().build();
        }
    } catch (IOException ex) {
        ex.printStackTrace();
        return ResponseEntity.status(500).build();
    }
}

}

三、前后端交互原理解释

<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="file">
    <button type="submit">Upload</button>
</form>

在这里对代码进行详细的注释:

客户端实现发送一个 POST 请求到 /upload 路径通常是通过浏览器中的 HTML 表单来完成的。在你提供的 HTML 代码中,已经有一个表单,它会将文件上传到服务器。

让我们解释一下这段 HTML 代码:

  • <form> 标签定义了一个表单,它将会提交数据到指定的 URL(在 action 属性中定义)。在这里,action 属性的值是 /upload,意味着表单将数据提交给服务器上的 /upload 路径。

  • method 属性定义了表单的提交方法。在这里,设置为 post,表示将使用 POST 请求提交数据。

  • enctype 属性定义了表单数据编码的类型。在这里,设置为 multipart/form-data,用于支持文件上传。

  • <input type="file" name="file"> 定义了一个文件选择输入框,允许用户选择要上传的文件。name 属性设置为 file,这个值与后端控制器方法中的 @RequestParam("file") 对应,用于接收上传的文件。

  • <button type="submit">Upload</button> 定义了一个提交按钮,当用户点击它时,表单将被提交到服务器上的 /upload 路径。

因此,当用户在浏览器中选择一个文件并点击“上传”按钮时,浏览器将会发送一个 POST 请求到服务器上的 /upload 路径,上传文件数据将随请求一起发送到服务器,后端控制器将接收这个请求并处理文件上传逻辑。

这个表单指定了一个提交目标为/upload的POST请求,并设置了enctype属性为multipart/form-data,以支持文件上传。

当用户在浏览器中选择要上传的文件并点击“上传”按钮时,浏览器将选定的文件作为HTTP请求的一部分发送到服务器。

在服务器端,Spring Boot应用中的FileUploadController接收这个上传请求,并通过@RequestParam(“file”) MultipartFile file注解来接收上传的文件。Spring Boot中的MultipartFile对象代表了上传的文件,你可以通过它来获取文件名、文件类型、文件内容等信息,并使用transferTo()方法将文件保存到服务器的指定位置。

在这个示例中,上传的文件被保存在服务器的指定目录下,具体路径由变量uploadDir指定。

// 代码存储路径 String uploadDir = “/path/to/upload/directory/”; File uploadedFile = new File(uploadDir + file.getOriginalFilename()); file.transferTo(uploadedFile); 所以,上传的文件实际上是存储在服务器的文件系统中,而不是前端。一旦文件被上传到服务器,后端应用就可以对它进行处理,例如存储到数据库中,或者提供下载接口让用户下载该文件。

四、小结 以上内容总结如下:

  1. Spring Boot中实现文件上传和下载:
    • 在Spring Boot中,文件上传和下载通常涉及使用Spring MVC的MultipartFile类处理文件上传,并配置一个Controller来处理上传和下载请求。

2.文件上传步骤:

  • 配置文件上传位置和大小限制。
  • 创建文件上传表单页面,使用HTML表单标签,其中包含一个input标签的type属性为file
  • 后端Controller处理文件上传请求,使用@RequestParam("file") MultipartFile file注解来接收上传的文件,并将文件保存到指定位置。
  1. 文件下载步骤:

    • 创建另一个Controller处理文件下载请求,根据文件名找到对应的文件,并将其内容以流的形式返回给客户端。
  2. 在前端使用Controller作为映射路径:

    • 配置Spring Boot以解析静态资源,并将HTML文件放置在静态资源目录下。
    • 在控制器中定义文件上传路径,使用@GetMapping@RequestMapping注解指定控制器路径作为映射路径。
    • 在HTML表单中使用控制器路径,将控制器路径作为表单的action属性值。

通过以上步骤,你可以在Spring Boot中实现文件上传和下载,并在前端使用控制器作为映射路径,从而建立起清晰的路由结构。

接收参数特性

对于接收参数类型为List<String>,如果接收到的参数只有一个,那么他会自动去除该参数的首尾空格!

这种行为是为了确保参数的一致性,避免因为首尾空格导致的意外错误。但如果您希望保留参数中的首尾空格,可以采取以下几种方法:

  1. 使用自定义参数解析器: 您可以创建一个自定义的参数解析器,在解析参数时不去除首尾空格。

  2. 使用@RequestParam注解的trim属性: 从Spring 5.3开始,@RequestParam注解提供了一个trim属性,允许您控制是否去除参数值的首尾空格。例如:

    @GetMapping("/your-endpoint")
    public ResponseEntity<?> yourMethod(@RequestParam(name = "link", trim = false) List<String> links) {
        // 处理links
    }

    在这个例子中,设置trim = false可以阻止Spring去除参数值的首尾空格。

  3. 手动处理参数: 在控制器方法中手动处理接收到的参数,例如使用String.replaceAll()方法替换掉首尾的空格。

  4. 使用@RequestBody: 如果参数是通过JSON body传递的,可以使用@RequestBody来接收一个对象,而不是使用@RequestParam来接收List<String>。这样,Spring不会自动去除字符串的首尾空格。

  5. log.info(request.getParameter("str"));

请注意,这些方法可能需要根据您的具体情况进行调整。如果问题仍然存在,可能需要进一步调试和检查Spring Boot的配置以及客户端的请求发送方式。

评论

评论加载中……