Spring概述

Spring框架是Java平台的一个用于解决软件开发复杂性的、轻量级的、开源的全栈应用程序框架和控制反转容器

体系结构

核心容器

核心容器(Core Container)由以下模块组成,spring-core, spring-beans,spring-context,spring-context-support,spring-expression (Spring表达式语言)

  • spring-core和spring-beans模块 提供了框架的基础功能,包括IOC和依赖注入功能。 BeanFactory是一个成熟的工厂模式的实现。你不再需要编程去实现单例模式,允许你把依赖关系的配置和描述从程序逻辑中解耦。
  • spring-context(上下文)模块建立在由Core和Beans模块提供的坚实的基础上:它提供一个框架式的对象访问方式,类似于一个JNDI注册表。上下文模块从Beans模块继承其功能,并添加支持国际化(使用,例如,资源集合),事件传播,资源负载,并且透明创建上下文,例如,Servlet容器。Context模块还支持Java EE的功能,如EJB,JMX和基本的远程处理。ApplicationContext接口是Context模块的焦点。 spring-context-support支持整合普通第三方库到Spring应用程序上下文,特别是用于高速缓存(ehcache,JCache)和调度(CommonJ,Quartz)的支持。
  • spring-expression模块 提供了强大的表达式语言去支持查询和操作运行时对象图。这是对JSP 2.1规范中规定的统一表达式语言(unified EL)的扩展。该语言支持设置和获取属性值,属性分配,方法调用,访问数组,集合和索引器的内容,逻辑和算术运算,变量命名以及从Spring的IoC容器中以名称检索对象。 它还支持列表投影和选择以及常见的列表聚合。

AOP和Instrumentation

  • spring-aop模块 提供了一个符合AOP联盟(要求)的面向方面的编程实现,例如,允许您定义方法拦截器和切入点(pointcuts),以便干净地解耦应该被分离的功能实现。 使用源级元数据(source-level metadata)功能,您还可以以类似于.NET属性的方式将行为信息合并到代码中。
  • 单独的spring-aspects模块 ,提供了与AspectJ的集成。
  • spring-instrument模块 提供了类植入(instrumentation)支持和类加载器的实现,可以应用在特定的应用服务器中。该spring-instrument-tomcat 模块包含了支持Tomcat的植入代理。

消息

  • spring-messaging(消息传递模块) 包含来自Spring Integration的项目,例如,Message,MessageChannel,MessageHandler,和其他用来传输消息的基础应用。该模块还包括一组用于将消息映射到方法的注解(annotations),类似于基于Spring MVC注释的编程模型。

数据访问/集成

数据访问/集成层由JDBC,ORM,OXM,JMS,事务模块组成。

  • spring-jdbc模块 提供了一个JDBC –抽象层,消除了需要的繁琐的JDBC编码和数据库厂商特有的错误代码解析。
  • spring-tx模块 支持用于实现特殊接口和所有POJO(普通Java对象)的类的编程和声明式事务 管理。
  • spring-orm模块 为流行的对象关系映射(object-relational mapping )API提供集成层,包括JPA和Hibernate。使用spring-orm模块,您可以将这些O / R映射框架与Spring提供的所有其他功能结合使用,例如前面提到的简单声明性事务管理功能。
  • spring-oxm模块 提供了一个支持对象/ XML映射实现的抽象层,如JAXB,Castor,JiBX和XStream。
  • spring-jms模块(Java Messaging Service) 包含用于生产和消费消息的功能。自Spring Framework 4.1以来,它提供了与 spring-messaging模块的集成。

Web

Web层由spring-web,spring-webmvc,spring-websocket模块组成。

  • spring-web模块提供基本的面向Web的集成功能,例如多部分文件上传功能,以及初始化一个使用了Servlet侦听器和面向Web的应用程序上下文的IoC容器。它还包含一个HTTP客户端和Spring的远程支持的Web相关部分。
  • spring-webmvc模块(也称为Web-Servlet模块)包含用于Web应用程序的Spring的模型-视图-控制器(MVC)和REST Web Services实现。 Spring的MVC框架提供了领域模型代码和Web表单之间的清晰分离,并与Spring Framework的所有其他功能集成

