Skip to content

Spring в двух словах

Синопсис

В прошлых двух постах Простое spring приложение (собрано shell скриптом) и Простое Spring приложение с использованием аннотации @Autowired мы обсудили что такое Spring, но при этом мы слишком глубоко погрузились в детали, поэтому эти посты получились слишком длинные. В этом посте я хочу максимально кратко в двух словах рассказать, что такое Spring и напишем небольшое спринговое приложение и избежим всех этих тонкостей со сборкой и запуском приложения из командной строки, поэтому воспользуемся мавеном.

Готовый проект можно взять на gitHub: https://github.com/dev-blogs/simple-spring-app-built-by-maven

Что такое Spring в двух словах

В двух словах спринг — это такой гигантский фэктори-паттерн который представляет из себя контейнер (или коробку) с бинами. Когда спринг запускается, он читает конфигурационный файл context.xml, создает бины которые описаны в этом конфиге (или проаннотированные аннотацией @Component стоящей над классами) и кладет их в контейнер (коробку):
11
На этом рисунки бины из себя представляют геометрические фигуры. Эти объекты спринг создает в едином экземпляре которые существуют на протяжении всего выполнения программы, то есть если мы вытащим из контекста (коробки) объект, а потом вытащим тот же объект повторно, то мы получим тот же самый экземпляр.
Создание спрингового контекста в коде выглядит так:

ConfigurableApplicationContext context;
context = new ClassPathXmlApplicationContext("context.xml");

После того как все бины сконфигурированы и положены в контекст (в коробку) мы можем их вытаскивать для своих нужд:
2
На этом рисунке мы вытаскиваем бин конус. В коде это выглядит так:

Сone cone = context.getBean(Cone.class);

То есть сначала мы создаем объект контекста обычным способом, то есть через оператор new а потом, теоретически, про оператор new можно забыть, все необходимые объекты мы запрашиваем у спринга выражением context.getBean(Some.class)
После того как мы вытащили бин из контекста, мы можем работать с бином:
3
В коде это выглядит так:

cone.move(23, 53);
cone.ratate(15);
double volume = cone.getVolume();
System.out.println("The volume of cone is " + cone);

То есть мы можем его вертеть, крутить, перемещать, запрашивать его объем и пр. А теперь давайте посмотрим как это все работает на небольшом приложении. Приложение такое же как и в предыдущих постах, спринг будет создавать и конфигурировать геометрические фигуры и выводить нам их площадь.

Структура проекта

simpleSpringAppBuiltByMaven
    ├──src
    │   ├──main
    │   │   ├──java
    │   │   │   └──com
    │   │   │       └──devblogs
    │   │   │           ├──component
    │   │   │           │   ├──figure
    │   │   │           │   │   ├──Figure.java
    │   │   │           │   │   ├──Circle.java
    │   │   │           │   │   └──Rectangle.java
    │   │   │           │   └──Print.java
    │   │   │           └──execute
    │   │   │               └──Execute.class
    │   │   └──resources
    │   │       └──context.xml
    │   └──test
    │       └──java
    │           └──com
    │               └──devblogs
    │                   └──execute
    │                       ├──AbstractTest.java
    │                       ├──CircleTest.java
    │                       └──RectangleTest.java
    └──pom.xml

Java code

Figure.java

package com.devblogs.component.figure;

public abstract class Figure {
    private String name;

    public Figure(String name) {
        this.name = name;
        System.out.println("Bean " + name + " has been created");
    }

    public String getName() {
        return this.name;
    }

    public abstract double getSquare();
}

Circle.java

package com.devblogs.component.figure;

import org.springframework.stereotype.Component;

@Component
public class Circle extends Figure {
    private int radius;
    public static double PI = 3.1415;

    public Circle() {
    	super("circle");
    }

    public Circle(String name, int radius) {
        super(name);
        this.radius = radius;
    }

    public void setRadius(int radius) {
    	this.radius = radius;
    }

