Migrating a SpringWebMVC App from JSP to AngularJS

Stella981
• 阅读 723

Migrating a Spring Web MVC application from JSP to AngularJS

Engineering

Michael Isvy

August 19, 2015

Note on authors

This post is a guest post by Han Lim and Tony Nguyen. Han and Tony have done a great presentation at our Singapore Spring User Group on Spring + Angular JS. This blog is based on their presentation.

Abstractf

In this article, we try to describe our experiences moving from server-side rendering view technologies like JSP, Struts and Velocity to client-side rendering view technologies using AngularJS, a popular Javascript framework for modern browsers. We will talk about some of the things to look out for when you are making this change and potential pitfalls you may encounter. If you are experienced in Spring Web MVC and JSP development and would like to find out how Spring MVC can work together with a client-side Javascript like AngularJS, this article may just be for you.

There is also an appendix that gives some additional insights on AngularJS that may seem strange or unfamiliar to people coming from the JSP world.

Sample Petclinic for reference

We have created a fork of the Spring Petclinic application and experimented with converting it to AngularJS (with a new design courtesy of Andrew Abogado). Our fork can be found here.

Preparation

When you start to migrate from a server-side templating engine like JSP or Thymeleaf to a Javascript-based templating engine on the client side, you will need to adopt a paradigm shift towards a client-server architecture. You have to cease thinking of the view as being a part of the web application and instead conceive the web application as 2 separate client-side and server-side applications. The AngularJS application thus becomes an application on its own that runs on your web browser and it communicate with the backend services provided by Spring MVC. The only commonality between the the Spring MVC application and the AngularJS is probably going to be the fact that they are deployed in the same Java WAR file and that the index file is served out of a JSP.

An illustration of this is in the diagram below which shows how a Spring application becomes a provider of RESTful Web Services, servicing various front end applications including an AngularJS browser-based application as well as a possibility to provide services for mobile clients like tablets or smartphones. These services could include OAuth, Authentication and other business logic services which should be obfuscated from public view. One should bear in mind that any data or business logic that is published in the form of JSON or javascript files are exposed for the client-side to see. Thus, if there is any business sensitive logic or workflow that should not be exposed, it should only be performed on the backend.

Another difference to note about using AngularJS instead of JSP is that we would prefer not to use HTML forms and the traditional form submissions to pass data to the server side. Instead, we will prefer to encapsulate form submissions in a JSON object which is sent over to the backend RESTful service via a AngularJS HTTP Post method call. In fact, we will prefer to use full range of HTTP verbs that is encouraged in developing RESTful services.

If you need to perform validation on user inputs, it can be done on the front end using AngularJS’s built-in validation or your own custom input validation. You should always validate your data before posting it to the server. It is also prudent to validate the same data at the server side as well to ensure that clients that do not check their data do not compromise the integrity of the data on the server side.

Migrating a SpringWebMVC App from JSP to AngularJS

Application structure

Let us now discuss how you could organize your Spring + AngularJS application. At WDS (our company), we use Maven as our dependency and package management tool for Java/Spring and that influenced how we decided to place our AngularJS javascript application. The AngularJS application is created withinsrc/main/webappand the main files are

components/ # the various components are stored here. js/app.js # where we bootstrap the application plugins/ # additional external plugins e.g. jquery. services/ # common services are stored here. images/ videos/

You can see an image capture of the folder structure in Eclipse below.

Migrating a SpringWebMVC App from JSP to AngularJS

Resources here are organized per thefeature-groupingmethod. There are also ways to group your resources based on types, e.g. grouping all your controllers, services and views into its namesake folder. There are pros and cons for each of those options.

There are also some Javascript-based package managers like npm or bower that you might want to consider using to simplify the management of your external dependencies. If you are using bower, you will have a folder created called bower_components where all the dependency resources will be installed. You will then need to include them in your templates as you would do for any Javascript library. As for npm, you can use it to manage all your Javascript server side system tools like Grunt (a sort of _Ant_-like task runner)

Using AngularJS directives vs JSP custom tags

If you have used Spring’s custom form tags in your JSPs for developing your forms, you may be wondering if AngularJS provides the same kind of convenience for mapping form inputs to objects. The answer is yes! As a matter of fact, it is easy to bind any HTML element to a Javascript object. The only difference is that now the binding occurs on the client-side instead of the server-side.

<form:form method="POST" commandName="user"> <table> <tr> <td>User Name :</td> <td><form:input path="name" /></td> </tr> <tr> <td>Password :</td> <td><form:password path="password" /></td> </tr> <tr> <td>Country :</td> <td> <form:select path="country"> <form:option value="0" label="Select" /> <form:options items="${countryList}" itemValue="countryId" itemLabel="countryName" /> </form:select> </td> </tr> </table> </form:form>

