Skip to content

Introduction to Spring

Alon Talmor edited this page Nov 19, 2017 · 7 revisions

made by @alonttal

Hello. I hope you will find this guide useful. Please let me know if you see any unclear wording or mistakes. #24

. . image

Introduction

Spring is an open source framework created to address the difficulty and complexity of a major application development. It is heavily based on two design principles – Dependency Injection (DI) and Aspect Oriented Programming (AOP) which we will cover later.

. . image

  • Tool that helps managing java projects. it configures how .java file get compiled into .class, manage CLASSPATH, and so on.
  • Practically, Maven will download all libraries we'll need and integrate it into our project.

. . image An important thing we need to understand before we dive into the technical stuff is what exactly a REST API is. REST stands for Representational State Transfer. It is an architecture style for designing networked applications. REST API usually works the same as a website does – when you make a call to the server you eventually receive data back over HTTP protocol. REST API treats objects on the server side as resources that can be created, update and destroyed. It basically uses 4 main HTTP request methods:

  • GET: get data from the server
  • POST: send data to the server
  • PUT: update a resource on the server
  • DELETE: delete a resource on a server A request could look something like this: https:// roommates.com/rest/ apartments/43545
  • https means we are using a secured http protocol
  • roommates.com/rest is the endpoint url which means we refer to roomates.com REST API
  • apartments will list all of the apartments in the server's database
  • 43545 filter the apartment with id=43545 Usually the response is returned in JSON format so it can be used in our application.

. . image One if the things Spring provides is a layered architecture, which helps in breaking our application into different logical units. This is a type of software architecture pattern called Model-View-Controller (MVC). In this pattern, each component has a different responsibility in the overall app: The Model is the core of the application, containing its data and logic – the thing which gives our app it's personality. The model does not communicate directly with the view. The View objects are the "pretty face" of the app. The window, buttons, menus and so on, are all view object. They all displayed on the user screen and respond to the user's actions. Even though a view may get a request to handle an event that involved with data, the view won't talk to the model directly. The model will also never talk directly to the view. To connect between these two snobs, we need a mediator, one that is called a Controller. Controller objects connect the app's view to its core logic and data. in other words, they deliver objects from the model to the view and vise-versa. this method creates a responsive and modular app.

. .

Setting up a Spring Project

  1. In this tutorial, I use an Eclipse IDE called Spring tool Suite (STS). You can download it at the following link: https://spring.io/tools/sts/all. Examples presented below are from 3.9.1 release. Of course you do not have to use STS and there are other alternatives.
  2. Click New -> Maven Project image
  3. Check "Create a simple project (skip archetype selection)".
  4. Fill up your Maven project configuration. Group id – main package of the project Artifact id – name of your project Version –the initial version of your project. just keep it as is. image
  5. After clicking finish STS will create Maven structured project. On the bottom right you may see that it is downloading some files, those are the initial files required for setting up your environment. from the project explorer, open up pom.xml. this file contains all of our Maven project setting and dependencies. We can already see that the initial information we entered are written there:
 	<modelVersion>4.0.0</modelVersion>
	<groupId>io.yearlyproject.roommates</groupId>
	<artifactId>roommates</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>Roommates</name>
  1. Next, add this to pom.xml:
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.8.RELEASE</version>
	</parent>

What is it? This XML code declares our project as a child of Springboot framework. Spring sets up a lot of things under the wood. Our project, as a child of Springboot, inherits it all.

  1. In order to make a web application we'll need a bunch of jars to add to our application. All we need to do is to add a meta-dependency that includes almost all we need in order to make a basic web application. Copy and paste the following:
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	</dependencies>
  1. Hit save (ctrl+s). you will see the maven is downloading some files. When it finishes you will be able to see that "Maven Dependencies" workspace was added. image
  2. You're also able to see that that the JDK version is 1.5. that is really old and we don’t want to use it. To change it we need to add to pom.xml:
	<properties>
		<jave.version>1.8</jave.version>
	</properties>
  1. The last step (for now) is to update our project with all the Maven configuration we've just set up. Hit Alt+F5 in order to set everything up.

Creating main

What we're going to do now is creating the project's main class.

  1. Create a class in src/main/java directory and add a main to it image
  2. Next, we need to tell Spring that this class implements a Spring application. There is an builtin annotation for exactly that purpose. All we need to do is to write @SpringBootApplication above the app class name
  3. Now, we will tell Spring to run this application, which is in our case "RoommatesApp", then create a servlet container and host this application in this servlet container. It might sound like a lot of work, but turns out that Spring has a builtin static method that does all this job: SpringAplication.run(RoommatesApp.class, args); The first argument of this method is the main class of our application. The second argument is the args that we pass to main which we might want to send to our Spring application.
  4. That’s it. We now have a Spring application (that doesn’t do anything)! If we click run, and go to http://localhost:8080/ we will see that we get an error page. We haven't defined a mapping to the url, so we're redirected to /error, but since there is also no redirection to /error we get an error page like this.

