📜 ⬆️ ⬇️

Spring Boot: from start to production


In this article I will try to describe all the steps that will be required to create a small project on Spring Boot and deploy it on the combat server.

We will not draw long preludes about the philosophy of java and spring, and get down to business right away.

To begin with, we need to create an application framework, having implemented all the necessary technology zoo (at least Spring, JPA, JDBC). Before the advent of spring boot, you had to spend a lot of time on it, unless of course you had a working blank in the code bins. And it is precisely the difficulty of creating such a framework, as it seems to me, to stop many from developing small web projects in java. Of course, once there was a lame spring roo, which could create a similar framework to the detriment of performance (hello to aspects), but even with it the number and complexity of configuration files made it necessary to meditate on them for a long time unprepared developer. However, now with the advent of Boot and Spring 4, life has become a little simpler and the number of configuration files has noticeably decreased.

So, the frame, yes.
')
If you have Intellij Idea 14.1, then there should be no problems with the frame at all, you can do everything through the special project creation wizard (File-New-Project ...- Spring Initializr). Next, you only need to specify the names of the projects, select the technologies of interest (Web, JDBC, JPA, PostgreSQL) and create a project.

If you do not have this IDE, then download the Spring Boot CLI , follow the instructions in INSTALL.txt. You need to set the system variable SPRING_HOME (the path to the folder with Spring Boot, not to the folder bin!) And add the path to SPRING_HOME / bin to the system PATH variable on windows.

So, the console console configured, now is the time to create a project. You can do this with the following command:
spring init --dependencies=web,data-jpa,jdbc yourapp 

UPDATE
In addition, as written in the comments, there is also a web constructor: start.spring.io

Next, import the resulting frame into your favorite IDE and begin to modify it to suit our needs.

To begin with, add the folder webapps to the src / main directory. We will create all web resources in it, and not in the resources folder, as the spring wants. The fact is that if we create files in the resources folder, then we will lose the opportunity to see the changes made in our web resources without restarting the server. And it can be unpleasant when, in order to see the changed text on a web page, you have to restart the web server.

Now in the webapps folder we create the index.html file and the css, js, font folders in which we will put the appropriate resources.

For example, let's make the simplest framework index.html:
 <!DOCTYPE html> <html lang="en"> <head> <title>Yourapp</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> </head> <body> <h1> HELLO WORLD </h1> </body> </html> 


Modify the pom.xml file
You should have something like this:
 <?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.yourcompany</groupId> <artifactId>yourapp</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>YourApp</name> <description></description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.2.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <start-class>com.yourcompany.Application</start-class> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>9.4-1201-jdbc41</version> <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> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>2.6</version> <executions> <execution> <id>copy-resources</id> <phase>validate</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <outputDirectory>${basedir}/target/classes/static</outputDirectory> <resources> <resource> <directory>src/main/webapp</directory> <filtering>true</filtering> </resource> </resources> </configuration> </execution> </executions> </plugin> </plugins> </build> </project> 

From the pom file we can see the following:
We use java 8 (it's time to try it). Our application class is called com.yourcompany.Application (don't forget to rename the standard generated class, which can be called for example DemoApplication).

We use postgresql 9.4 (it would also be nice to install it locally on our machine). Connection pool for interaction with a database we take the most fashionable and productive (HikariCP). In addition, we use a special plugin, which, when we generate the final jar, will transfer all our data from the webapp to resources / static, as spring boot wants. Otherwise, you will not be able to see all those web pages that you create in the webapps folder when launch jar-nickname.

Add the config package and create the JpaConfig class in it:
 @Configuration @EnableTransactionManagement @EnableJpaRepositories(basePackageClasses = Application.class) public class JpaConfig implements TransactionManagementConfigurer { @Value("${dataSource.driverClassName}") private String driver; @Value("${dataSource.url}") private String url; @Value("${dataSource.username}") private String username; @Value("${dataSource.password}") private String password; @Value("${hibernate.dialect}") private String dialect; @Value("${hibernate.hbm2ddl.auto}") private String hbm2ddlAuto; @Bean public DataSource configureDataSource() { HikariConfig config = new HikariConfig(); config.setDriverClassName(driver); config.setJdbcUrl(url); config.setUsername(username); config.setPassword(password); return new HikariDataSource(config); } @Bean public LocalContainerEntityManagerFactoryBean configureEntityManagerFactory() { LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean(); entityManagerFactoryBean.setDataSource(configureDataSource()); entityManagerFactoryBean.setPackagesToScan("com.yourcompany"); entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); Properties jpaProperties = new Properties(); jpaProperties.put(org.hibernate.cfg.Environment.DIALECT, dialect); jpaProperties.put(org.hibernate.cfg.Environment.HBM2DDL_AUTO, hbm2ddlAuto); entityManagerFactoryBean.setJpaProperties(jpaProperties); return entityManagerFactoryBean; } @Bean public PlatformTransactionManager annotationDrivenTransactionManager() { return new JpaTransactionManager(); } } 

