Showing posts with label Springframework. Show all posts
Showing posts with label Springframework. Show all posts

Wednesday, June 24, 2020

Building a multitenant service

If you build any production service that is valuable, very soon you find a customer who wants to use it but wants it white-labeled for him. You also very quickly find out that there is a need to data isolation, i.e. different customers want their data to be kept in their own databases. Because of these reasons, it is always a good idea to design a service keeping multi-tenancy in mind.
In this tutorial we will look at some of the important features required for a multi-tenant service and how do we leverage springframework to deliver a service that is truly enabled for a modern multi-tenant service.
Here are couple of the important aspects of a multi-tenant service.
  1. Everybody gets their own endpoints
  2. Everybody can be given their own databases
Let's design how our endpoint URLs would look like keeping tenancy in mind. We add a discriminator in the URL that identified a tenant. For example our OAuth URLs would become something like below.
http://example.com/tenant1/oauth/token
http://example.com/tenant1/oauth/check_token
To accomplish, we define our RequestMapping with an embedded tenant variable that becomes part of each of the URLs.

 public static final String BASE_ENDPOINT = "/{tenant}";
 public static final String USER_ENDPOINT = BASE_ENDPOINT + "/user";
 public static final String REGISTRATION_ENDPOINT = BASE_ENDPOINT + "/registration";



As we can see, the first part of the URL defines the tenant. It is not mandatory but I also design the service so that a header is expected that also defines the tenant. This is just to guard against some client mistakenly calling a wrong tenant because the same tenant has to be added in two places. We will use following header.
X-tenant:tenant1
Every incoming request into the system needs to know the tenant and the authenticated user that is part of the request. We define a sequence of filters for this purpose. Since these filters need to be called in a particular order, we define the order as follow.

public static final int TENANT_HEADER_PRECEDENCE = Ordered.HIGHEST_PRECEDENCE;
public static final int SEED_DATA_PRECEDENCE = TENANT_HEADER_PRECEDENCE - 1;
public static final int TENANT_PRECEDENCE = SEED_DATA_PRECEDENCE - 1;
public static final int USER_PRECEDENCE = Ordered.LOWEST_PRECEDENCE;

As you can see we are going to define four filters which will perform specific functions.
  1. TenantHeader filter will extract tenant header from the incoming request, match the URL piece with the header and set it in a ThreadLocal variable.
  2. SeedData filter is only require to create some seed data to make the service usable. We need a default tenant in the system so that we can start making some requests. This doesn't do anything most of the time.
  3. Tenant filter will extract the tenant object and set it into another ThreadLocal variable.
  4. User filter is the last in the precedence and will extract the current authenticated user and will store it into a ThreadLocal.
We are using following TutorialRequestContext class to store these thread local variables.

Now let's look at these filters one by one.

This filter just extracts the X-tenant header stores in the thread local.

This filter checks that there should be atleast one tenant in the system, if not found, it inserts a default tenant. This is required so that we can use rest calls.

This filter extract the complete Tenant object from the database and stores it into the thread local.

This filter extract currently authenticate user and populates it in the thread local.
In the last tutorial, we used the spring provided default ClientDetailsService, we create our own service in this tutorial to make sure we can have a schema that we like. To do that we need a entity, TutorialClientDetails and a service TutorialClientDetailsService.

This entity just implements the ClientDetails interface

This service needs to implement a method loadClientByClientId.

Now that all the foundation work is in place, we get down to making our service multitenant. The first things that we need to do is to remove all the old dataSources that we had defined. We will now define a routing data source that will choose the right data source based on the tenant that we are taking to. This is where our tenant discriminator thread local will be usedful. Take a look at the following class.

Here we implement a method determineCurrentLookupKey which uses thread local to identify the current tenant and returns the key.

The bulk of smartness lies in the class TutorialMultitenantConfig, specifically the dataSource bean that returns the default dataSource for the system. What we are assuming that within our resource directory, we will have a tenants subdirectory and within that we will have one properties file per tenant. The property file will look like below.
 