. .

Writing a Controller

What a controller does is to decide what to return the client when a request is sent to the server. In java, a controller is basically a class marked with some fancy annotations. It contains code that manages which method to run when a URL access is requested. Let's add a controller to our project, that what it does is each time we access the localhost we'll receive a greeting.

  1. First let's create a new class image
  2. Next, we need to tell Spring that this class is a RESTful controller. To do that we need to add the annotation @RestController above the class name.
  3. Next let’s add this method:
	public String greetGuests() {
		return "Welcome to Roommates website!";
	}

Spring needs to know on which pages this method needs to be called. In order to do that, we need to use another annotation. By adding @RequestMapping("/") above the class name we tell Spring that each time there is a HTTP request (GET,POST,.. ) to the main page of our website it has to excute this method. If we would have written "/greetings" inside the annotation bracket Spring would execute this method every time a client requested /greetings url. 4. Compile and run the application to check the results of what we have done so far. .

Next we are going to add users to our project. For that we'll need to create 3 classes:

  1. User class: this will be a POJO class for user
public class User {
	private int id;
	private String name;
	private List<String> appartmentsPublished;
	
	public User(int id, String name, List<String> appartmentsPublished) {
		this.id = id;
		this.name = name;
		this.appartmentsPublished = appartmentsPublished;
	}

	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	public List<String> getAppartmentsPublished() {
		return appartmentsPublished;
	}

}
  1. UserService class: this is the class UserController is going to depend on when doing stuff with users. It is a better practice to extract the business service from the controller itself, and move it to services.
@Service
public class UserService {

	private List<User> usersList = new ArrayList<>(Arrays.asList(
				new User(1,"Alon", Arrays.asList("1","3")),
				new User(2,"Or", Arrays.asList("2")),
				new User(3,"Oren", Arrays.asList(""))
			));
	
	public List<User> getUsers() {
		return usersList;
	}
}

Note the usage of @Service above the class name. when Spring sees @Service it creates a singleton of the class during the app initialization. Imagine in the future how this getUsers() method can do all kinds of more advance things, like getting all the users from the database and such. 3. UserController class: this class is going to handle REST requests for users.

@RestController
public class UserController {
	
	@Autowired
	private UserService userService;

	@RequestMapping("/users")
	public List<User> getUsers(){
		return userService.getUsers();
	}
}

Note the @RestController and @RequestMapping annotations which we've already covered. Besides that, notice @Autowired marked above userService variable. This is a Spring annotation which markes the variable as needs a dependency injection. Spring will inject the singleton instance it created for UserService and inject it into this userService variable. Now that we run the application and go to localhost:8080/users we will see all the hard-coded users in JSON format. this is what we've got so far: image .

Let's make things a bit more complicated. What if we don’t want to get ALL the users, but we want to get a specific user from the server? Spring allows us to also do that, and we will now see how.

  1. First let's add a getUser method to UserService class, that receives an id parameter and returns the user with that id:
	public Optional<User> getUser(int id) {
		return usersList.stream().filter(user -> user.getId() == id).findFirst();
	}
  1. Secondly, we renovate UserController class with the a new getUser method:
	@RequestMapping("/users/{id}")
	public User getUser(@PathVariable int id) {
		return userService.getUser(id).orElse(null);
	}

So what did we do here? You can see that now @RequestMapping argument has this {id} in it. This tell Spring that the URI contains an argument which is going to be used in this method. this parameter needs to be marked with @PathVariable, as you can see in the example above. 3. Now if we compile and run the app, and then go to http://localhost:8080/users/1 we will see that we only get Alon's record. If we try to go to http://localhost:8080/users/5 we'll receive a blank page since there is not user with id "5". .

POST

image before we start, download Postman from chrome store. This tool will help us do POST requests to the server without the need of a UI. .

  1. The procedure is very alike to what we did with GET requests. We need to add a method to UserService that will add a user to the list:
	public void addUser(User user) {
		usersList.add(user);
	}
  1. After that, we create a new method in the controller. But how do we specify that this method is for POST calls? If we only annotate it with RequestMapping("/users") it will be a GET request by default. Turns out that RequestMapping can get a requestMethod argument that specifies the type of the request.
	@RequestMapping(method=RequestMethod.POST, value="/users")
	public void addUser(@RequestBody User user) {
		userService.addUser(user);
	}