测试

  • spring-test模块 支持使用JUnit或TestNG对Spring组件进行单元测试和 集成测试。它提供了Spring ApplicationContexts的一致加载和这些上下文的缓存。它还提供可用于独立测试代码的模仿(mock)对象。

控制反转(IOC)和依赖注入(DI)

控制反转(Inverse Of Control),这是spring的核心,贯穿始终。所谓IoC,对于spring框架来说,就是由Spring来负责控制对象的生命周期和对象间的关系

IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。
IoC很好的体现了面向对象设计法则之一—— 好莱坞原则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

依赖注入(Dependency Injection) ,是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而 不需要关心具体的资源来自何处,由谁实现

理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

  • 谁依赖于谁: 当然是应用程序依赖于IoC容器;
  • 为什么需要依赖: 应用程序需要IoC容器来提供对象需要的外部资源;
  • 谁注入谁: 很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
  • 注入了什么: 就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

IoC和DI有什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年Martin Fowler又给出了一个新的名字:“依赖注入”并且,在他的文章中进一步解释了控制反转(IoC)和依赖注入(DI)的原则思想。相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。

参考

代码

  • 在之前的学习中,我们都是直接在Service层中直接实例化Dao层并使用,也就是说控制权是在UserServiceImpl手上的
    UserServiceImpl.java
    1
    2
    3
    4
    5
    6
    7
    8
    public class UserServiceImpl implements UserService {

    private UserMapper userMapper = new UserMapperImpl();

    public void register(User user){
    userMapper.register(user);
    }
    }
  • 这样的话,当我们更换了UserMapperImpl,就必须修改UserServiceImpl的代码,将其变更为private UserMapper userMapper = new AnotherUserMapperImpl();
    在这种情况下,我们其实可以为UserServiceImpl提供一个setUserMapper的方法
    UserServiceImpl.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class UserServiceImpl implements UserService {

    private UserMapper userMapper;
    public void setUserMapper(UserMapper userMapper){
    this.userMapper = userMapper;
    }

    public void register(User user){
    userMapper.register(user);
    }
    }
  • 使用这种方式,当我们更换了UserMapperImp,只需要在使用UserServiceImpl之前set一个UserMapper的实现类即可
    此时的UserServiceImpl已经不是主动创建userMapper了,也就是实现了控制反转
    并且,在外部setUserMapper的方式,就可以称为注入
    Spring就是基于这种理念去管理对象,使系统的耦合性大大降低,可以更加专注的在业务实现上。

Spring入门

  • 首先导入spring-context模块或者是spring-webmvc模块
    (spring-context中包含了aop,beans,core,spEl(表达式语言))
    spring-webmvc中包含了spring-context以及spring-web

  • 在资源目录下创建Spring的配置文件(如果你是IDEA,可以直接New - XML Configuration - Spring Config)
    然后在根标签beans下创建一个子标签,其中class对应实体类的全限定名,property子标签对应实体类的setXXX方法

    applicationContext.xml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?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="student" class="com.coderxi.pojo.Student">
    <property name="name" value="张三"/>
    <property name="age" value="18"/>
    </bean>
    </beans>

    如果实体类没有无参构造,会造成报错,可以使用<constructor-arg>替换<property>来使用全参构造注入

  • java代码获取到这个bean对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //通过CPX加载xml文件
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

    //两种方式获取,一种强制转型,一种提供一个class而不必强转
    Student student1 = (Student) context.getBean("student");
    Student student2 = context.getBean("student", Student.class);

    //在默认的情况下,这两个对象的地址是一样的,因为获取<bean>默认是使用单例模式(singleton)
    System.out.printf("%s和%s比较结果:%s",student1,student2,student1==student2);
    //结果为true,地址一样

    在多线程的情况下可以为<bean>设置scope="prototype"来使用原型模式,这样的话,比较获取的对象时,地址是不一样的