Here we have added usual spring data source properties alongwith a name property which will be the name of the tenant. This name will be matched with the name in the URL and the header.
In the class TutorialMultitenantConfig, look at the definition of variable TENANT_PROPERTIES_RESOURCE, this basically looks up for all the *.properties file in the tenant directory.  Lines 61 through 74, create a data source object for each of the tenants and store these in a map with key being the tenant name. We remember determineCurrentLookupKey method which returned the name of current tenant, that return value is used to fetch appropriate data source object for the request being processed. Line 88 defines a default data source that is used if there is no data source present for the given tenant.

Now our multi tenanted service is ready and it is time to test. The first thing that we need to do is create couple of more databases with exact same schema as the first database. Please keep in mind that this has to be done manually even if you have defined ddl-auto property. Just take a mysqldump of the first database and import in two other databases.
We also need to make sure that the system atleast has one tenant defined in each of the databases. This is required in order to make sure we are able to use the rest calls. The best approach is to have a default tenant and then take a dump so that default tenant is also copied in each of the databases. We have created an admin user which has ADMIN role. We call the user endpoint that will return the list of all the users.
$ curl --request GET \
>   --url http://localhost:8081/tenant1/user \
>   --header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTI5Mzc1MzksInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiVVNFUiIsIkFETUlOIl0sImp0aSI6IjRmOTdiNThkLTI1NTEtNDA4Yi04ZWM4LWUzZGZmZWQ3MTQ2NiIsImNsaWVudF9pZCI6InN1cGVyc2VjcmV0Y2xpZW50Iiwic2NvcGUiOlsicmVhZCIsImNvZGUiLCJ3cml0ZSJdfQ.fWt_H-ORHP44xOIljoqMfVIeGkqJGQqUBMj8paVxAPM' \
>   --header 'cache-control: no-cache' \
>   --header 'content-type: application/json' \
>   --header 'postman-token: 76b3525e-bc3b-e629-91b6-a75253f657d8' \
>   --header 'x-tenant: tenant1' |python -m json.tool

[
    {
        "createdAt": 1592936869000,
        "createdBy": "UnAuthenticated",
        "email": "admin@springframework.in",
        "fullname": "Spring Tutorial Admin",
        "grantedAuthorities": [
            "ADMIN",
            "USER"
        ],
        "id": 6,
        "mask": 1,
        "tenantId": 1,
        "updatedAt": 1592936869000,
        "updatedBy": "UnAuthenticated",
        "username": "admin"
    },
    {
        "createdAt": 1592904896000,
        "createdBy": "UnAuthenticated",
        "email": "defaultuser@defaultadmin.com",
        "fullname": "Default User",
        "grantedAuthorities": [],
        "id": 2,
        "mask": 0,
        "tenantId": 1,
        "updatedAt": 1592904896000,
        "updatedBy": "UnAuthenticated",
        "username": "defaultuser"
    },
    {
        "createdAt": 1592904900000,
        "createdBy": "UnAuthenticated",
        "email": "vinay@avasthi.com",
        "fullname": "Vinay Avasthi",
        "grantedAuthorities": [],
        "id": 3,
        "mask": 1,
        "tenantId": 1,
        "updatedAt": 1592904900000,
        "updatedBy": "UnAuthenticated",
        "username": "vavasthi"
    }
]

