Welcome to Song Wei's blog


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

java类加载机制

发表于 2018-03-14 |

1.什么是类的加载

类的加载是指将编译完成的字节码文件即class文件中的二进制数据读入内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

2.什么时候进行类加载

jvm可以实现预加载功能,类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范规定JVM可以预测加载一个类,如果这个类出错,但是应用程序没有调用这个类, JVM也不会报错;如果调用这个类的话,JVM才会报错。

3.类的生命周期

前五个阶段为类的加载过程,其中加载、验证、准备、初始化四个阶段开始顺序是确定的,而解析阶段可能发生在初始化之后,这是为了支持java动态绑定。而前五个阶段是按顺序开始的,通常这些阶段会交叉进行,通常在一个阶段执行过程中调用或激活另一个阶段。

3.1加载

JVM需要完成三件事情

  • 类加载器通过类的全路径限定名读取类的二进制字节流
  • 将二进制字节流代表的类结构转化到运行时数据区的方法区中
  • 在堆中生成代表这个类的java.lang.Class实例,作为对方法区数据访问入口

3.2验证

加载和验证是交叉进行的,验证的主要目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

  • 文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
  • 元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。
  • 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
  • 符号引用验证:确保解析动作能正确执行。

验证阶段非常重要但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

3.3准备

准备是为类静态变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。

  • 进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。
  • 所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。

假设一个类变量的定义为:public static int i = 2;
i在准备阶段过后的初始值为0,而不是2,因为这时候尚未开始执行任何Java方法,而把value赋值为2的public static指令是在程序编译后,存放于类构造器()方法之中的,所以把i赋值为2的动作将在初始化阶段才会执行。

  • 如果类字段的字段属性即同时被final和static修饰,那么在准备阶段变量value就会被初始化为属性所指定的值。

类变量i被定义为: public static final int i = 3;
编译时Javac将会为i生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为3。

3.4解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。
直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

3.5初始化

初始化类的静态变量和静态代码块为用户自定义的值,在类被Java程序“第一次主动使用”的时候,才会触发初始化操作。其中包括:

  • new了一个类的对象
  • main方法所在类
  • 调用了类的静态成员和静态方法
  • 修改类的静态成员的值
  • 使用java.lang.reflect包对类进行反射调用
  • 初始化子类时,父类未被调用,则先初始化父类

4.类加载器

JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系如下:

继承关系可通过以下程序验证:

1
2
3
4
5
6
7
8
public class Main{
public static void main(String[] args) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
System.out.println(loader);
System.out.println(loader.getParent());
System.out.println(loader.getParent().getParent());
}
}

输出

sun.misc.Launcher$AppClassLoader@135fbaa4
sun.misc.Launcher$ExtClassLoader@2503dbd3
null

注: ExtClassLoader的父类为null,是因为BootStrap ClassLoader使用C语言实现的。

  • Bootstrap ClassLoader 负责加载$JAVA_HOME中 jre/lib/rt.jar 里所有的class或Xbootclassoath选项指定的jar包。由C++实现,不是ClassLoader子类。

  • Extension ClassLoader 负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar 或 -Djava.ext.dirs指定目录下的jar包。

  • App ClassLoader 负责加载classpath中指定的jar包及 Djava.class.path 所指定目录下的类和jar包。

  • User ClassLoader 通过java.lang.ClassLoader的子类自定义加载class,属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader。

JVM类加载机制

  • 全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
  • 父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
  • 缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效