Here is an example of the same form in AngularJS

<form name="UserForm" data-ng-controller="ExampleUserController"> <table> <tr> <td>User Name :</td> <td><input data-ng-model="user.name" /></td> </tr> <tr> <td>Password :</td> <td><input type="password" data-ng-model="user.password" /></td> </tr> <tr> <td>Country :</td> <td> <select data-ng-model="user.country" data-ng-options="country as country.label for country in countries"> <option value="">Select<option /> </select> </td> </tr> </table> </form>

Form inputs in AngularJS are augmented with additional capabilities like thengRequireddirective that makes the field mandatory based on certain conditions. There are also built-in validation for checking ranges, dates, patterns etc.. You can find out more at AngularJS’s official documentation found here which provides all the relevant form input directives.

Considerations when moving from JSP to AngularJS

In order to successfully migrate your JSP-based application to one that uses AngularJS, there are a few factors to consider.

Converting your Spring controllers to RESTful services

You will need to transform your controllers so instead of forwarding the response to a templating engine to render a view to the client, you will provide services that will be serialized into JSON data instead. The following is an example of how a standard Spring MVC controllerRequestMappinguses theModelAndViewobject to render a view with the Owner as described in the url mapping.

@RequestMapping("/api/owners/{ownerId}") public ModelAndView showOwner(@PathVariable("ownerId") int ownerId) { ModelAndView mav = new ModelAndView("owners/ownerDetails"); mav.addObject(this.clinicService.findOwnerById(ownerId)); return mav; }

A controller RequestMapping like that can be converted into an equivalent RESTful service that returns the owner based on the ownerId. Your template can then be moved into AngularJS which will then bind the owner object to the AngularJS template.

@RequestMapping(value = "/api/owners/{id}", method = RequestMethod.GET) public @ResponseBody Owner find(@PathVariable Integer id) { return this.clinicService.findOwnerById(id); }

In order for Spring MVC to convert your returned object (which need to be Serializable) to a JSON object, you can use the Jackson2 serialization library which is part of the Spring MVC dependency. In the example below, we had to customize the date serialization format by Jackson2 so we added the xml snippet in our Spring Context xml file to describe the date format for our JSON ObjectMapper Factory so that it knows that the Jackson2 ObjectMapper requires a date of such format. You can see the snippet that performs this Spring context configuration below. If there’s no customization of the date format (or any other serialization requirements), you can use the default ones which means that you do not even need to include this section as Spring MVC by default will component scan the ObjectMapper and inject it into your controller class via autowiring.

<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean" p:indentOutput="true" p:simpleDateFormat="yyyy-MM-dd'T'HH:mm:ss.SSSZ"></bean> <mvc:annotation-driven conversion-service="conversionService" > <mvc:message-converters> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" > <property name="objectMapper" ref="objectMapper" /> </bean> </mvc:message-converters> </mvc:annotation-driven>

Once you have converted your controllers into RESTful services, you can then access these resources from your AngularJS application.

One nice way to access RESTful services in AngularJS is to use the built-inngResourcedirective that allows you to access your RESTful services in an elegant and concise manner. An example of the Javascript code to access RESTful services using this directive can be illustrated by the following:

var Owner = ['$resource','context', function($resource, context) { return $resource(context + '/api/owners/:id'); }]; app.factory('Owner', Owner); var OwnerController = ['$scope','$state','Owner',function($scope,$state,Owner) { $scope.$on('$viewContentLoaded', function(event){ $('html, body').animate({ scrollTop: $("#owners").offset().top }, 1000); }); $scope.owners = Owner.query(); }];

The above snippet shows how a “resource” can be created by declaring an Owner resource and then initialising it as an Owner service. The controller can then use this service to query for Owners from the RESTful endpoint. In this way, you can easily create the resources that your application require and map it easily to your business domain model. This declaration is done once only in the app.js file. You can actually take a look at this actual file in action here.

When moving to RestAPI, it is important to remember that the RestAPI is the public interface rather than the website content. The JSON model is fully visible to users.
For example, if we need to display user profiles, password masking should be done on the JSON object rather than in the template. In order to do this, sometimes we need to create DTO objects for our RestAPI.

Synchronizing states between the backend and your AngularJS application

Synchronizing states is something that needs to be managed when you are developing a client-server architecture. You will need to give some thought to how your application updates its state from the backend or refresh its view whenever some state changes.

Authentication

Having your client-side code exposed to the public makes it even more important to think through how you would like to authenticate your users and maintain a session with your application. One important consideration in deciding your authentication method is to choose between a stateful session or a stateless one depending on your application architecture.

You can check Dave Syer’s series of blogs on how to integrate AngularJS with Spring Security here.

Testing