    public double getSquare() {
        return PI*this.radius*this.radius;
    }
}

Rectangle.java

package com.devblogs.component.figure;

import org.springframework.stereotype.Component;

@Component
public class Rectangle extends Figure {
    private int length;
    private int width;

    public Rectangle() {
    	super("rectangle");
    }

    public Rectangle(String name, int length, int width) {
        super(name);
        this.length = length;
        this.width = width;
    }

    public void setWidth(int width) {
    	this.width = width;
    }

    public void setLength(int length) {
    	this.length = length;
    }

    public double getSquare() {
        return this.length*this.width;
    }
}

Print.java

Мы не помечаем бин print аннотацией @Component, так как спринг столкнется с неопределенностью когда надо будет просетить поле Figure, поэтому этот бин настраивается в конфигурационном файле вручную.

package com.devblogs.component;

import com.devblogs.component.figure.Figure;

public class Print {
    private Figure figure;

    public Print() {
        System.out.println("Bean print is being created");
    }

    public void setFigure(Figure figure) {
    	this.figure = figure;
    }

    public void showSquare() {
        System.out.println("Square of " + this.figure.getName() + " is " + this.figure.getSquare());
    }
}

App.java

package com.devblogs.execute;

import com.devblogs.component.Print;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String [] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
        Print print = (Print) context.getBean("print");
        print.showSquare();
    }
}

Тесты

Тесты напишем двумя способами. В первом случае спринговый контекст будем поднимать вручную, а во втором случае используя аннотацию @ContextConfiguration.

Чтобы в тестах вручную поднять контекст, нужно добавить код который его создает. Лучшим таким место будет метод проаннотированный аннотацией @BeforeClass. Метод аннотированный @BeforeClass вызывается до того, как создастся инстанс тестового класса, то есть если мы поместим в этот метод создание спрингового контекста, то можно быть уверенным, что к моменту создания инстанса самого тестового класса, контекст уже будет готов. Так как этот метод вызывается до создания инстанса тестового класса, то этот метод ясное дело должен быть статическим.
С методом, где будем создавать спринговый контекст, определились теперь подумаем вот о чем. Так как в мы потом захотим добавить еще несколько тестовых классов, то зачем плодить спринговые контексты в каждом из них, когда можно создать общий для всех тестовых классов класс и вынести туда общую логику. Так что добавим класс AbstractTest и вынесем туда код, который поднимает спринговый контекст.

AbstractTest.java

package com.devblogs.execute;

import org.junit.BeforeClass;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public abstract class AbstractTest {
	protected static ConfigurableApplicationContext context = null;

	@BeforeClass
	public static void beforeClass() {
		context = new ClassPathXmlApplicationContext("context.xml");
	}
}

Класс RectangleTest наследуется от класса AbstractTest в котором определена логика создания спрингового контекста. В класс RectangleTest происходит непосредственное извлечение нужного бина из контекста, который пришел из класса пэрэнта.

RectangleTest.java

package com.devblogs.execute;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import com.devblogs.component.figure.Rectangle;

public class RectangleTest extends AbstractTest {
	private Rectangle rectangle;

	@Before
	public void setUp() throws Exception {
		rectangle = context.getBean(Rectangle.class);
		rectangle.setWidth(10);
		rectangle.setLength(10);
	}

	@Test
	public void test() {
		Assert.assertEquals(100, (int) rectangle.getSquare());
	}
}

Тестовый класс CircleTest немного проще, так как вся логика по созданию спрингового контекста и вытягиванию из контекста бина спрятана за аннотациями @ContextConfiguration и @Autowired и абстрактным классом AbstractJUnit4SpringContextTests. Класс CircleTest наследуется от спрингового класса AbstractJUnit4SpringContextTests в который просечивается спринговый контекст, который мы указываем аннотацией @ContextConfiguration. Далее спринг находит в тестовом классе поле проаннотированное аннотацией @Autowired и просечивает в него нужный бин. Далее в методе проаннотированном аннотацией @Test работаем с бином как обычно.