In addition, we will add the following lines to the application.properties file:
 dataSource.driverClassName=org.postgresql.Driver dataSource.url=jdbc:postgresql://<ip- ,   PostgreSQL>:5432/yourapp_data dataSource.username=postgres dataSource.password= hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect hibernate.hbm2ddl.auto=update 

Finally, in Application.java, we change the initialization string to the following:
 SpringApplication.run(new Class<?>[] {Application.class, JpaConfig.class}, args); 

Thus, we set up a connection to the PostgreSQL database.

Do not forget to create the database itself and a simple table in it. This is most conveniently done via PgAdmin.
Having created an empty yourapp_data database in it, execute the table creation script:
 CREATE TABLE yourapp_data ( data_id uuid NOT NULL, data_description character varying(100) NOT NULL, CONSTRAINT yourapp_data_pk PRIMARY KEY (data_id) ) WITH ( OIDS=FALSE ); ALTER TABLE yourapp_data OWNER TO postgres; 

Now it's time to do a little stuffing our project. Namely, to add some database entity and learn how to work with it, receiving data from the client to form it and sending the same data to the client about the already created entities.

We create the controller, entity, repository, service, utils packages.

In the entity package, we create an interface:
 public interface DomainObject extends Serializable { } 

and essence:
 public class Data implements DomainObject { private UUID id; private String description; public Data(UUID id, String description) { this.id = id; this.description = description; } public UUID getId() { return id; } public void setId(UUID id) { this.id = id; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } } 

We will not use the JPA and Hibernate annotations in this example, as these technologies slow down the work a lot (the query can be executed 10 times slower than on pure jdbc), and since we don’t have very complex entities that really need ORM, then use the usual jdbcTemplate.

Create a repository interface:
 public interface DataRepository<V extends DomainObject> { void persist(V object); void delete(V object); Set<String> getRandomData(); } 

And its implementation:
 @org.springframework.stereotype.Repository("dataRespitory") public class DataRepositoryImpl implements DataRepository<Data> { @Autowired protected JdbcOperations jdbcOperations; @Override public void persist(Data object) { Object[] params = new Object[] { object.getId(), object.getDescription() }; int[] types = new int[] { Types.VARCHAR, Types.VARCHAR }; jdbcOperations.update("INSERT INTO yourapp_data(\n" + " data_id, data_description)\n" + " VALUES (cast(? as UUID), ?);", params, types); } @Override public void delete(Data object) { jdbcOperations.update("DELETE FROM yourapp_data\n" + " WHERE data_id = '" + object.getId().toString() + "';"); } @Override public Set<String> getRandomData() { Set<String> result = new HashSet<>(); SqlRowSet rowSet = jdbcOperations.queryForRowSet("SELECT data_description FROM yourapp_data p ORDER BY RANDOM() LIMIT 50;"); while (rowSet.next()) { result.add(rowSet.getString("data_description")); } return result; } } 

Instead of the jdbcTemplate already mentioned, we, as you can see, use JdbcOperations, which is its interface. We have to use interfaces everywhere, separating them from the implementation, because, firstly, it’s stylish, fashionable, youth, and secondly, spring in our case uses the standard jdk’sny Proxy for our objects, therefore, the implementation cannot be directly injected, until we introduce the full aspects and AspectJ compile-time weaving. In our case, this is not required in order not to overload the application.

There are already a few. We create our service (we are good developers and have to separate business logic from the logic of working with the DBMS?).

Interface:
 public interface DataService { public boolean persist(String problem); public Set<String> getRandomData(); } 

Implementation:
 @Service("dataService") public class DataServiceImpl implements DataService { private static final Logger LOG = LoggerFactory.getLogger(DataServiceImpl.class); @Autowired @Qualifier("dataRespitory") private DataRepository dataRepository; @Override public boolean persist(String problem) { try { dataRepository.persist(new Data(UUID.randomUUID(), problem)); return true; } catch (Exception e) { LOG.error("ERROR SAVING DATA: " + e.getMessage(), e); return false; } } @Override public Set<String> getRandomData() { return dataRepository.getRandomData(); } } 