AngularJS comes with the necessary tools to help you perform testing at all layers of your Javascript development from unit to functional testing. Planning how you test and perform builds incorporating those tests will determine the quality of your front end client. We use a maven plugin calledfrontend-maven-pluginto assist us in our build tests.

Conclusion

Migrating to AngularJS from JSP may seem daunting but it can be very rewarding in the long run as it makes for a more maintainable and testable user interface. The trend towards client side rendered views also encourages building more responsive web applications that were previously hampered by the design in server side rendering. The advent of HTML 5 and CSS3 has ushered us to a new era in View rendering technologies, with different competing frameworks like EmberJs, ReactJs, BackboneJs etc.. However, in terms of momentum, AngularJS has been getting a lot of attention and having used it for a while, we can see why. We hope that this article contain useful tips for the people who intend to take the plunge. You can examine the fork of the Spring Petclinic that has some code examples to see how we did it.

Appendix

A brief introduction to AngularJS

AngularJS is a Javascript framework created at Google that touts itself as a “Superheroic Web MVW Framework” (where the “W” in the “MVW” being a tongue-in-cheek reference to “Whatever” for all the various MVx architectures. As it is based on an MVx architecture, AngularJS provides a structure to Javascript development and thus gives Javascript an elevated status compared to traditional Spring + JSP applications that only use Javascript to provide that bit of interactivity on the user interface.

With AngularJS, your Javascript-based view layer also inherits features like Dependency-Injection, HTML-vocabulary extension (via the use of custom directives), unit-testing and functional testing integration as well as DOM-selectors ala JQuery (using jqlite as it provides only a subset of JQuery but you could also easily use JQuery if you prefer). AngularJS also introduces scopes to your Javascript code so that variables declared in your code are bound only to the scope that is required.This prevents variables pollution that inadvertently arises when the size of your Javascript grows.

When you are developing a Spring Web MVC application using JSP, you will likely use the Spring-provided form tags to bind your form inputs to a server side model. Similarly, AngularJS provides a way to bind form inputs to models on the client side. In fact, it provides instantaneous 2-way data-binding from the form input to your model on the Javascript application. That means that not only do you have the benefits of having your view updated with changes inside your Javascript model, any changes you make to your UI will also update the Javascript model (and consequently any other views that is bound to that model). It is almost magical to see all the views that are bound to the same JS model on the app update the model automatically.

Moreover, since your model can be set to a particular scope, only views that belong to the same scope will be affected, allowing you to sandbox code that should be local only to a particular portion of your view. (This is done via an AngularJS attribute calledng-controllerthat is set in your HTML templates). You can see the difference in a later section comparing JSP tags and AngularJS directives.

Two-way Data Binding

In a Spring-JSP web application, there is one way data binding from Spring model to jsp view. Any change to the model will be reflected to Jsp view but not the reverse. This is the nature of web applications. If we build a desktop application, it is possible to do reverse data binding with Swing UI.

However, for a web application exposing REST resources, there may be no direct data binding. Data is sent from the server to the browser as JSON objects. Without AngularJS and the like, developers need to write javascript code in order to bind javascript object to html controls.

Because manual data binding is a tedious task, some developers try to automate the task by creating a Javascript framework for data binding. It is worth remembering that this data binding happens on the client side and the model for data binding is a Javascript object rather than a server side model.

Angular pushes this idea further by creating a two-way binding. Changing values in an HTML control will be reflected in the object in real time.

Migrating a SpringWebMVC App from JSP to AngularJS

Binding is a useful concept if you need to deal with complex UI components like AJAX tables.

For example: we need to render a list of users and roles in an AngularJs application, with the following html template:

<tr ng-repeat="user in users"> <td>{{user.username}}</td> <td>{{user.role}}</td> </tr> ... <a ng-click="addUser()">Add new user</a>

The code to add a user can be this simple:

$scope.addUser = function(){ newUser = {} $scope.users.push(newUser ); }

If the arrayusershas one more element, the table will automatically have one more row.

AngularJS Templates

Using AngularJS, it is possible to write relatively complex User Interfaces in an organized and elegant manner, always encapsulating the required logic within your components and never running the risk of errant global Javascript variables polluting your scope. It is also very testable, and there are built-in mechanisms to perform tests at the unit and functional level, ensuring that your User Interface codebase goes through the same rigorous testing that your Java/Spring code undergoes, ensuring quality even at the user interface level.

Another advantage of using AngularJS to write your html templates is that the templates are essentially similar to html even with the various front end logic baked into your view. It is possible to incorporate AngularJS logic into your template and still do a client-side validation control. In the JSP world, you can try viewing a JSP file from a browser with all the template logic in place and most likely your browser will give up rendering the page.
You can see how a typical AngularJS template looks like :

<div class="row thumbnail-wrapper"> <div data-ng-repeat="pet in currentOwner.pets" class="col-md-3"> <div class="thumbnail"> <img data-ng-src="images/pets/pet{{pet.id % 10 + 1}}.jpg" class="img-circle" alt="My Pet Image"> <div class="caption"> <h3 class="caption-heading" data-ng-bind="pet.name"></h3> <p class="caption-meta" data-ng-bind="pet.birthdate"></p> <p class="caption-meta"><span class="caption-label" data-ng-bind="pet.type.name"></span></p> </div> <div class="action-bar"> <a class="btn btn-default" data-toggle="modal" data-target="#petModal" data-ng-click="editPet(pet.id)"> <span class="glyphicon glyphicon-edit"></span> Edit Pet </a> <a class="btn btn-default"> <span></span> Add Visit </a> </div> </div> </div> </div>

You can probably spot some non-HTML additions to the template. It includes attributes likedata-ng-clickwhich maps a click on a button to a method name call. There’s alsodata-ng-repeatwhich loops through a JSON array and generates the necessary html code to render the same view for each item in the array. Yet with all the logic in place, we are still able to validate and view the html template from the browser.
AngularJS calls all the non-html tags and attributes “directives” and the purpose of these directives is to enhance the capabilities of HTML. AngularJS also supports both HTML 4 and 5 so if you have templates that are still relying on HTML 4 DOCTYPEs, it should still work fine (although the validators for HTML 4 will not recognize data-ng-x attributes).

One big difference between using AngularJS and JSP is the rendering time. If you use JSPs, the server renders html content. In contrast, if you use AngularJS, the rendering is happening in browser. Therefore, both the templates and JSON objects are to be sent to client side. It is worth to notice that AngularJS may briefly display the template before running DOM manipulation to generate content. For example, if AngularJS has not completed loaded, the date of birth in the page will be shown with an empty value before showing the real value.

Scopes in AngularJS

One important concept to grasp in AngularJS is that of scopes. In the past, whenever I had to write Javascript for my web application, I had to manage the variable names and construct special name-spaced objects in order to store my scoped properties. However, AngularJS does it for you automatically based on its MVx concept. Every directive will inherit a scope from its controller (or if you would like, an isolated scope that does not inherit other scope properties). The properties and variables created in this scope do not pollute the rest of the scopes or global context.

Scopes are used as the “glue” of an AngularJS application. Controllers in AngularJS use scopes to interact with the views. Scopes are also used to pass models and properties between directives and controllers. The advantage of this is that we are now forced to design our application in a way that components are self-contained and relationships between components have to be considered carefully through a use of a model that can be prototypically inherited from a parent scope.

A scope can be nested in another scope prototypically in the same way Javascript implements its inheritance model via prototyping. However, any property name that is declared in the child scope that is similar to the parent will hide the parent property from the child scope thereafter. An example of this can be described in the code below:

<!DOCTYPE html> <html> <head> <script data-require="angular.js@*" data-semver="1.4.0-rc.0" src="https://code.angularjs.org/1.4.0-rc.0/angular.js"></script> <link rel="stylesheet" href="style.css" /> <script src="script.js"></script> </head> <body data-ng-app="demo"> <h1>Scopes in AngularJS</h1> <div data-ng-controller="parentController"> <div data-ng-controller="childController"> <span>This is a demonstration of scopes</span> <div> Parent model: <span data-ng-bind="$parent.model.name"></span> </div> <div> Current model: <span data-ng-bind="model.name"></span> </div> <div> <button data-ng-click="updateModel()">Click me</button> </div> </div> </div> </body> </html>

At the very top in the hierarchy of scopes is the $rootScope, a scope that is accessible globally and can be used as the last resort to share properties and models across the whole application. The use of this should be minimized as it introduces a sort of “global” variable that can pose the same problems when it is overused.

More information about scopes can be gleaned from the AngularJS documentation found here.

Directives in AngularJS

Directives are one of the most important concepts in AngularJS. They bring all the additional customized markup in the HTML elements, attributes, classes or comments. They are the ones giving the markup new functionalities.

The following code snippet demonstrates a customized directive calledwdsCustomthat will replace the markup elementwith markup that contains information about a model calledwds. That model element is declared in the controller scope that wraps the directive. You can have a look at the filesapp.js,index.htmland directive templatewds-custom-directive.htmlto see how this works in the plunkr snippet available here.

As this article does not attempt to teach you how to write a directive, you can refer to the official documentation here.

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
3年前
HIVE 时间操作函数
日期函数UNIX时间戳转日期函数: from\_unixtime语法:   from\_unixtime(bigint unixtime\, string format\)返回值: string说明: 转化UNIX时间戳(从19700101 00:00:00 UTC到指定时间的秒数)到当前时区的时间格式举例:hive   selec
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这