<bean>scope属性(作用域)

  • singleton 单例模式
  • prototype 原型模式
  • request
    在Spring容器中,即XmlWebApplicationContext会为每个HTTP请求创建一个全新的Request-Processor对象供当前请求使用,当请求结束后,该对象实生命周期就结束。当同时有10个HTTP请求进来的时候,容器会分别针对这10个请求返回10个全新的RequestProcessor对象实例,且它们之间互不干扰。所以严格意义上说request可以看作prototype的一种特例,除了request的场景更加具体
  • session
    放到session中的最普遍的信息就是用户登录信息,Spring容器会为每个独立的session创建属于它们自己全新的UserPreferences对象实例。与request相比,除了拥有session scope的bean比request scope的bean可能更长的存活时间,其他没什么差别
  • global session
    global session只有应用在基于portlet的Web应用程序中才有意义,它映射到portlet的global范围的session。如果在普通的基于servlet的Web应用中使了用这个类型的scope,容器会将其作为普通的session类型的scope来对待
  • 什么是portlet
    Portlets是一种Web组件-就像servlets-是专为将合成页面里的内容聚集在一起而设计的。通常请求一个portal页面会引发多个portlets被调用。每个portlet都会生成标记段,并与别的portlets生成的标记段组合在一起嵌入到portal页面的标记内。

<import>合并多个Spring配置

在beans加入import子标签可以合并多个配置文件
<import resource="applicationContext2.xml"/>
<import resource="applicationContext3.xml"/>

<context:property-placeholder>加载外部properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="jdbcConfig" class="com.coderxi.config.JdbcConfig">
<property name="diver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="password" value="${jdbc.password}"/>
<property name="username" value="${jdbc.username}"/>
</bean>
</beans>

Spring注解开发

  • 在使用注解之前,需要修改配置文件

    applicationContext.xml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    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
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
    >
    <!--扫描com.coderxi包下所有的组件-->
    <context:component-scan base-package="com.coderxi"/>
    </beans>
  • <context:annotation-config/>使用说明
    如果你想使用@Autowired注解,那么就必须事先在 Spring 容器中声明 AutowiredAnnotationBeanPostProcessor 的 Bean
    如果想使用@Resource @PostConstruct @PreDestroy等注解就必须声明CommonAnnotationBeanPostProcessor
    于是spring给我们提供<context:annotation-config/>的简化配置方式,自动帮你完成声明。
    不过,我们使用注解一般都会配置扫描包路径选项<context:component-scan base-package="XX.XX"/>
    该配置项其实也包含了自动注入上述processor的功能,因此当使用 <context:component-scan/> 后,就可以将 <context:annotation-config/> 移除了。

  • 我们也可以完全不使用xml而使用java类来配置Spring,只需要对你的类使用@Configuration

    com.coderxi.config.MySpringConfig.java
    1
    2
    3
    4
    5
    6
    7
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;

    @Configuration
    @ComponentScan(basePackages = "com.coderxi")
    public class MySpringConfig {
    }

SpringAOP

面向切面编程(AOP)

面向切面编程(Aspect-Oriented Programming),是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任分开封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

AOP 要达到的效果是,保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能 ,(其实就是代理模式的典型应用)。

从该图可以很形象地看出,所谓切面,相当于应用对象间的横切点,我们可以将其单独抽象为单独的模块。

参考

相关概念

术语 解释
Aspect 切面 Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
Joint point 连接点 表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它
Pointcut 切点 表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式
Advice 增强 Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别
Target 目标对象 织入 Advice 的目标对象.。
Weaving 织入 将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程