Fine. Now we create a pair of auxiliary classes necessary for the implementation of the controller:
 public class RestException extends Exception { public RestException() { } public RestException(String message) { super(message); } public RestException(String message, Throwable cause) { super(message, cause); } public RestException(Throwable cause) { super(cause); } public RestException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } } 

This is our implementation of Exception. It may be useful in the future, although not required, but the following class is tied to it:
 @Controller public class ExceptionHandlerController { private static final Logger LOG = Logger.getLogger(ExceptionHandlerController.class); @ExceptionHandler(RestException.class) public @ResponseBody String handleException(RestException e) { LOG.error(": " + e.getMessage(), e); return ": " + e.getMessage(); } } 

If we caught such an error in our controller, it will be processed additionally in this method.
Finally, we will write a small classic that will form the data structure for transmission to the client:
 public class Ajax { public static Map<String, Object> successResponse(Object object) { Map<String, Object> response = new HashMap<String, Object>(); response.put("result", "success"); response.put("data", object); return response; } public static Map<String, Object> emptyResponse() { Map<String, Object> response = new HashMap<String, Object>(); response.put("result", "success"); return response; } public static Map<String, Object> errorResponse(String errorMessage) { Map<String, Object> response = new HashMap<String, Object>(); response.put("result", "error"); response.put("message", errorMessage); return response; } } 

All with auxiliary classes finished. It remains to write our controller. It will be as simple as a cork:
 @Controller public class DataController extends ExceptionHandlerController { private static final Logger LOG = Logger.getLogger(DataController.class); @Autowired @Qualifier("dataService") private DataService dataService; @RequestMapping(value = "/persist", method = RequestMethod.POST) public @ResponseBody Map<String, Object> persist(@RequestParam("data") String data) throws RestException { try { if (data == null || data.equals("")) { return Ajax.emptyResponse(); } dataService.persist(data); return Ajax.emptyResponse(); } catch (Exception e) { throw new RestException(e); } } @RequestMapping(value = "/getRandomData", method = RequestMethod.GET) public @ResponseBody Map<String, Object> getRandomData() throws RestException { try { Set<String> result = dataService.getRandomData(); return Ajax.successResponse(result); } catch (Exception e) { throw new RestException(e); } } } 

There are two methods in it - save the received data and give a portion of random data to the client. The controller inherits the ExceptionHandlerController we created earlier. Exception handling is written only as a template and needs to be modified accordingly.

So, the main part of the server code is written, it remains to check its operation on the client. To do this, you need to refine our index.html file and at the same time add the jquery library to the js directory.
index.html:
 <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>YourApp</title> <script src="js/jquery-2.1.3.min.js"></script> </head> <body> <h1> HELLO WORLD </h1> <input type="text" id="data"/> <a id="post" href="#">POST</a> <a id="get" href="#">GET</a> <div id="container"></div> </body> <script> $('#get').click(function () { $.ajax({ type: "GET", cache: false, url: '/getRandomData', data: "", success: function (response) { var html = ""; $.each(response.data, function (i) { html = html + response.data[i] + "<br/>"; }); $('#container').html(html); } }); }); $('#post').click(function () { if (!$("#data").val()) { alert("Enter your data!"); } else { $.ajax({ type: "POST", cache: false, url: '/persist', data: { 'data': $("#data").val() }, success: function (response) { $('#get').click(); } }); } }); </script> </html> 

Yes, the UI did not turn out to be God knows how beautiful, but with its help we can check the operation of the application.
Run our project. In Intellij Idea, this can be done through a special launch configuration (Spring Boot).
If everything is done correctly, then at localhost: 8080 you can see the Hello World header, an input line and two buttons. Try typing something into the input line and clicking on the POST button. If after this you see a similar text below the input field, then everything works as it should. Now it remains to modify the project to fit your needs, add a trendy UI (for example, materializecss.com ) and create a reasonable, kind, eternal.

However, sooner or later you will create the desired and the question will arise about how to bring your child to the masses. This will be the second part of the article.

Let's start with a small but important.
Even if the project is small, you still need your own domain for it. If you just run around some idea and don’t want to spend big money to register a domain on the same godaddy, you can use the free alternative: freenom.com

This service will allow you to register a domain in .tk, .ml, .ga, .cf, .gq zones for free
Yes, not the best zones, but:


Further we will be engaged in the server where all this will turn. Since we have a small project, we will need a small server. Ideally enough for VPS. You can get it in different places, for example www.digitalocean.com
So, we register, create the simplest droplet and put ubuntu on it (in my case it is ubuntu 12.04, I will describe further instructions for this system, but the rest will be about the same)

Great, we have a server, it's time to pour our project on it.

To begin with we collect the project maven'om. This can be done through the IDE, or at worst, go to the root directory of the project and enter the command mvn clean install (the path to the maven should be registered in the system variable path on Windows). After executing the command, the assembled jar'nik is placed in a local repository (by default called .m2), from where it can be pulled down to be sent to the server.

To transfer the file to the server, use WinSCP if you are working under Windows.
Next, go to our server using putty on Windows or ssh on Linux.
Go to the directory where our jar-nickname was copied and try to start it with the command java -jar youapp.jar

Most likely, it did not work out. Why all? Our project was created on java 8, and you can find out which java is on the server using the java -version command. And most likely it is either 6 or 7.

But let's not be discouraged, we will set ourselves a new version:
 sudo add-apt-repository ppa:webupd8team/java sudo apt-get update sudo apt-get install oracle-java8-installer 

Now it’s the postgres lineup. Before we used the local version on the developer's machine, now it’s time to put the DBMS on the server.

To do this, first run the magic sequence of commands:
 sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' sudo apt-get install wget ca-certificates wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - sudo apt-get update sudo apt-get upgrade sudo apt-get install postgresql-9.4 postgresql-contrib-9.4 

Run postgres:
 sudo service postgresql start 

Next, execute the psql login command:
 sudo -u postgres psql 

Set password:
 \password postgres 

And quit using the \ q command

We edit the /etc/postgresql/9.4/main/postgresql.conf file by changing the line #listen_addresses = 'localhost' to listen_addresses = '*'
This way we can connect to postgresql from the outside using pgadmin. Although, of course, it is desirable to avoid this for security reasons, and when everything is configured and debugged, disable this feature.

Then edit the file /etc/postgresql/9.4/main/pg_hba.conf
Two new lines should be added and one line modified for 127.0.0.1 as follows:
 host all all 127.0.0.1/32 trust host all all <ip- >/32 trust host all all <ip-  >/32 trust 

I intentionally changed md5 to trust, since I personally had problems running the project, thereby turning off password checking for specified addresses. Perhaps you will not have them.

Now everything is set up. Although it is possible to tune the postgres indefinitely, but we have only a small project, which means we will leave it as it is.

Restart postgres:
  sudo service postgresql restart 
and check his work.

Everything, with the postgres setting, is finished, what do we have next in the script?

As noted earlier, to run the assembled jar, the java -jar youapp.jar command is sufficient.
However, with such a launch, in order to enter the site from the outside, you will have to register a port (by default 8080). So that users can enter the site by simply entering its address, we will need a proxy server. As it can take nginx, which will need to be pre-configured.

Install nginx:
 sudo apt-get install nginx 

In my case, the nginx root directory was / etc / nginx. There, we first need to change the file / sites_available / default as follows:
 server { listen 80; server_name youapp.com; location / { proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://127.0.0.1:8080/; } } 

However, this is not all. It is also necessary to modify our project so that it supports the proxy we configured. It’s not hard to do this, all you need to do is add lines to application.properties (do not forget to upload the new version with the changes):
 server.tomcat.remote_ip_header=x-forwarded-for server.tomcat.protocol_header=x-forwarded-proto 

Now you can start nginx with the service nginx start command and then try to start our project. It will be available at the link of the site, or, if you have not purchased a domain, then by its ip-address, without specifying the port.

There is one more small touch. It is a little inconvenient to always start a project in the manner described above. It would be nice if the input console on the server was released when the project started, the application would not close after exiting the ssh session and that application logs were kept somewhere. You can do this with the nohup command. Pre-create a bash script, calling it script.sh:
 #!/bin/bash java -jar youapp.jar 

We assign him the right to perform:
 chmod +x ./script.sh 

And run the command:
 nohup ./start.sh > log.txt 2>&1 & 

That's all, the application is running.

To stop the application, you can either use the pkill -9 java command (provided that this is the only java application running on the server), or by using the htop utility, highlighting this process, pressing the F9 button, selecting SIGKILL on the left and pressing enter . To the note: sometimes it does not work the first time and the procedure has to be repeated.

Now, if everything is done correctly, you can open the site of our project in the browser and enjoy the result.

PS I hope I did not miss anything. If you find a mistake, please write about it in a personal message. The text will be promptly corrected.

Source: https://habr.com/ru/post/257223/


All Articles