In the preceding exercise, you saw how to incorporate a static View to show product images on the product details page. We simply put some images in a directory on the server and did some configuration, and Spring MVC was able to pick up those files while rendering the product details page. What if we automated this process? I mean, instead of putting those images in the directory, what if we were able to upload the images to the image directory?
How can we do this? Here comes the multipart request. A multipart request is a type of HTTP request to send files and data to the server. Spring MVC provides good support for multipart requests. Let's say we want to upload some files to the server, then we have to form a multipart request to accomplish that.
Let's add an image upload facility in our add products
page:
WebApplicationContextConfig.java
) for CommonsMultipartResolver
as follows:@Bean public CommonsMultipartResolver multipartResolver() { CommonsMultipartResolver resolver=new CommonsMultipartResolver(); resolver.setDefaultEncoding("utf-8"); return resolver; }
pom.xml
, which you can find under the project root directory itself.pom.xml
; select the Dependencies tab and click on the Add button of the Dependencies section.commons-fileupload
, in Artifact Id enter commons-fileupload
, in Version enter 1.2.2
, select Scope as compile, then click on the OK button. org.apache.commons
as Group Id, commons-io
as Artifact Id, 1.3.2
as Version, and Scope as compile, then click on the OK button and save pom.xml
.product.java
) and add a reference to org.springframework.web.multipart.MultipartFile
with corresponding setters and getters as follows (don't forget to add getters and setters for this field):private MultipartFile productImage;
addProduct.jsp
, which you can find under the/src/main/webapp/WEB-INF/views/
directory in your project, and add the following set of tags after the <form:input id="condition">
tag group: <div class="form-group">
<label class="control-label col-lg-2" for="productImage">
<spring:message code="addProduct.form.productImage.label"/> </label>
<div class="col-lg-10">
<form:input id="productImage" path="productImage"
type="file" class="form:input-large" />
</div>
</div>
messages.properties
) for the product's image label, as follows:addProduct.form.productImage.label = Product Image file
enctype
attribute to multipart/form-data
in the form
tag as follows and save addProduct.jsp
: <form:form modelAttribute="newProduct" class="form-
horizontal" enctype="multipart/form-data">
ProductController.java
and modify the processAddNewProductForm
method's signature by adding an extra method parameter of the type HttpServletRequest
(javax.servlet.http.HttpServletRequest
); so basically your processAddNewProductForm
method signature should look like the following code snippet: public String processAddNewProductForm(
@ModelAttribute("newProduct") Product newProduct,
BindingResult result, HttpServletRequest request) {
processAddNewProductForm
method just before productService.addProduct(newProduct)
:MultipartFile productImage = newProduct.getProductImage(); String rootDirectory = request.getSession().getServletContext().getRealPath("/"); if (productImage!=null && !productImage.isEmpty()) { try { productImage.transferTo(new File(rootDirectory+"resources\images"+ newProduct.getProductId() + ".png")); } catch (Exception e) { throw new RuntimeException("Product Image saving failed", e); } }
initialiseBinder
method, add a productImage
field to the whitelisting set as follows: binder.setAllowedFields("productId",
"name",
"unitPrice",
"description",
"manufacturer",
"category",
"unitsInStock",
"condition",
"productImage");
http://localhost:8080/webstore/market/products/add
. You will be able to see our add products
page with an extra input field so you can choose which file to upload. Just fill out all the information as usual and, importantly, pick an image file of your choice for the newly-added image file; click on the Add button. You will be able to see that the image has been added to the Products page and to the product details page.Spring's CommonsMultipartResolver
(org.springframework.web.multipart.commons.CommonsMultipartResolver
) class is the thing that determines whether the given request contains multipart content and parses the given HTTP request into multipart files and parameters. That's the reason we created a bean for that class within our web application context in step 1. And, through the setMaxUploadSize
property, we set a maximum of 10,240,000 bytes as the allowed file size to be uploaded:
@Bean public CommonsMultipartResolver multipartResolver() { CommonsMultipartResolver resolver=new CommonsMultipartResolver(); resolver.setDefaultEncoding("utf-8"); resolver.setMaxUploadSize(10240000); return resolver; }
From steps 2 to 5, we added some of the org.apache.commons
libraries as our Maven dependencies. This is because Spring uses those libraries internally to support the file uploading feature.
Since the image that we were uploading belongs to a product, it is better to keep that image as part of the product information; that's why in step 6 we added a reference to the MultipartFile
in our domain class (Product.java
) and added corresponding setters and getters. This MultipartFile
reference holds the actual product image file that we are uploading.
We want to incorporate the image uploading facility in our add products
page; that's why, in the addProduct.jsp
View file, we added a file input tag to choose the desired image:
<div class="form-group"> <label class="control-label col-lg-2" for="productImage"> <spring:message code="addProduct.form.productImage.label"/> </label> <div class="col-lg-10"> <form:input id="productImage" path="productImage" type="file" class="form:input-large" /> </div> </div>
In the preceding set of tags, the important one is the <form:input>
tag, which has the type
attribute as file
so that it can make the Choose File button display the file chooser window. As usual, we want this form
field to be bound with the domain object field; that's the reason we gave the path
attribute as productImage
. If you remember, this path name is just the same MultipartFile
reference name that we added in step 6.
As usual, we want to externalize the label message for this file input tag as well; that's why we added <spring:message>
, and in step 8 we added the corresponding message entry in the message source file (messages.properties
).
Since our add product form is now capable of sending an image file as well as part of the request, we need to encode the request as a multipart request. This is why in step 9 we added the enctype
attribute to the <form:form>
tag and set its value as multipart/form-data
. The enctype
attribute indicates how the form data should be encoded when submitting it to the server.
We wanted to save the image file in the server under the resources/images
directory, as this directory structure will be available directly under the root directory of our web application at runtime. So, in order to get the root directory of our web application, we need HttpServletRequest
. See the following code snippet:
String rootDirectory = request.getSession().getServletContext().getRealPath("/");
That's the reason we added an extra method parameter called request
of the type HttpServletRequest
to our processAddNewProductForm
method in step 10. Remember, Spring will fill this request
parameter with the actual HTTP request.
In step 11, we simply read the image file from the domain object and wrote it into a new file with the product ID as the name:
MultipartFile productImage = newProduct.getProductImage(); String rootDirectory = request.getSession().getServletContext().getRealPath("/"); if (!productImage.isEmpty()) { try { productImage.transferTo(new File(rootDirectory+"resources\images"+newProduct.getProductId() + ".png")); } catch (Exception e) { throw new RuntimeException("Product Image saving failed", e); } }
Remember, we purposely saved the images with the product ID name because we have already designed our products (products.jsp
) page and details (product.jsp
) page accordingly to show the right image based on the product ID.
And as a final step, we added the newly introduced productImage
file to the whitelisting set in the binder configuration within the initialiseBinder
method.
Now if you run your application and enter http://localhost:8080/webstore/market/products/add
, you will be able to see your add products
page with an extra input field to choose the file to upload.
It's nice that we were able to upload the product image to the server while adding a new product. Why don't you extend this facility to upload a PDF file to the server? For example, consider that every product has a user manual and you want to upload these user manuals while adding a product.
Here are some of the things you can do to upload PDF files:
pdf
under the src/main/webapp/resources/
directory in your projectMultipartFile
reference in your product domain class (Product.java
) to hold the PDF file and change Product.java
accordinglyaddProduct.jsp
ProductController
.java
accordingly; don't forget to add the newly added field to the whitelistSo finally, if the newly added product ID is P1237
, you will be able to access the PDF under http://localhost:8080/webstore/pdf/P1237.pdf
.
Good luck!