Now we run the same query in tenant2
$ curl --request GET   --url http://localhost:8081/tenant2/user   --header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTI5MzgzMDcsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiVVNFUiIsIkFETUlOIl0sImp0aSI6IjJmOWVkOTYxLTk5MjktNDI3Zi1iZGU4LTc5NGIwYWNhMGYzNiIsImNsaWVudF9pZCI6InN1cGVyc2VjcmV0Y2xpZW50Iiwic2NvcGUiOlsicmVhZCIsImNvZGUiLCJ3cml0ZSJdfQ.KNzjB_JqJDaVI5vZNK-OcXDgvM5Uwt4I8tsVCazerpU'   --header 'cache-control: no-cache'   --header 'content-type: application/json'   --header 'postman-token: b1a9651c-e4c0-7b2e-c7b3-e0b3e06fa40e'   --header 'x-tenant: tenant2' |python -m json.tool
[
    {
        "createdAt": 1592904896000,
        "createdBy": "UnAuthenticated",
        "email": "defaultuser@defaultadmin.com",
        "fullname": "Default User",
        "grantedAuthorities": [],
        "id": 2,
        "mask": 0,
        "tenantId": 1,
        "updatedAt": 1592904896000,
        "updatedBy": "UnAuthenticated",
        "username": "defaultuser"
    },
    {
        "createdAt": 1592904900000,
        "createdBy": "UnAuthenticated",
        "email": "vinay@avasthi.com",
        "fullname": "Vinay Avasthi",
        "grantedAuthorities": [],
        "id": 3,
        "mask": 1,
        "tenantId": 1,
        "updatedAt": 1592904900000,
        "updatedBy": "UnAuthenticated",
        "username": "vavasthi"
    },
    {
        "createdAt": 1592938002000,
        "createdBy": "UnAuthenticated",
        "email": "ut2@springframework.in",
        "fullname": "User in T2",
        "grantedAuthorities": [],
        "id": 6,
        "mask": 1,
        "tenantId": 1,
        "updatedAt": 1592938002000,
        "updatedBy": "UnAuthenticated",
        "username": "userint2"
    },
{
        "createdAt": 1592937749000,
        "createdBy": "UnAuthenticated",
        "email": "admin@springframework.in",
        "fullname": "Springframework Tenant2 Administrator",
        "grantedAuthorities": [
            "ADMIN",
            "USER"
        ],
        "id": 5,
        "mask": 1,
        "tenantId": 1,
        "updatedAt": 1592937749000,
        "updatedBy": "UnAuthenticated",
        "username": "admin"
    }
]
As we can see, we are seeing two totally different sets of users which are stored in two totally different sets of databases.

Complete code for this blog post is available in my github repository here

Friday, June 5, 2020

Auditing in MySQL

One of the important requirements in many RDBMS based workloads is to have audit log where any row in any table is stamped with who changed it and  when was it changed? To do this in every place where the table is modified is extremely cumbersome. In this blog post we look at how we can enable the springframework based auditing framework to perform this activity.

Before we do that, we need to upgrade the versions for our dependencies since I have been using this project for quite sometime. Also I decided to use lombok logging annotation and removed all the dependencies on log4j.

Here are the modifications to the pom.xml for setting dependencies.


Now we look at our User entity. We need to add auditing fields to this table. Since in a realistic project one would have multiple entities, we create a abstract entity base class with all the audit fields. We also move the primary key to the abstract base class. When we do that, hibernate provides multiple ways of mapping the entities to the database schema. We want all the parent attributes to be stored in a single table with the child class attribute. To accomplish that, we need to add @MappedSuperClass to the base class.

As we can see, we have added five attributes in the base class. One is the id for all our entities and rest four will be used for auditing purposes.
At this time we also add another layer to our code. Currently all the Endpoints directly call the Repository layer, this causes a problem if we want to write functions that can be reused across different endpoints. An example of this need is retrieveUser method that takes an argument that could be a username or a email. Currently this method lies in the Endpoint layer as a private method. This is a useful method in many different contexts, so we create a new UserService layer and move this method there.


Now, let's get to the original task of enabling auditing. First we define a Auditing Config as below.

We had earlier defined a ThreadLocal that is used by the auditAware method defined above to extract currently logged in user and return its userId. As we can see the audit fields in the AbstractBaseEntity expects a Long for @createdBy and  @LastModifiedBy fields. The EntityAuditConfig also has annotation @EnableJpaAuditing which is required.
At this point we also add a new endpoint called ProfileEndpoint which can be used to manage the entity that represents a user profile. This entity currently only contains a url.
Now if we perform any operation on any of the endpoint, we will see the auditing fields automatically populated. Give it a spin. It is a life saver in many productions applications. I have had situations where users changed their passwords, forgot them and then complained saying that they have been hacked. 
The complete code for this and previous posts can be found at my github repository. This tutorial changes are under v1.5.