CircleTest.java

package com.devblogs.execute;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.CircleTest;
import com.devblogs.component.figure.Circle;

@ContextConfiguration("classpath:context.xml")
public class CircleTest extends AbstractJUnit4SpringContextTests {
	@Autowired
	private Circle circle;

	@Before
	public void init() {
		circle.setRadius(10);
	}

	@Test
	public void test() {
		Assert.assertEquals(314, (int) circle.getSquare());
	}
}

Конфиг

Бин print мы определяем в конфигурационном файле. Это связано с тем, что в этом бине абстрактное поле figure и спринг самостоятельно не может определить, что мы хотим в него положить окружность или прямоугольник, поэтому мы настраиваем бин вручную в конфигурационном файле с явным указанием фигуры-бина, который мы хотим просетить в бин print. Все остальные бины сканируются спрингом и поднимаются автоматически.

context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context.xsd">

 	<context:component-scan base-package="com.devblogs.component"/>

 	<bean id="print" class="com.devblogs.component.Print">
        <property name="figure" ref="circle" />
    </bean>

</beans>

pom.xml

<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.task</groupId>
	<artifactId>simpleSpringAppBuiltByMaven</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>simpleSpringApp</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<spring.version>3.2.7.RELEASE</spring.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.11</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
</project>

Сборка и запуск тестов

Запускаем только тесты командой mvn test:

mvn test
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building simpleSpringApp 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ simpleSpringAppBuiltByMaven ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:2.5.1:compile (default-compile) @ simpleSpringAppBuiltByMaven ---
[INFO] Compiling 5 source files to E:\practice\spring from scratch\simple-spring-app-built-by-maven\target\classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ simpleSpringAppBuiltByMaven ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory E:\practice\spring from scratch\simple-spring-app-built-by-maven\src\test\resources
[INFO]
[INFO] --- maven-compiler-plugin:2.5.1:testCompile (default-testCompile) @ simpleSpringAppBuiltByMaven ---
[INFO] Compiling 3 source files to E:\practice\spring from scratch\simple-spring-app-built-by-maven\target\test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ simpleSpringAppBuiltByMaven ---
[INFO] Surefire report directory: E:\practice\spring from scratch\simple-spring-app-built-by-maven\target\surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.devblogs.execute.CircleTest
Apr 20, 2016 4:35:11 PM org.springframework.test.context.TestContextManager retrieveTestExecutionListeners
INFO: Could not instantiate TestExecutionListener class [org.springframework.test.context.web.ServletTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their dependencies) available.
Apr 20, 2016 4:35:11 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [context.xml]
Apr 20, 2016 4:35:12 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.GenericApplicationContext@7edd14d9: startup date [Wed Apr 20 16:35:12 EEST 2016]; root of context hierarchy
Apr 20, 2016 4:35:12 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@53ce3388: defining beans [circle,rectangle,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,print,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy
Bean circle has been created
Bean rectangle has been created
Bean print is being created
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.539 sec
Running com.devblogs.execute.RectangleTest
Apr 20, 2016 4:35:12 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@5b421fc8: startup date [Wed Apr 20 16:35:12 EEST 2016]; root of context hierarchy
Apr 20, 2016 4:35:12 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [context.xml]
Apr 20, 2016 4:35:12 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@43f52b8: defining beans [circle,rectangle,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,print,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy
Bean circle has been created
Bean rectangle has been created
Bean print is being created
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.034 sec

Results :

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.035s
[INFO] Finished at: Wed Apr 20 16:35:12 EEST 2016
[INFO] Final Memory: 11M/244M
[INFO] ------------------------------------------------------------------------

В выводе должно быть Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, это значит, что два теста завершились успешно.

Поделиться в социальных сетях

Опубликовать в Google Plus
Опубликовать в LiveJournal
Опубликовать в Мой Мир
Опубликовать в Одноклассники
Опубликовать в Яндекс

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *