Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions 3-0-spring-framework/3-0-0-hello-spring-framework/README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# <img src="https://raw.githubusercontent.com/bobocode-projects/resources/master/image/logo_transparent_background.png" height=50/>Hello ApplicationContext exercise :muscle:
Improve your *Spring ApplicationContext* Java configuration skills
### Task
The task is to **configure `ApplicationContext`** that contains `AccountService` bean, `AccountDao` bean
and `TestDataGenerator` bean. Your job is to follow the instructions in the *todo* section and **implement
a proper configuration.**

To verify your configuration, run `AppConfigTest.java`


### Pre-conditions :heavy_exclamation_mark:
You're supposed to be familiar with *Spring IoC* and *Dependency injection*

### How to start :question:
* Just clone the repository and start implementing the **todo** section, verify your changes by running tests
* If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source)
* Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation

### Related materials :information_source:
* [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/>

49 changes: 49 additions & 0 deletions 3-0-spring-framework/3-0-0-hello-spring-framework/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?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">
<parent>
<artifactId>3-0-spring-framework</artifactId>
<groupId>com.bobocode</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>3-0-0-hello-spring-framework</artifactId>

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>com.bobocode</groupId>
<artifactId>spring-framework-exercises-util</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.24</version>
</dependency>
<dependency>
<groupId>com.bobocode</groupId>
<artifactId>spring-framework-exercises-model</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.bobocode.config;

import com.bobocode.TestDataGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
import org.springframework.stereotype.Component;

/**
* This class specify application context configuration. Basically, it's all about which instances of which classes
* should be created and registered in the context. An instance that is registered in the context is called 'bean'.
* <p>
* To tell the container, which bean should be created, you could either specify packages to scan using @{@link ComponentScan},
* or declare your own beans using @{@link Bean}. When you use @{@link ComponentScan} the container will discover
* specified package and its sub-folders, to find all classes marked @{@link Component}.
* <p>
* If you want to import other configs from Java class or XML file, you could use @{@link Import}
* and @{@link ImportResource} accordingly
* <p>
* todo 1: make this class a Spring configuration class
* todo 2: enable component scanning for dao and service packages
* todo 3: provide explicit configuration for a bean of type {@link TestDataGenerator} with name "dataGenerator" in this class.
* hint: use method creation approach with @Bean annotation;
* todo 4: Don't specify bean name "dataGenerator" explicitly
*/

public class ApplicationConfig {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.bobocode.dao;

import com.bobocode.model.Account;

import java.util.List;

/**
* Defines an API for {@link Account} data access object (DAO).
*/
public interface AccountDao {
List<Account> findAll();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.bobocode.dao;

import com.bobocode.TestDataGenerator;
import com.bobocode.model.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toList;

/**
* This class should be marked with @{@link Component}, thus Spring container will create an instance
* of {@link FakeAccountDao} class, and will register it the context.
* <p>
* todo: configure this class as Spring component with bean name "accountDao"
* todo: use explicit (with {@link Autowired} annotation) constructor-based dependency injection for specific bean
*/

public class FakeAccountDao implements AccountDao {
private List<Account> accounts;

public FakeAccountDao(TestDataGenerator testDataGenerator) {
this.accounts = Stream.generate(testDataGenerator::generateAccount)
.limit(20)
.collect(toList());
}

@Override
public List<Account> findAll() {
return accounts;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.bobocode.service;

import com.bobocode.dao.AccountDao;
import com.bobocode.model.Account;

import java.util.Comparator;
import java.util.List;

/**
* Provides service API for {@link Account}.
* <p>
* todo: configure {@link AccountService} bean implicitly using special annotation for service classes
* todo: use implicit constructor-based dependency injection (don't use {@link org.springframework.beans.factory.annotation.Autowired})
*/

public class AccountService {
private final AccountDao accountDao;

public AccountService(AccountDao accountDao) {
this.accountDao = accountDao;
}

public Account findRichestAccount() {
List<Account> accounts = accountDao.findAll();
return accounts.stream()
.max(Comparator.comparing(Account::getBalance))
.get();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package com.bobocode;

import com.bobocode.config.ApplicationConfig;
import com.bobocode.dao.AccountDao;
import com.bobocode.dao.FakeAccountDao;
import com.bobocode.service.AccountService;
import org.junit.jupiter.api.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertNotNull;

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class ApplicationConfigTest {

@Test
@Order(1)
@DisplayName("Config class is marked as @Configuration")
void configClassIsMarkedAsConfiguration() {
Configuration configuration = ApplicationConfig.class.getAnnotation(Configuration.class);

assertNotNull(configuration);
}

@Test
@Order(2)
@DisplayName("@ComponentScan is enabled")
void componentScanIsEnabled() {
ComponentScan componentScan = ApplicationConfig.class.getAnnotation(ComponentScan.class);

assertNotNull(componentScan);
}

@Test
@Order(3)
@DisplayName("@ComponentScan configured for \"dao\" and \"service\" packages")
void componentScanPackagesAreSpecified() {
ComponentScan componentScan = ApplicationConfig.class.getAnnotation(ComponentScan.class);
String[] packages = componentScan.basePackages();
if (packages.length == 0) {
packages = componentScan.value();
}
assertThat(packages).containsExactlyInAnyOrder("com.bobocode.dao", "com.bobocode.service");
}

@Test
@Order(4)
@DisplayName("DataGenerator bean is configured in method marked with @Bean")
void dataGeneratorBeanIsConfiguredExplicitly() {
Method[] methods = ApplicationConfig.class.getMethods();
Method testDataGeneratorBeanMethod = findTestDataGeneratorBeanMethod(methods);

assertNotNull(testDataGeneratorBeanMethod);
}

@Test
@Order(5)
@DisplayName("DataGenerator bean name is not specified explicitly")
void dataGeneratorBeanNameIsNotSpecifiedExplicitly() {
Method[] methods = ApplicationConfig.class.getMethods();
Method testDataGeneratorBeanMethod = findTestDataGeneratorBeanMethod(methods);
Bean bean = testDataGeneratorBeanMethod.getDeclaredAnnotation(Bean.class);

assertThat(bean.name().length).isEqualTo(0);
assertThat(bean.value().length).isEqualTo(0);
}

@Test
@Order(6)
@DisplayName("FakeAccountDao is configured as @Component")
void fakeAccountDaoIsConfiguredAsComponent() {
Component component = FakeAccountDao.class.getAnnotation(Component.class);

assertNotNull(component);
}

@Test
@Order(7)
@DisplayName("AccountService is configured as @Service")
void accountServiceIsConfiguredAsService() {
Service service = AccountService.class.getAnnotation(Service.class);

assertNotNull(service);
}

@Test
@Order(8)
@DisplayName("AccountService bean name is not specified explicitly")
void accountServiceBeanNameIsNotSpecifiedExplicitly() {
Service service = AccountService.class.getAnnotation(Service.class);

assertThat(service.value()).isEqualTo("");
}

@Test
@Order(9)
@DisplayName("AccountService doesn't use @Autowired")
void accountServiceDoesNotUseAutowired() throws NoSuchMethodException {
Annotation[] annotations = AccountService.class.getConstructor(AccountDao.class).getDeclaredAnnotations();

assertThat(annotations.length).isEqualTo(0);
}

private Method findTestDataGeneratorBeanMethod(Method[] methods) {
for (Method method : methods) {
if (method.getReturnType().equals(TestDataGenerator.class)
&& method.getDeclaredAnnotation(Bean.class) != null) {
return method;
}
}
return null;
}
}
Loading