Monday, February 11, 2019

10. Application with multiple datasources

Many times it is a practical requirement to have multiple databases for a single application. These databases could be at different locations on the cloud and different entities in your application may be dealing with these databases.
Multi-tenancy is a great requirement when multiple data sources are needed. Many tenants may insist on having their own databases. Here we present how we can configure the spring application to interact with multiple databases.
The first step is to look at our application.properties file. We have a list of properties defined for the default dataSource which we will need to replicate for our second database. Let's assume we are going to use two data sources, the first one is called the user and the second one is called other.

As we can see above, we have replicated all the data source properties and given it a new prefix, other. Now, these two databases could have completely independent settings. They could point to totally different databases. Each of these properties could be configured to completely different settings. Here we have just changed the name of the database, username, password.
To make this work. we will have to split the repositories and the entity objects for each of the data sources. Here we create the following hierarchy of packages for each of the data sources.
src/main/java
- in.springframework.blog.tutorials
 - user
    - domain
    - repository
  - other
    - domain
    - repository
As we can see for each of the data sources, we have a domain package that would contain the entities and a repository package that would contain the repository class. This is needed so that each of the entity managers only searches for its own classes.
Now we need to define a configuration of each of the data sources that we have defined. The first data source will contain the user table and will also be the primary data source.

As we can see in the class above, it uses all the properties prefixed with spring.datasource and scans directories related to the user data source. Now let's look at the configuration for other data source.

The things to note in the other data source is that the @Primary annotation doesn't exist because we can have only one set of primary beans of a type. Also, the directories to be searched are for the other data source's domain and repository objects.
At this time we also move the old User and UserRepository classes to their respective subdirectories. We also create Other and OtherRepository classes in their respective subdirectories. We also change CrudRepository to JpaRepository in each of the repository classes.
Now, our application is set up to use two different data sources and we can verify that by running the application. Since we have set ddl-auto property to update, it should create a new schedule when the application is run.
$ mysql -u tutorial -ptutorial123 tutorial
mysql: [Warning] Using a password on the command line interface can be insecure.
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 115
Server version: 8.0.12 Homebrew

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show tables;
+--------------------+
| Tables_in_tutorial |
+--------------------+
| hibernate_sequence |
| user               |
+--------------------+
2 rows in set (0.00 sec)

mysql> desc user;
+------------+--------------+------+-----+---------+-------+
| Field      | Type         | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+-------+
| id         | bigint(20)   | NO   | PRI | NULL    |       |
| email      | varchar(255) | YES  | UNI | NULL    |       |
| fullname   | varchar(255) | YES  |     | NULL    |       |
| password   | varchar(255) | YES  |     | NULL    |       |
| username   | varchar(255) | YES  | UNI | NULL    |       |
| auth_token | varchar(255) | YES  | UNI | NULL    |       |
| expiry     | datetime     | YES  |     | NULL    |       |
| mask       | bigint(20)   | NO   |     | NULL    |       |
| authToken  | varchar(255) | YES  |     | NULL    |       |
+------------+--------------+------+-----+---------+-------+
9 rows in set (0.00 sec)

$ mysql -u other -pother123 other
mysql: [Warning] Using a password on the command line interface can be insecure.
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 116
Server version: 8.0.12 Homebrew

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show tables;
+--------------------+
| Tables_in_other    |
+--------------------+
| hibernate_sequence |
| other              |
+--------------------+
2 rows in set (0.00 sec)

mysql> desc other
    -> ;
+-----------+--------------+------+-----+---------+-------+
| Field     | Type         | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| id        | bigint(20)   | NO   | PRI | NULL    |       |
| otherData | varchar(255) | YES  |     | NULL    |       |
+-----------+--------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
The source code for this tutorial is available at git repository as v1.2.

Wednesday, January 23, 2019

9. Role based data access