开始

  • 导入spring-aop包含spring-aop的模块
  • 开启SpringAOP<aop:aspectj-autoproxy/>
    applicationContext.xml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:aop="http://www.springframework.org/schema/aop"
    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
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"
    >
    <!--扫描com.coderxi包下所有的组件-->
    <context:component-scan base-package="com.coderxi"/>
    <!--开启SpringAOP-->
    <aop:aspectj-autoproxy/>
    </beans>

通知类型

类型 注解 功能
前置通知 @Before 该通知织入在方法调用之前。
异常返回通知 @AfterThrowing 该通知织入在方法抛出异常之后。
正常返回通知(后置通知) @AfterReturning 在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。
(最终)返回通知 @After 在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。
环绕通知 @Around 环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行。

使用

  • 业务实现类
    com.coderxi.service.impl.UserServiceImpl.java
    1
    2
    3
    4
    5
    6
    @Service
    public class UserServiceImpl implements UserService {
    public void register(User user){
    System.out.println("注册:"+user);
    }
    }
  • 切片类
    1
    2
    3
    4
    5
    6
    7
    8
    @Aspect
    @Component
    public class Aspect {
    @Before("execution(* com.coderxi.service.impl.*ServiceImpl.*(..))")
    public void before(JoinPoint point){ //也可以省略JoinPoint point,这里是为了打印方法名
    System.out.println("开始执行"+point.getSignature().getName()+"操作!");
    }
    }
  • 现在,当我们执行业务实现类的任何方法的时候,都会执行这个before方法
  • 除了环绕通知(Around)以外,其他通知基本一致

环绕通知(Around)

1
2
3
4
5
6
7
8
9
10
11
12
@Aspect
@Component
public class Aspect throws Throwable {
@Around("execution(* com.coderxi.service.impl.*ServiceImpl.*(..))")
public Object before(ProceedingJoinPoint point) throws Throwable {
System.out.println("开始执行"+point.getSignature().getName()+"操作!");
Object result = point.proceed();
System.out.println("完成执行"+point.getSignature().getName()+"操作!");
System.out.println("执行结果:"+result);
return point;
}
}

Spring Mybatis整合

MyBatis-Spring

MyBatis-Spring是MyBatis的一个社区子项目,用于与Spring的集成。
Maven仓库地址官方文档

使用

  • 可以新创建一个xml用于专门配置mybatis
    在applicationContext下使用<import resource="spring-mybatis.xml"/>导入这个xml文件即可
  • 第一步需要先获取到dataSource数据源
    spring-mybatis.xml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <context:property-placeholder location="classpath:mybatis/jdbc.properties"/>
    <!--Druid配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    <property name="initialSize" value="${initialSize}"/>
    <property name="maxActive" value="${maxActive}"/>
    <property name="minIdle" value="${minIdle}"/>
    <property name="maxWait" value="${maxWait}"/>
    </bean>
  • 在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。
    spring-mybatis.xml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!--配置数据源-->
    <property name="dataSource" ref="dataSource"/>
    <!--导入mybatis-config.xml(原配置文件)-->
    <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
    <!--配置实体类别名包-->
    <property name="typeAliasesPackage" value="com.coderxi.pojo"/>
    <!--mapper文件路径-->
    <property name="mapperLocations" value="classpath:mybatis/mapper/*Mapper.xml"/>
    </bean>
    更多SqlSessionFactoryBean的属性配置可以参考官网
  • 现在的mybatis-config.xml,比以前干净了很多
    mybatis-config.xml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" >
    <configuration>
    <settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"/>
    </plugins>
    </configuration>
  • 最后,配置一下MapperScannerConfigurer
    spring-mybatis.xml
    1
    2
    3
    4
    5
    6
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!--映射接口的包,这个包里面的所有的接口都会被扫描到-->
    <property name="basePackage" value="com.coderxi.mapper"/>
    <!--将我们的sqlSessionFactory注入到这个类-->
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>
  • 完活!

spring系统环境变量问题