Again, we also need to annotate the parameter with @RequestBody. This annotation tells Spring that the POST data can be found in the HTTP request payload. 3. We also need User to have a default constructor: public User() {} Because we don't have an UI yet, will use Postman to send a POST request to our server.

  1. In the upper bar we need to type the URL of the POST request image
  2. In the header tab we need to specify that this HTTP request is in JSON format Key: Content-Type ; value: application/json image
  3. In the body tab we write the user that we want to add in raw JSON format. image
  4. Then hit send.
  5. When we do a GET request on localhost:8080/users we can now see that Daniel was added to the list of users! image

DI

let's start with an example:

public class User {
	private Email email = new Email();
	
	public void sendMessage(String msg) {
		this.email.send(msg); 
	}
}

In this scenario, email is hard-coded into the User class body. This means that if we'll want to change the email service to some other (maybe more advance) service in the future it will be a harder task. Not to mention the difficulty if it is used in more classes. Another thing to consider is changing this email service to maybe Facebook or Whatsapp messaging – this will require a complete redesign of the class. Even more importantly, testing can become very irritating. Because the User class is so strictly bounded to the Email class it cannot be tested without testing both of them. Imaging how it will feel like to see our mailbox blown up by emails sent by ourselves after a few sunny days of testing. Do we really want to send a real email every time we run a test? Apparently, there is no way to mock emails with the current implementation. Let's do a second try:

public class User {
	private MessageService msgSrv;
	
	public User(MessageService msgSrv){
		this. msgSrv = msgSrv;
	}
	public void sendMessage(String msg) {
		this.msgSrv.send(msg); 
	}
}

We also need to make sure that Email implements MessageService. With this implementation, we are solving all of the above-mentioned problems. But it still has a downside – every user creation requires many more lines of code now! i.e. the code becomes very verbose, especially if we decide to add more parameters to the User's constructor. DI is a design pattern which we used in the above example. We removed the dependency resolution from compile-time to runtime and improved our classes to be loosely coupled, extendable and maintainable.

Spring DI

  1. I've edited the User class so it can take advantage of the Spring DI, shall we take a look at it again:
@Component
public class User {
	//@Autowired
	private MessageService msgSrv;
	
	@Autowired
	public User(MessageService msgSrv){
		this. msgSrv = msgSrv;
	}

	//@Autowired
	//public void setMessageService(MessageService msgSrv){
	//	this. msgSrv = msgSrv;
	//}

	public void sendMessage(String msg) {
		this.msgSrv.send(msg); 
	}
}

Every Spring application has a Spring Container. By adding @Component annotations above the class definition, we are adding these classes into the Spring Container. this allows Spring to be aware of them and manage them for us. Classes which are inside the Spring Container are called Spring Beans. Other annotations for Spring beans are: @Repository, @Service, @Controller, each one having a different meaning to the Spring framework. We also need to add @Component above MessageService class so Spring will be able to inject it. The @Autowired annotation tells Spring where we want the injection to be applied. The entities which are able to be marked using Spring DI annotations are: fields, constructors and setters. In the above example I'm showing all the options, in this case I chose to annotate the constructor. 2. The next thing we need to do is to create a configuration class that will configure what type of objects we want to inject.

@Configuration
@ComponentScan(value={…})
Public class DIConfiguration{

	@Bean
	Public MessageService getMessageService(){
		return new HomingPigeon();
	}

}

This division to configuration classes allows us to specify different configurations for different purposes. For instance, we can write a configuration that will be used in development, another one that will be used in production and other for testing. @Configuration tells Spring to treat this class as a configuration class. When that being interpreted into the Spring framework and can look for @Bean inside it. The @ComponentScan the comes right after that defines where (in what packages) Spring should look for beans that require injects particular to this configuration. Lastly @Bean are the configuration itself. methods are used in order to match the interface to its implementation. In the above example, it injects HomingPigeon each time MessageService is required. 3. To drive everything up, we need to add the following code (note the in JUnit testing the methid is a bit different):

	AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DIConfiguration.class);
	User user = context.getBean(User.class);
	
	//we do things with user, like:
	user.sendMessage("Your apartment ad was published successfully.");

	//a bit of boilerplate for closing the context
	context.close();

with the few annotations we used previously, AnnotationConfigApplicationContext can do its trick. There is also a way to do DI with Spring using XML configuration file, which I will not cover here.

AOP