Many times we have a need to return data from an endpoint based on the role. Springframework provider easy mechanism for us to be able to do that. Let's take an example. In the previous example, we want to add a GET method in UserEndpoint that returns all the users. For any safe system, we want to return all the users if the role is ADMIN but if the role is USER then we want to return only that particular user. We don't want to write multiple methods for that purpose.
    @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    @PostFilter("hasAuthority('ADMIN') or filterObject.authToken == authentication.name")
    public Iterable getUsers() {
        Iterable users = userRepository.findAll();
        return users;
    }
The method is described above. As we can see it is an extremely simple method, it calls findAll method on the repository which will return all the valid user records and returns an Iterable collection back. The interesting aspect of this method is in the @PostFilter annotation. Let's try to understand the annotation. The first condition is hasAuthority('ADMIN'). It implies that if the authenticated role is ADMIN then return the records as it is. The next bit of the filter condition uses an object called filterObject. This is an automatically defined expression that we can use in @PostAuthorize filter. A complete list of all the expressions exists here. The expression filterObject is used for each element of a collection that is returned from the endpoint. Since we know that when this endpoint is called, we would only be authenticating using authToken, the name of authentication in security context is set to the token itself. We can verify this in the code in AuthenticationFilter class.
            else {

                Optional token = getOptionalHeader(httpRequest,"token");
                TokenPrincipal authTokenPrincipal = new TokenPrincipal(token);
                processTokenAuthentication(authTokenPrincipal);
            }

The code fragment described above creates a TokenPrincipal object with the token as its name. That is the reason we have the condition filterObject.authToken == authentication.name. Taking the complete condition of @PostFilter we can see that the condition implies that return everything unconditionally if the role is ADMIN otherwise return the users with authToken as the currently authenticated user.
We have two users defined on the system. One has a role of the user and the other has a role of admin. Here are the examples of what happens when we call the endpoint with both of these users.
The first example is with a user with role USER.
$ curl -X GET "http://localhost:8081/user" -H "accept: application/json" -H "token: 3d47912d-73a0-4c4c-95e6-0486273d6221-28fa4f38-0f1b-4740-8e1d-3228288de631" | python -m json.tool
[
    {
        "authToken": "3d47912d-73a0-4c4c-95e6-0486273d6221-28fa4f38-0f1b-4740-8e1d-3228288de631",
        "email": "user@springframework.in",
        "expiry": 1548345909000,
        "fullname": "User",
        "id": 2,
        "mask": 1,
        "password": "User123",
        "username": "user"
    }
]

The second example is with a user with role ADMIN.
$ curl -X GET "http://localhost:8081/user" -H "accept: application/json" -H "token: a137dd09-11e4-4dcf-a141-0b235d39a505-60d43bf4-3674-4248-be1d-c2669f14589f" | python -m json.tool
[
    {
        "authToken": "3d47912d-73a0-4c4c-95e6-0486273d6221-28fa4f38-0f1b-4740-8e1d-3228288de631",
        "email": "user@springframework.in",
        "expiry": 1548345909000,
        "fullname": "User",
        "id": 2,
        "mask": 1,
        "password": "User123",
        "username": "user"
    },
    {
        "authToken": "a137dd09-11e4-4dcf-a141-0b235d39a505-60d43bf4-3674-4248-be1d-c2669f14589f",
        "email": "admin@springframework.in",                                                                                         
        "expiry": 1548348263000,                                                                                                     
        "fullname": "Administrator",                                                                                                 
        "id": 3,                                                                                                                     
        "mask": 4,                                                                                                                   
        "password": "Admin123",                                                                                                      
        "username": "admin"                                                                                                          
    }
]

As we can see above, the call with the role USER only returns the object related to that particular user while the call with the role returns all the users present in the system. This is how we can achieve role based object access without writing multiple endpoints.

Tuesday, January 22, 2019

7. Enabling Swagger

Before we proceed any further, let's enable swagger on our service so that testing it becomes easy. The first step is to add springfox dependencies.