如果使用了context:property-placeholder来读取jdbc.properties而jdbc.properties中使用了username=...来配置用户名,spring默认会优先加载系统环境变量,此时获取到的username的值实际上指的是当前计算机的用户名。而不是properties配置文件中指定的username的值。
解决方式

  1. (推荐)修改配置文件中username的key,避免与系统当前用户的username冲突
    1
    jdbc.username=...
  2. 采用本地配置覆盖系统的配置local-override="true"
    1
    <context:property-placeholder location="classpath:mybatis/jdbc.properties" local-override="true"/>
  3. 使用已过时的PreferencesPlaceholderConfigurer读取
    1
    2
    3
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
    <property name="location" value="classpath:jdbc.properties"/>
    </bean>

fastjson的坑

返回的json数据前台页面报406,而后台没有报错,原因是使用了fastjson而未使用jackson来解析json
解决方式:

1
2
3
4
5
6
7
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes" value="text/html;charset=UTF-8"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>

SpringMVC

生命周期

graph LR C[客户端<br>Client] --发起请求--> DS[前端控制器<br>DispatcherServlet] --"请求查找处理器Handler"--> HM[处理器映射器<br>HandlerMappering] --"返回执行链HandlerExcutionChain {<br>HandlerInterceptor(拦截器)<br>Handler(处理器) }"--> DS --"请求执行处理器Handler"--> HA[处理器适配器<br>HandlerAdapter] --执行Handler返回ModelAndView--> DS --请求解析ModelAndView--> VR[视图解析器<br>ViewResolver] --返回视图View--> DS --对视图进行渲染,响应结果--> C

流程详解

SpringMvc流程

数据校验

注解 说明
@Null 验证对象是否为null
@NotNull 验证对象是否不为null
@NotEmpty 验证对象是否不为null且不为empty
@Size 验证对象(数组、集合、字符串)长度是否在范围之内
@Length 验证字符串长度是否在范围之内
@Min 验证Number和String对象是否大于指定的值
@Max 验证Number和String对象是否小于指定的值
@Email 验证是否为邮箱地址,如果为null,不进行验证
@Pattern 验证字符串是否符合正则表达式规则

异常处理

1
2
3
4
5
6
7
@Component
public class CustomExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
return new ModelAndView("error").addObject("ex",ex);
}
}

SSM整合

最终整理的配置文件:点击下载

文件上传下载

  • 贴个工具类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    import org.springframework.web.multipart.MultipartFile;

    import javax.servlet.http.HttpServletResponse;
    import java.io.*;
    import java.net.URLEncoder;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.UUID;

    public class FileUtils {

    private static ServletRequestAttributes getServletCurrentRequestAttributes(){
    return (ServletRequestAttributes)RequestContextHolder.currentRequestAttributes();
    }

    public static String uploadFile(MultipartFile file) throws IOException {
    if(file==null||file.getSize()<1) return null;
    String realPath = getServletCurrentRequestAttributes().getRequest().getServletContext().getRealPath("");
    String extension =file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
    String newFileName= UUID.randomUUID()+extension;
    File newFile = new File(realPath+"/upload/"+newFileName);
    if(!newFile.exists()){
    newFile.getParentFile().mkdirs();
    newFile.createNewFile();
    }
    file.transferTo(newFile);
    return "/upload/"+newFileName;
    }

    public static void downloadFile(String filepath) throws IOException {
    File file = new File(filepath);
    HttpServletResponse response = getServletCurrentRequestAttributes().getResponse();
    response.setHeader("Content-disposition","attachment;filename="+ URLEncoder.encode(file.getName(),"utf-8"));
    try(BufferedInputStream input = new BufferedInputStream(new FileInputStream(getServletCurrentRequestAttributes().getRequest().getSession().getServletContext().getRealPath(filepath)));
    BufferedOutputStream output = new BufferedOutputStream(response.getOutputStream())){
    byte[] buffer=new byte[8192];
    for (int read = 0;(read=input.read(buffer,0,8192)) != -1;)output.write(buffer,0,read);
    }
    }

    }