双亲委派模型

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

  1. 当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
  2. 当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader```去完成。
  3. 如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
  4. 若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

结束生命周期
在如下几种情况下,Java虚拟机将结束生命周期

  1. 执行了System.exit()方法
  2. 程序正常执行结束
  3. 程序在执行过程中遇到了异常或错误而异常终止
  4. 由于操作系统出现错误而导致Java虚拟机进程终止

Spring访问多个不同数据库

发表于 2018-03-12 |

在使用Spring开发时,配置单一数据库是非常容易的,但同时访问多个数据库不可避免,本文使用Spring boot简化配置多数据库源。

搭建两个数据库

  • Mysql5.7
1
2
3
4
5
6
7
8
9
CREATE DATABASE mysqlsample;
USE DATABASE mysqlsample;
CREATE TABLE usermaster (
id int(11) NOT NULL PRIMARY KEY ,
name varchar(255) DEFAULT NULL,
email varchar(20) DEFAULT NULL,
phone varchar(20) DEFAULT NULL
);
INSERT INTO usermaster VALUE('1','tom','mysql@mysql.com','1234567890')
  • PostgreSQL 10
1
2
3
4
5
6
7
8
CREATE TABLE usermaster (
id integer PRIMARY KEY ,
name character varying,
email character varying,
phone character varying(10)
);
INSERT INTO usermaster(id, name, email, phone)
VALUES (1, 'john', 'postgresql@postgresql.com', '1234567890');

使用IDEa搭建项目

pom.xml如下

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>demo</name>
<description>Demo project for Spring Boot</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>


</project>

application.properties如下

1
2
3
4
5
6
7
8
9
server.port=8080
#指定第一个数据源postgresql
first.datasource.jdbc-url=jdbc:postgresql://localhost:5432/postgres
first.datasource.driver-class-name=org.postgresql.Driver
#指定第二个数据源mysql
second.datasource.jdbc-url = jdbc:mysql://localhost:3306/mysqlsample
second.datasource.username = root
second.datasource.password = Tyrion568&
second.datasource.driver-class-name=com.mysql.jdbc.Driver

first.datasource为PostgreSQL定义的属性
second.datasource为mysql定义的属性

DBConfig.java

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
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

/**
* @author Song Wei
* @description
* @date 12/03/2018 14:47
*/
@Configuration
public class DBConfig {

@Bean(name = "mysqlDB")
@Primary
@ConfigurationProperties(prefix = "second.datasource")
public DataSource mysqlDataSource() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean(name = "mysqlJdbcTemplate")
@Autowired
public JdbcTemplate jdbcTemplate(@Qualifier("mysqlDB") DataSource dsMySQL) {
return new JdbcTemplate(dsMySQL);
}

@Bean(name = "postgresDB")
@ConfigurationProperties(prefix = "first.datasource")
public DataSource postgresDataSource() {
return DataSourceBuilder.create().build();
}

@Bean(name = "postgresJdbcTemplate")
@Autowired
public JdbcTemplate postgresJdbcTemplate(@Qualifier("postgresDB") DataSource dsPostgres) {
return new JdbcTemplate(dsPostgres);
}
}

第一个Bean注解创建mysqlDB bean
第二个ConfigurationProperties注解加载所有前缀为spring.ds_mysql的属性
随后创建并初始化DataSource并创建了MysqlDB DataSource对象
Qualifier以创建的mysqlDB为qualifier并用DataSource对象初始化JdbcTemplate实例
随后初始化postgresql template

DemoController.java

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
44
45
46
47
48
49
50
51
package com.example.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
* @author Song Wei
* @description
* @date 12/03/2018 14:59
*/
@RestController
public class DemoController {

@Autowired
@Qualifier("postgresJdbcTemplate")
private JdbcTemplate postgresTemplate;

@Autowired
@Qualifier("mysqlJdbcTemplate")
private JdbcTemplate mysqlTemplate;

@GetMapping(value = "/getPostgresUser")
public String getPostgreUser(){
Map<String, Object> map = new HashMap();
String query = "select * from usermaster";
try {
map = postgresTemplate.queryForMap(query);
} catch (Exception e) {
e.printStackTrace();
}
return "PostgreSQL : " + map.toString();
}
@RequestMapping(value = "/getMysqlUser")
public String getMysqlUser() {
Map<String, Object> map = new HashMap();
String query = "select * from usermaster";
try {
map = mysqlTemplate.queryForMap(query);
} catch (Exception e) {
e.printStackTrace();
}
return "MySQL : " + map.toString();
}
}

运行程序访问地址
URL1:http://localhost:8080/getPostgresUser
访问mysql

URL2:http://localhost:8080/getPostgresUser
访问PostgreSQL

java中的null

发表于 2018-03-11 |

1.null是java中的关键字,像public、static、void。大小写敏感,Null或NULL都是错误的,编译器不能识别。

1
2
Object object = Null; //wrong
Object object = null; //ok

2.在java中的每一种基本类型都有默认值,int默认值为0,boolean默认值是false,任何引用类型变量的默认值都是null(所有变量都是如此,如成员变量、局部变量、实例变量、静态变量)。

1
2
3
4
private static Object object;
public static void main(String args[]){
System.out.print("The value of object is:" + object);
}

输出:The value of object is:null

3.null既不是对象也不是类型,而仅仅是一种特殊值,可以将null赋予任何引用类型,也可以将null转换成任何类型。

1
2
3
4
5
6
7
String s = null; //ok
Float f = null; //ok
Object o = null; //ok

String s = (String)null; //ok
Float f = (Float)null; //ok
Object o = (Object)null; //ok

4.null可以赋值给给引用变量,你不能将null赋给基本类型变量,例如int、double、float、boolean。如果你那样做了,编译器将会报错。但是如果将null赋值给包装类object,然后将object赋给各自的基本类型,编译器不会报,但是你将会在运行时期遇到空指针异常,这是Java中的自动拆箱导致的。

1
2
3
4
5
6
int i = null; //wrong
float f = null; //wrong
double d = null; //wrong

Integer i = null; //ok
int j = i; //ok

5.任何含有null值的包装类在Java拆箱生成基本数据类型时候都会抛出一个空指针异常。在使用HashMap和Integer键值的时候会发生很多这样的错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.HashMap;
import java.util.Map;

public class Test {
public static void main(String args[]) throws InterruptedException {
Map numberAndCount = new HashMap<>();
int[] numbers = {3, 5, 7,9, 11, 13, 17, 19, 2, 3, 5, 33, 12, 5};

for(int i : numbers){
int count = numberAndCount.get(i);
numberAndCount.put(i, count++); // NullPointerException here
}
}
}

输出:Exception in thread “main” java.lang.NullPointerException
at Test.main(Test.java:12)

6.如果使用了带有null值的引用类型变量,instanceof操作将会返回false

1
2
3
4
5
6
7
8
9
10
11
public class Main{
public static void main(String[] args){
Integer i = null;
if(i instanceof Integer){
System.out.println("i is instance of Integer");

}else{
System.out.println("i is not an instance of Integer");
}
}
}

输出:i is not an instance of Integer

7.值为null的引用类型变量调用非静态方法会抛出空指针异常,但是值为null的引用类型变量可以调用静态方法而不会抛出异常,因为静态方法使用静态绑定,不会抛出空指针异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Main{
public static void main(String[] args){
Main m = null;
m.staticMethod();
m.noneStaticMethod();
}
private static void staticMethod(){
System.out.println("static can be called by null reference");
}
private void noneStaticMethod(){
System.out.println("none static can not be called by null reference");
}
}

I am static method, can be called by null reference
Exception in thread “main” java.lang.NullPointerException
at Testing.main(Testing.java:4)

注:java1.8及其以上已经禁止实例变量调用静态方法

8.可以将null传递给方法使用,这时方法可以接收任何引用类型,例如public void print(Object obj)可以这样调用print(null)。从编译角度来看这是可以的,但结果完全取决于方法。Null安全的方法,如在这个例子中的print方法,不会抛出空指针异常,只是优雅的退出。如果业务逻辑允许的话,推荐使用null安全的方法。

9.可以使用==或者!=操作来比较null值,但是不能使用其他算法或者逻辑操作,例如小于或者大于。跟SQL不一样,在Java中null==null将返回true。

1
2
3
4
5
6
7
8
9
10
11
public class Main{
public static void main(String[] args) {
Object object1 = null;
Object object2 = null;
if (object1 == object2){
System.out.println("null == null is true");
}else {
System.out.println("null == null is false");
}
}
}

输出:null == null is true

codeoptimization

发表于 2018-03-07 |

java集合关系总结图

发表于 2018-03-07 |

java集合关系

linux grep命令

发表于 2018-03-07 |
  1. 简介
1
grep(Global Search Regular Expression And Print Out The Line)是Linux,Unix文本搜索工具,利用正则表达式匹配搜索文本,并把匹配的行打印出来
  1. 格式
1
grep 参数 files
  1. 主要参数
1
2
3
4
5
6
7
8
9
10
|参数|描述|
|:-:|:-:|
|-i|不区分大小写|
|-c|只输出匹配行的数目|
|-n|显示匹配行及行号|
|-s|不显示错误信息|
|-v|反向匹配,显示不匹配的行|
|-l|查询多文件时候只输出包含匹配字符的文件名|
|-r|递归查找子目录|
|--color|将关键字加上颜色|
  1. 常用正则表达式
1
2
3
4
5
6
7
8
9
|参数|描述|
|:-:|:-:|
|\|反义字符|
|^$|开始和结束|
|[]|单个字符|
|-|匹配范围字符|
|*|前面字符出现零次或者多次|
|+|前面字符出现一次或者多次|
|.|任意字符|
  1. 使用场景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
**不区分大小写,加上-i来忽略大小写**

* 在文本中搜索一个单词
grep pattern filename
在filename文件中查找所有匹配pattern的单词`
* 在多个文件中查找
grep pattern filename1 filename2 ...
在多个文件中查找所有匹配pattern的单词`
* 使用-v反向输出,显示不匹配的行
grep -v pattern filename
* 标记匹配颜色
grep pattern filename --color
* 使用-c统计个数
grep -c pattern filename
* 使用-r递归查找子目录
grep -r pattern directory
directory .表示当前目录

mysql中DATE_FORMAT用法

发表于 2018-01-07 |

DATE_FORMAT(date,format)
根据fromat字符串参数格式化日期
format参数需加百分号表示参数
例:

1
2
SELECT DATE_FORMAT('2017-1-7 10:45:23','%Y-%c-%e');
2017-1-7

参数 描述
%a 周英文缩写(Sun,Mon..Sat)
%b 月数英文缩写(Jan,Feb..Dec)
%c 月数数字(0,1..12)
%D 带英文尾缀的一月中天数(0th,1st,2nd,3rd..)
%d 一月中天数数字格式(00..31)
%e 一月中天数数字格式(0..31)
%f 日期的毫秒数(000000..999999)
%H 一天中小时数24小时制(00..23)
%h 一天中小时数12小时制(01..12)
%I 一天中小时数12小时制(01..12)
%i 一小时的分钟数(00..59)
%j 一年中天数(001..366)
%k 一天中小时数24小时制(0..23)
%l 一天中小时数12小时制(1..12)
%M 月数英文全称(January..December)
%m 月数数字(00..12)
%p AM PM
%r 12小时制时间(hh:mm:ss加AM PM)
%S 秒数(00..59)
%s 秒数(00..59)
%T 24小时制时间(hh:mm:ss)
%U 一年中周数(00..53),周日是一周的头一天
%u 一年中周数(00..53),周一是一周的头一天
%W 一周天数英文全称(Sunday..Saturday)
%w 一周天数数字(0=Sunday..6=Saturday)
%Y 年数,四位数(2017)
%y 年数,二位数(17)
1
2
3
4
5
6
7
8
9
10
按年统计
select 列 from 表 group by date_format(列,'%Y');
按月统计
select 列 from 表 group by date_format(列,'%Y-%c');
按周统计
select 列 from 表 group by date_format(列,'%Y-%u');
按天统计
select 列 from 表 group by date_format(列,'%Y-%c-%e');
按小时统计
select 列 from 表 group by date_format(列,'%Y-%c-%e %H');

centos7 yum错误cannot find a valid baseurl for repo:base/7/x86_64

发表于 2017-12-18 |

问题主要原因是无法联网,解决方法

方法1

  • vim /etc/sysconfig/network-scripts/ifcfg-eth0(端口可能会不一致,数字会有变化)
  • onboot=no改为onboot=yes
  • 重启网络 service network restart

方法2

  • vim /etc/resolv.conf
  • 添加nameserver 8.8.8.8
  • 重启网络 service network restart

linux shutdown命令

发表于 2017-12-14 |

shutdown命令

参数 含义用法
-t 参数后加秒数,代表过几秒后关机
-k 并不关机,只是发送警告消息
-r 在关掉系统服务之后 重启
-h 在关掉系统服务之后 关机
-n 不经过init程序,直接以shutdown来关机
-f 关机重启后,强制略过fsck磁盘检查
-F 关机重启后,强制进行fsck磁盘检查
-c 取消已经在进行的shutdown命令内容
时间 指定系统关机时间

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
shutdown -h now
立刻关机,now相当于时间为0

shutdown -h +20
在二十分钟后关机

shutdown -r +20
在二十分钟后重启

shutdown -k now 'the system will rebot'
系统不会关闭,只是发消息给所有用户通知

shutdown -h 20:20
在晚上20:20关机

注:
除使用图形界面,可以以任何身份关机
其余例如远程ssh服务都需要root权限

spring boot实现文件上传下载

发表于 2017-12-08 |

前端

前端使用html的form表单实现文件上传
method设置为post,并将enctype设置为multipart/form-data
部分html代码:

1
2
3
4
<form th:action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" value="click to upload" />
</form>

/upload对应后端接口

文件上传

文件处理controller

1
2
3
4
5
6
7
8
9
10
11
12
13
@Controller
public class FileController {
//自定义文件处理服务类
@Autowired
private FileServiceImpl fileService;
/**
*文件上传
*/
@RequestMapping(value = "/upload",method = RequestMethod.POST)
public String upload(@RequestParam("file") MultipartFile file) {
return fileService.uploadFile(file);
}
}

定义fileService类处理上传

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
@Service
public class FileService{

/**
* 文件上传处理函数
*/
public String uploadFile(MultipartFile file) {
//判断文件是否为空
if (file.isEmpty()) {
return "Empty";
}
//文件名
String fileName = file.getOriginalFilename();
//文件的保存路径 自定义可单独写个类
String filePath = "/Users/Songie/";
//文件最终的存储位置
File dest = new File(filePath + fileName);
//判断目标文件所在的目录是否存在
if(!dest.getParentFile().exists()) {
//如果目标文件所在的目录不存在,则创建父目录
System.out.println("文件所处目录不存在,创建");
//创建目录
if(!dest.getParentFile().mkdirs()) {
System.out.println("创建文件所处目录失败!");
return "fail";
}
}
//如果文件所在目录存在
try {
//存储文
file.transferTo(dest);
return "success";
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return "fail";
}
}

文件下载

前端

1
<a href="/download/文件名">下载</a>

controller中的下载方法

1
2
3
4
5
6
7
/**
* 文件下载
*/
@RequestMapping("/download/{fileName}")
public String downloadFile(@PathVariable("fileName") String fileName,HttpServletRequest request, HttpServletResponse response){
return fileService.downloadFile(fileName,request,response);
}

定义fileService类处理下载

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/**
* 文件下载处理函数
*/
public String downloadFile(fileName,HttpServletRequest request, HttpServletResponse response){
//文件的保存路径 自定义可单独写个类
String filePath = "/Users/Songie/";
File file = new File(filePath, fileName);
//如果文件存在
if (file.exists()) {
//重置buffer
response.resetBuffer();
//设定编码为UTF-8
response.setCharacterEncoding("UTF-8");
//设置头部为下载信息
response.setHeader("Content-type","application/force-download;charset=UTF-8");
try {
response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));// 设置文件名
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//各种流信息
FileInputStream fileInputStream = null;
BufferedInputStream bufferedInputStream = null;
OutputStream outputStream = null;

try {
byte[] buffer = new byte[1024];
fileInputStream = new FileInputStream(file);
bufferedInputStream = new BufferedInputStream(fileInputStream);
outputStream=response.getOutputStream();
int i = bufferedInputStream.read(buffer);
while (i != -1) {
outputStream.write(buffer, 0, i);
i = bufferedInputStream.read(buffer);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭各种流
if (outputStream != null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}

}
if (bufferedInputStream!= null) {
try {
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileInputStream != null) {
try {
fileInputStream.close();
return "success";
} catch (IOException e) {
e.printStackTrace();
}
}
}//finally结束

}//if结束
return "fail";
}//downloadFile结束

注:若想限制文件上传大小,需要在application.properties中配置参数

1
2
3
spring.http.multipart.enabled=true
spring.http.multipart.max-file-size=60MB
spring.http.multipart.max-request-size=60MB
12

Song Wei

15 日志
7 标签
GitHub E-Mail
© 2017 — 2018 Song Wei
次