Now that we have dependencies added, we need to define a swagger configuration. Most of our endpoint will need to have a token passed as HTTP header, we need to configure our swagger configuration to facilitate that.

We create a swagger configuration file as defined above. The globalOperationParameters clause dictates to swagger that each endpoint will have a parameter named token of type header and is mandatory. We know there is two endpoint that will not have a token header but we can just pass some dummy values. We can also make it non-mandatory if we so wish.

We also modify authentication endpoint to take a username and password header for authentication endpoint. That is defined in the AuthenticateEndpoint as above. Look at the @RequestHeader parameter passed in the login method of the endpoint.
        web.ignoring().antMatchers("/swagger-ui.html");
        web.ignoring().antMatchers("/v2/api-docs/**");
        web.ignoring().antMatchers("/swagger-resources/**");


Also, make sure that SecurityConfiguration.java that we had defined in one of the previous posts a list of swagger related URLs were made part of an exception list so that these do not go through authentication filter.
Now if we rebuild the server and run it and visit http://localhost:8081/swagger-ui.html we see the swagger page with all the API endpoints listed there. Here is the screenshot of the swagger UI as seen on visiting above URL.

Wednesday, December 19, 2018

5. Replacing tomcat with jetty

By default when we run spring boot application, it runs a tomcat server. I have personally found tomcat too heavyweight and I prefer to use Jetty for writing a webservices backend.
2018-12-19 10:54:08.403  INFO 16029 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)

2018-12-19 10:54:08.424  INFO 16029 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]

2018-12-19 10:54:08.424  INFO 16029 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/9.0.12

To replace tomcat with Jetty, we need to make a couple of changes.  The first step is to add Jetty dependencies in the pom.xml.
  
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-jetty</artifactId>
  </dependency>
  <dependency>
   <groupId>org.eclipse.jetty</groupId>
   <artifactId>jetty-deploy</artifactId>
  </dependency>
  <dependency>
   <groupId>org.eclipse.jetty</groupId>
   <artifactId>jetty-rewrite</artifactId>
  </dependency>
  <dependency>
   <groupId>org.eclipse.jetty</groupId>
   <artifactId>jetty-util</artifactId>
  </dependency>

Since springframework boot bom automatically includes a dependency on tomcat in spring-boot-starter-web, we specifically need to exclude it. So we change
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

to
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <exclusions>
    <exclusion>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-tomcat</artifactId>
    </exclusion>
   </exclusions>
  </dependency>


The next step is to add a customizer for jetty which will allow us to set some parameters related to Jetty.
As we can see the customizer uses a set of variables that we can override in application.properties file to modify the behavior of the server.

2018-12-19 11:06:44.577  INFO 16219 --- [           main] o.e.jetty.server.AbstractConnector       : Started ServerConnector@42ebece0{HTTP/1.1,[http/1.1]}{0.0.0.0:8081}
2018-12-19 11:06:44.580  INFO 16219 --- [           main] o.s.b.web.embedded.jetty.JettyWebServer  : Jetty started on port(s) 8081 (http/1.1) with context path ''
Now the logs clearly show that in place of tomcat, it is running jetty.

Monday, November 19, 2018

1. Starting a springframework project

The best way to start a spring framework project is to use the spring initializer to do that. Visit Spring Initializer website. Provider values for group and artifact and select the dependencies that you want to initialize with. To start with you can start with minimum dependencies and then add them later on. These are just maven dependencies.

Here I have selected Web and Security to start with. Now we click on Generate Project. It downloads a zip file which is the starting project. We unzip the file and then we run following command.
$ mvn clean package -DskipTests

 It will download the dependencies and then compile the project. The finally built jar file is in the tutorials/target directory and is named tutorials-0.0.1-SNAPSHOT.jar. The name is created using the version number defined in the pom.xml file generated. We can run the project by running following command.
$ java -jar target/tutorials-0.0.1-SNAPSHOT.jar

It will run the project and the following output is displayed. The last line tells us that the project launch is successful.

So here it is. Our first springframework project.