Skip to content

Commit 62b251f

Browse files
authored
Merge pull request #10 from bobocode-projects/GP-67_new_hello-spring-framework_(for_web)_exercise_MAIN
Gp 67 new hello spring framework (for web) exercise main
2 parents da63885 + 661c9c8 commit 62b251f

File tree

15 files changed

+498
-0
lines changed

15 files changed

+498
-0
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# <img src="https://raw.githubusercontent.com/bobocode-projects/resources/master/image/logo_transparent_background.png" height=50/>Hello ApplicationContext exercise :muscle:
2+
Improve your *Spring ApplicationContext* Java configuration skills
3+
### Task
4+
The task is to **configure `ApplicationContext`** that contains `AccountService` bean, `AccountDao` bean
5+
and `TestDataGenerator` bean. Your job is to follow the instructions in the *todo* section and **implement
6+
a proper configuration.**
7+
8+
To verify your configuration, run `AppConfigTest.java`
9+
10+
11+
### Pre-conditions :heavy_exclamation_mark:
12+
You're supposed to be familiar with *Spring IoC* and *Dependency injection*
13+
14+
### How to start :question:
15+
* Just clone the repository and start implementing the **todo** section, verify your changes by running tests
16+
* If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source)
17+
* Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation
18+
19+
### Related materials :information_source:
20+
* [Spring IoC basics tutorial](https://github.com/boy4uck/spring-framework-tutorial/tree/master/spring-framework-ioc-basics)<img src="https://raw.githubusercontent.com/bobocode-projects/resources/master/image/logo_transparent_background.png" height=20/>
21+
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<artifactId>3-0-spring-framework</artifactId>
7+
<groupId>com.bobocode</groupId>
8+
<version>1.0-SNAPSHOT</version>
9+
</parent>
10+
<modelVersion>4.0.0</modelVersion>
11+
12+
<artifactId>3-0-0-hello-spring-framework</artifactId>
13+
14+
<dependencies>
15+
<dependency>
16+
<groupId>org.springframework</groupId>
17+
<artifactId>spring-context</artifactId>
18+
<version>5.2.12.RELEASE</version>
19+
</dependency>
20+
<dependency>
21+
<groupId>org.springframework</groupId>
22+
<artifactId>spring-test</artifactId>
23+
<version>5.2.12.RELEASE</version>
24+
</dependency>
25+
<dependency>
26+
<groupId>com.bobocode</groupId>
27+
<artifactId>spring-framework-exercises-util</artifactId>
28+
<version>1.0-SNAPSHOT</version>
29+
</dependency>
30+
<dependency>
31+
<groupId>org.hamcrest</groupId>
32+
<artifactId>hamcrest-all</artifactId>
33+
<version>1.3</version>
34+
<scope>test</scope>
35+
</dependency>
36+
<dependency>
37+
<groupId>org.slf4j</groupId>
38+
<artifactId>slf4j-simple</artifactId>
39+
<version>1.7.24</version>
40+
</dependency>
41+
<dependency>
42+
<groupId>com.bobocode</groupId>
43+
<artifactId>spring-framework-exercises-model</artifactId>
44+
<version>1.0-SNAPSHOT</version>
45+
<scope>compile</scope>
46+
</dependency>
47+
</dependencies>
48+
49+
</project>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.bobocode.config;
2+
3+
import com.bobocode.TestDataGenerator;
4+
import org.springframework.context.annotation.Bean;
5+
import org.springframework.context.annotation.ComponentScan;
6+
import org.springframework.context.annotation.Import;
7+
import org.springframework.context.annotation.ImportResource;
8+
import org.springframework.stereotype.Component;
9+
10+
/**
11+
* This class specify application context configuration. Basically, it's all about which instances of which classes
12+
* should be created and registered in the context. An instance that is registered in the context is called 'bean'.
13+
* <p>
14+
* To tell the container, which bean should be created, you could either specify packages to scan using @{@link ComponentScan},
15+
* or declare your own beans using @{@link Bean}. When you use @{@link ComponentScan} the container will discover
16+
* specified package and its sub-folders, to find all classes marked @{@link Component}.
17+
* <p>
18+
* If you want to import other configs from Java class or XML file, you could use @{@link Import}
19+
* and @{@link ImportResource} accordingly
20+
* <p>
21+
* todo 1: make this class a Spring configuration class
22+
* todo 2: enable component scanning for dao and service packages
23+
* todo 3: provide explicit configuration for a bean of type {@link TestDataGenerator} with name "dataGenerator" in this class.
24+
* hint: use method creation approach with @Bean annotation;
25+
* todo 4: Don't specify bean name "dataGenerator" explicitly
26+
*/
27+
28+
public class ApplicationConfig {
29+
30+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.bobocode.dao;
2+
3+
import com.bobocode.model.Account;
4+
5+
import java.util.List;
6+
7+
/**
8+
* Defines an API for {@link Account} data access object (DAO).
9+
*/
10+
public interface AccountDao {
11+
List<Account> findAll();
12+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.bobocode.dao;
2+
3+
import com.bobocode.TestDataGenerator;
4+
import com.bobocode.model.Account;
5+
import org.springframework.beans.factory.annotation.Autowired;
6+
import org.springframework.stereotype.Component;
7+
8+
import java.util.List;
9+
import java.util.stream.Stream;
10+
11+
import static java.util.stream.Collectors.toList;
12+
13+
/**
14+
* This class should be marked with @{@link Component}, thus Spring container will create an instance
15+
* of {@link FakeAccountDao} class, and will register it the context.
16+
* <p>
17+
* todo: configure this class as Spring component with bean name "accountDao"
18+
* todo: use explicit (with {@link Autowired} annotation) constructor-based dependency injection for specific bean
19+
*/
20+
21+
public class FakeAccountDao implements AccountDao {
22+
private List<Account> accounts;
23+
24+
public FakeAccountDao(TestDataGenerator testDataGenerator) {
25+
this.accounts = Stream.generate(testDataGenerator::generateAccount)
26+
.limit(20)
27+
.collect(toList());
28+
}
29+
30+
@Override
31+
public List<Account> findAll() {
32+
return accounts;
33+
}
34+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.bobocode.service;
2+
3+
import com.bobocode.dao.AccountDao;
4+
import com.bobocode.model.Account;
5+
6+
import java.util.Comparator;
7+
import java.util.List;
8+
9+
/**
10+
* Provides service API for {@link Account}.
11+
* <p>
12+
* todo: configure {@link AccountService} bean implicitly using special annotation for service classes
13+
* todo: use implicit constructor-based dependency injection (don't use {@link org.springframework.beans.factory.annotation.Autowired})
14+
*/
15+
16+
public class AccountService {
17+
private final AccountDao accountDao;
18+
19+
public AccountService(AccountDao accountDao) {
20+
this.accountDao = accountDao;
21+
}
22+
23+
public Account findRichestAccount() {
24+
List<Account> accounts = accountDao.findAll();
25+
return accounts.stream()
26+
.max(Comparator.comparing(Account::getBalance))
27+
.get();
28+
}
29+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package com.bobocode;
2+
3+
import com.bobocode.config.ApplicationConfig;
4+
import com.bobocode.dao.AccountDao;
5+
import com.bobocode.dao.FakeAccountDao;
6+
import com.bobocode.service.AccountService;
7+
import org.junit.jupiter.api.*;
8+
import org.springframework.context.annotation.Bean;
9+
import org.springframework.context.annotation.ComponentScan;
10+
import org.springframework.context.annotation.Configuration;
11+
import org.springframework.stereotype.Component;
12+
import org.springframework.stereotype.Service;
13+
14+
import java.lang.annotation.Annotation;
15+
import java.lang.reflect.Method;
16+
17+
import static org.assertj.core.api.Assertions.assertThat;
18+
import static org.junit.jupiter.api.Assertions.assertNotNull;
19+
20+
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
21+
public class ApplicationConfigTest {
22+
23+
@Test
24+
@Order(1)
25+
@DisplayName("Config class is marked as @Configuration")
26+
void configClassIsMarkedAsConfiguration() {
27+
Configuration configuration = ApplicationConfig.class.getAnnotation(Configuration.class);
28+
29+
assertNotNull(configuration);
30+
}
31+
32+
@Test
33+
@Order(2)
34+
@DisplayName("@ComponentScan is enabled")
35+
void componentScanIsEnabled() {
36+
ComponentScan componentScan = ApplicationConfig.class.getAnnotation(ComponentScan.class);
37+
38+
assertNotNull(componentScan);
39+
}
40+
41+
@Test
42+
@Order(3)
43+
@DisplayName("@ComponentScan configured for \"dao\" and \"service\" packages")
44+
void componentScanPackagesAreSpecified() {
45+
ComponentScan componentScan = ApplicationConfig.class.getAnnotation(ComponentScan.class);
46+
String[] packages = componentScan.basePackages();
47+
if (packages.length == 0) {
48+
packages = componentScan.value();
49+
}
50+
assertThat(packages).containsExactlyInAnyOrder("com.bobocode.dao", "com.bobocode.service");
51+
}
52+
53+
@Test
54+
@Order(4)
55+
@DisplayName("DataGenerator bean is configured in method marked with @Bean")
56+
void dataGeneratorBeanIsConfiguredExplicitly() {
57+
Method[] methods = ApplicationConfig.class.getMethods();
58+
Method testDataGeneratorBeanMethod = findTestDataGeneratorBeanMethod(methods);
59+
60+
assertNotNull(testDataGeneratorBeanMethod);
61+
}
62+
63+
@Test
64+
@Order(5)
65+
@DisplayName("DataGenerator bean name is not specified explicitly")
66+
void dataGeneratorBeanNameIsNotSpecifiedExplicitly() {
67+
Method[] methods = ApplicationConfig.class.getMethods();
68+
Method testDataGeneratorBeanMethod = findTestDataGeneratorBeanMethod(methods);
69+
Bean bean = testDataGeneratorBeanMethod.getDeclaredAnnotation(Bean.class);
70+
71+
assertThat(bean.name().length).isEqualTo(0);
72+
assertThat(bean.value().length).isEqualTo(0);
73+
}
74+
75+
@Test
76+
@Order(6)
77+
@DisplayName("FakeAccountDao is configured as @Component")
78+
void fakeAccountDaoIsConfiguredAsComponent() {
79+
Component component = FakeAccountDao.class.getAnnotation(Component.class);
80+
81+
assertNotNull(component);
82+
}
83+
84+
@Test
85+
@Order(7)
86+
@DisplayName("AccountService is configured as @Service")
87+
void accountServiceIsConfiguredAsService() {
88+
Service service = AccountService.class.getAnnotation(Service.class);
89+
90+
assertNotNull(service);
91+
}
92+
93+
@Test
94+
@Order(8)
95+
@DisplayName("AccountService bean name is not specified explicitly")
96+
void accountServiceBeanNameIsNotSpecifiedExplicitly() {
97+
Service service = AccountService.class.getAnnotation(Service.class);
98+
99+
assertThat(service.value()).isEqualTo("");
100+
}
101+
102+
@Test
103+
@Order(9)
104+
@DisplayName("AccountService doesn't use @Autowired")
105+
void accountServiceDoesNotUseAutowired() throws NoSuchMethodException {
106+
Annotation[] annotations = AccountService.class.getConstructor(AccountDao.class).getDeclaredAnnotations();
107+
108+
assertThat(annotations.length).isEqualTo(0);
109+
}
110+
111+
private Method findTestDataGeneratorBeanMethod(Method[] methods) {
112+
for (Method method : methods) {
113+
if (method.getReturnType().equals(TestDataGenerator.class)
114+
&& method.getDeclaredAnnotation(Bean.class) != null) {
115+
return method;
116+
}
117+
}
118+
return null;
119+
}
120+
}

0 commit comments

Comments
 (0)