In OOP modularity of application is achieved by creating seperation of concerns between different classes. For example, if we have a User class we can create a Logger class to monitor user actions. Anyhow, the logger's methods will have to be called explicitly somewhere inside the user method to perform the logging. The problem with this design is that we have to add service code lines to the user's methods that are not linked to thier core buisness logic. In addition to that, we would probably want to use the logger in many other methods and class, making us to write the same code over and over again. This is where AOP comes into play. We can use it to increase modularity by adding additional behavior (logging, security, transactions) without dirty the buisness code itself. It allows us to write separate pointcut specifications that specify what happens before or after the target-method executes! For example we can say: "log all methods calls that are annotated with @ log' or "run encryption code on data returned by methods which their names start with 'secured' ". Before diving into implementation details, we have to get familiar with AOP terminology:

  • Aspect: is a special class the defines advices. In other words, it has methods that add additional behavior to target-method.
  • Joinpoint: a point in the app where method of an aspect is triggered. In Spring joinpoint is always the execution of a method.
  • Advice: method of an aspect class that is called at joinpoints.
  • Pointcut: defines where an advice needs to be called. In other words, in which joinpoints an advice needs to be called.

Spring AOP & AspectJ

In order to use AspectJ, include these 3 dependencies to pom.xml

		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
			<version>1.8.12</version>
		</dependency>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>1.8.12</version>
		</dependency>
		<dependency>
			<groupId>aopalliance</groupId>
			<artifactId>aopalliance</artifactId>
			<version>1.0</version>
		</dependency>

Spring provides five kinds of advice

  1. @Before - run advice before the execution of join point.
  2. @After - run advice after the execution of join point (obviously). The advice will be executed both after method return and exception throw.
  3. @AfterReturning – only after regular return of method.
  4. @AfterThrowing – only after exception is thrown.
  5. @Around – wraps the join point method and is executed before and after it. Springs supports both annotation based aspect and XML based aspect to implement aspects. In this tutorial, I will go over the annotation style library called AspectJ. If you are interested in the XML schema I suggest you start googling. To start feeling it, let's see some code examples: In our app we have an Apartment class like so:
Public class Apartment {
	private String id;
	private int price = 0;
	
	public Apartment(String id){
		this.id = id;
	}
	public void getID(){
		return id;	
	}
	public void setPrice(int price){
		if(price < 0){
			throw new RuntimeException();
		}else{
			this.price = price;
		}
	}
}

Our ambition is to create a simple logger to track apartments usage, Let's do it!

@Component
@Aspect
@EnableAspectJAutoProxy
Public class LoggerAspect{
	@AfterReturning("execution(path.to.Apartment.*(*)")
	public void logAfter(JoinPoint jp){
System.out.println(jp.getSignature().getName() +" in " + jp.getTarget().getClass() + " finished successfully");
	}
	
	@AfterThrowing("within(path.to.Apartment)")
	public void logFailures(JoinPoint jp){
System.out.prinln(jp.getSignature().getName()+" with args: "+jp.getArgs()+"  in " + jp.getTarget().getClass() + " failed")
	}
}

Here we've created a simple aspect class that logs every successful method execution of the Apartment class. The advice can recieve a JoinPoint object as parameter which can help dredge useful information regards the joinpoint object. We have also created an aspect that logs every method of Apartment class each time it throws an exception. Few things to note:

  • the aspect is annotated with @Component to define it as Spring Bean.
  • the aspect is annotated with @Asppect mark Spring this is a special class.
  • the aspect is also annotated with @EnableAspectJAutoProxy, to enable AOP.
  • pointcut definition can include regular expressions.
  • the 'execution' keyword in the pointcut definition means that the advice is triggered on every execution of the applicable target-method.
  • the 'within' keyword in the pointcut defenition means that the advice is triggered within every method call of the specified class. Let's add another advice to our logger:
@Component
@Aspect
@EnableAspectJAutoProxy
Public class LoggerAspect{
	//…
	@Around(@annotation(path.to.anotation.TimeMeasured)")
	Public Object logTime(ProceedingJoinPoint pjp)
		int startMillies = System.currentTimeMillis();
		try{
			value = pjp.proceed();
		} catch( Throwable e) {
			e.printStackTrace();
		}
		Int endMillies = System.currentTimeMillis();
System.out.prinln(jpj.getSignature().getName() + " took " + (endMillies  - startMillies) + " milliseconds.");
		Return value;
	}
}

@Around advice is required to include ProceedingJoinPoint as parameter. The proceed() method invokes the target joinpoint. It is also the advice responsibility to return the target's value. So basically, the advice precede and follows the target-method execution. I hope you noticed the use of @annotation in the pointcut definition. In this context it means "run the advice every time you see a method annotated with @TimeMeasured". Spring AspectJ can only work on Spring Beans, so we have to always remember to define the joinpoint classes as Beans.

lastly we can run our application:

	//Config.class is some random class configuring which package should be scanned by Spring
	ApplicationContext context = new AnnotationConfigurationApplicationContext(Config.class)
	Apartment apartment = context.getBean("apartment",1);
	
	//.. do some things with apartment
	
	context.close();
Clone this wiki locally