0%

Most of the contents are excerpt from “Modern Java in Action”

What are streams?

Streams are an update to the Java API that let you manipulate collections of data
in a declarative way (you express a query rather than code an ad hoc implementation
for it.) In addition, streams can be processed in parallel transparently, without you having to write any multithreaded code.

  • Before (Java 7)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
List<Dish> lowCaloricDishes = new ArrayList<>();
for(Dish dish: menu) {
if(dish.getCalories() < 400) {
lowCaloricDishes.add(dish); // Filters the elements using an accumulator
}
}

Collections.sort(lowCaloricDishes, new Comparator<Dish>() { // Sorts the dishes with an anonymous class
public int compare(Dish dish1, Dish dish2) {
return Integer.compare(dish1.getCalories(), dish2.getCalories());
}
});

List<String> lowCaloricDishesName = new ArrayList<>();
for(Dish dish: lowCaloricDishes) {
lowCaloricDishesName.add(dish.getName()); // Processes the sorted list to select the names of dishes
}
  • After (Java 8)
1
2
3
4
5
6
7
8
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
List<String> lowCaloricDishsesName =
menu.stream()
.filter(d -> d.getCalories() < 400)
.sorted(comparing(Dish::getCalories))
.map(Dish::getName)
.collect(toList());

You can see that the new approach offers several immediate benefits from a software
engineering point of view:

  • The code is written in a declarative way: you specify what you what to achieve
    as opposed to specifying how to implement an operation (using control-flow blocks such as loops and if conditions)

  • You chain together several building-block operations to express a complicated
    data-processing pipeline while keeping your code readable and its intent clear.

The Stream API in Java 8 lets you write code that’s

  • Declarative – More concise and readable
  • Composable – Greater flexibility
  • Parallelizable – Better performance

Stream operations

Stream operations that can be connected are called intermediate operations, and oerations that close a stream are called terminal operations.

Intermediate operations

Intermediate operations such as filer or sorted return another stream as the return type. This allows the operations to be connected to form a query.
What’s important is that inermediate operations don’t perform any processing until a terminal operations can usually be merged and processed into a single pass by terminal operation.

Terminal operations

Terminal operations produce a result from a stream pipleline.
A result is any non-stream value such as List, an Integer, or even void.
For example, in the following pipeline, forEach is terminal operation that returns
void and applies a lambda to each object. Passing System.out.println to forEach asks it to print every object in the stream.

stream().forEach(System.out::println);

Working with streams

Filtering

Filtering with a predicate

The Stream interface supports a filter method. This operations takes as argument a predicate (a function returning boolean) and returns a stream including all elements that match the predicate.

1
2
3
4
5
import static java.util.stream.Collectors.toList;
List<Dish> vegetarianDishes =
menu.stream()
.filter(Dish::isVegetarian) // Use a method reference to check if dish is vegetarian friendly.
.collect(toList());

Filtering unique elements

Streams also support a method called distinct that returns a stream with unique
elements (according to the implementation of the hashcode and equals methods of
the objects produced by the stream).

1
2
3
4
5
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);

Truncating a stream

Streams support the limit(n) methods, which returns another stream that’s no
longer thant a given size. The requested size is passed as argument to limit.
If the stream is ordered, the first elements are returned up to a maximum of n.

Skipping elements

Streams support the skip(n) method to return a stream that discards the first n
elements. If the stream has fewer than n elements, an empty stream is returned.
Note that limit(n) and skip(n) are complementary.

Mapping

A common data processing idion is to select information from certain objects.
For example, in SQL you can select a particular column from a table. The Stream API
provides similar facilities through the map and flatMap methods.

Applying a function to each element of a stream

Stream support the map method, which takes a function as argument. The function is
Applied to each element, mapping it into a new element (the word mapping is used
because it has a meaning similar to transforming but with the nuance of “creating
a new version of” rather than “modifying”).
For example, in the following code you pass a method reference Dish::getName to the map method to extract the names of the dishess in the stream:

1
2
3
List<String> dishNames = menu.stream()
.map(Dish::getName)
.collect(toList());

Flattening streams

How could you return a list of all the unique characters for a list of words?
For example, given the list of words [“Hello”, “World”] -> [“H”, “e”, “l”, “o”, “W”, “r”, “d”].

1
2
3
4
words.stream()
.map(word -> word.spit(""))
.distinct()
.collect(toList());

Problem: the map method returns a String[] (an array of String) for each word.

Attempt Using Map and Arrays.stream

First, you need a stream of characters instead of a stream of arrays. There’s a method called Arrays.stream() that takes an array and produces a stream:

1
2
String[] arrayOfWord = {"Goodbye", "World"};
Stream<String> streamOfWords = Arrays.stream(arrayOfWords);

Use it in the previous pipeline to see what happens:

1
2
3
4
5
words.stream()
.map(word -> word.split(""))
.map(Arrays::stream)
.distinct()
.collect(toList());

Indeed, first convert each word into an array of its individual letters and then
make each array into a separate stream.

Using flatMap

1
2
3
4
5
6
List<String> uniqueCharacters =
words.stream()
.map(word -> word.split("")) // Converts each word into a array of its individual letters
.flatMap(Arrays::stream) // Flatten each generated stream into single stream
.distinct()
.collect(toList());

Using the flatMap method has the effect of mapping each array not with a stream
but with the contents of that stream. All the separate streams that were generated
when using map(Arrays::stream) get amalgamated – flatten into a single stream.

Finding and matching

Another common data processing idiom is finding whether some elements in a set of
data match a given property.
The Stream API provides such facilities through the allMatch, anyMatch, noneMatch, findFirst, and findAny

  • anyMatch(Predicate<T>): Checking to see if a predicate matches at least one element

  • allMatch(Predicate<T>): Checking to see if a predicate matches all elements

    1
    2
    boolean isHealthy = menu.stream()
    .allMatch(dish -> dish.getCalories() < 1000);
  • noneMatch(Predicate<T>): The opposite of allMatch is noneMatch. It ensures that no elements in the stream match the given predicate.

    1
    2
    boolean isHealth = menu.stream()
    .noneMatch(d -> d.getCalories() >= 1000);
  • findAny(): Returns an arbitrary element of the current stream.

    1
    2
    3
    4
    menu.stream()
    .filter(Dish::isVegetarian)
    .findAny() // Returns an Optional<Dish>
    .ifPresent(dish -> System.out.println(dish.getName())); // If a value is contained, it's printed; otherwise nothing happens.
  • findFirst(): Returns a first element in an encounter order.

    1
    2
    3
    4
    5
    6
    List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
    Optional<Integer> firstSquareDivisibleByThree =
    someNumbers.stream()
    .map(n -> n * n)
    .filter(n -> n % 3 == 0)
    .findFirst(); // 9

Reducing

Summing the elements

You can sum all elements of a stream as follows:

1
int sum = numbers.stream().reduce(0, (a, b) -> a + b);

reduce takes two arguments:

  • An initial value, here 0.
  • A BinaryOperator<T> to combine two elements and produce a new value; here
    you use the lambda (a, b) -> a + b.

Maximum and minimum

1
2
Optional<Integer> max = numbers.stream().reduce(Integer::max);
Optional<Integer> min = numbers.stream().reduce(Integer::min);

Intermediate and terminal operations

Operation Type Return type Type / functional interface used Function descriptor
filter Intermediate Stream<T> Predicate<T> T -> boolean
distinct Intermediate Stream<T>
takeWhile Intermediate Stream<T> Predicate<T> T -> boolean
dropWhile Intermediate Stream<T> Predicate<T> T -> boolean
skip Intermediate Stream<T> long
limit Intermediate Stream<T> long
map Intermediate Stream<R> Function<T, R> T -> R
flatMap Intermediate Stream<R> Function<T, Stream<R>> T -> Stream<R>
sorted Intermediate Stream<T> Comparator<T> (T, T) -> int
anyMatch Terminal boolean Predicate<T> T -> boolean
noneMatch Terminal boolean Predicate<T> T -> boolean
allMatch Terminal boolean Predicate<T> T -> boolean
findAny Terminal Optional<T>
findFirst Terminal Optional<T>
forEach Terminal void Consumer<T> T -> void
collect Terminal R Collector<T, A, R>
reduce Terminal Optional<T> BinaryOperator<T> (T, T) -> T
count Terminal long

Collecting data with streams

The collect is a reduction operation, like reduce, that takes as an argument
various recipes for accumulating the elements of a stream into a summary result.
These recipes are defined by a new Collector interface, so it’s important to
distinguish Collection, Collector, and collect.

Here are some example queries of what you’ll be able to do using collect and
collectors:

  • Group a list of transactions by currency to obtain the sum of the values of all
    transactions with that currency (returning a Map<Currency, Integer>)
  • Partition a list of transactions into two groups: expensive and not expensive
    (returnting a Map<Boolean, List<Transaction>>)
  • Create multilevel groupings, such as grouping transactions by cities and then
    further categorizing by whether they’re expensive of not (returning a Map<String, Map<Boolean, List<Transaction>>>)

Grouping transactions by currency in imperative style

1
2
3
4
5
6
7
8
9
10
Map<Currency, List<Transaction>> transactionsByCurrencies = new HashMap<>();
for (Transaction transaction : transactions) {
Currency currency = transaction.getCurrency(); // Extracts the Transaction’s currency
List<Transaction> transactionsForCurrency = transactionsByCurrencies.get(currency);
if (transactionsForCurrency == null) {
transactionsForCurrency = new ArrayList<>();
transactionsByCurrencies.put(currency, transactionsForCurrency);
}
transactionsForCurrency.add(transaction);
}

Using a more general Collector paramter to the collect method on stream rather
than the toList special case used in the previous chapter:

1
2
Map<Currency, List<Transaction>> transactionsByCurrencies =
transactions.stream().collect(groupingBy(Transaction::getCurrency));

Grouping

A common database operation is to group items in a set, based on one or more properties.
You can easily perform this task using a collector returned by the Collectors.groupBy factory method, as follows:

1
Map<Dish.Type, List<Dish>> dishesByType = menu.stream().collect(groupingBy(Dish::getType));

Here, you pass to the groupingBy method a Function (expressed in the form of a
method reference) extracting the corresponding Dish.Type for each Dish in the stream. We call this Function a classification function specifically because it’s used to classify the elements of the stream into different groups.

Partitioning

Partitioning is a special case of grouping: having a predicate called a partitioning function as a classification fuction. The fact that the partitioning
fuction returns a boolean means the resulting grouping Map will have Boolean as
a key type, and therefore, there can be at most two different groups – one for true and one for false.

1
2
Map<Boolean, List<Dish>> partitionedMenu =
menu.stream().collect(partitioningBy(Dish::isVegetarian));

Background: MVC model

Model-view-controller (usually known as MVC) is a software design pattern
commonly used for developing user interfaces that divides the related program logic
into three interconnected elements.

Components

MVC Process

Model

The central component of the pattern. It is the application’s dynamic data structure,
independent of the user interface. It directly manages the data, logic and rules of the application.

View

Any representation of information such as chart, diagram or table.
Mulitple view of the same informantion are possible,
such as a bar chart for management and tabular view for accountants.

Controller

Accepts input and converts it to commands for the model or view

  • The model is responsible for managing the data of the application. It receives user input from the controller.
  • The view means presentation of the model in a particular format.
  • The controller responds to the user input and performs interactions on the data model objects.
    The controller receives the input, optionally validates it and then passes the input to the model.

웹 페이지는 중복 개발되는 요소가 존재한다.

Controller에서 중복으로 호출 되는 부분을 처리하기 위해서

  • 별도의 객체로 분리한다.
  • 별도의 메소드로 분리한다.

Controller와 Service

Business method를 별도의 Service 객체에서 구현하도록 하고 컨트롤러는 Service객체를 사용하도록 한다.

Service 객체

Between the controller and the model, a layer called a service is sometimes interposed.
It fetches data from the model and lets the controller use the fetched data.
This layer allows a cleaner separation of data storage (model), data fetching (service) and data manipulation (controller).
Since this layer is not part of the original MVC concept, it is optional,
but can be useful for code management and reusability purposes in some cases.

Business logic을 수행하는 method를 가지고 있는 객체를 서비스 객체라고 한다.
보통 하나의 Business logic은 하나의 transaction 으로 동작한다.

Transaction

  1. 원자성 (Atomicity)
    모든 과정이 성공했을 때만 정보를 반영한다

  2. 일관성 (Consistency)
    Transaction 작업 처리 결과가 항상 일관성이 있어야 한다. 처음에 참조한 데이터로 transaction이 진행 되어야 한다. 각 사용자가 일관된 데이터를 볼 수 있다.

  3. 독립성 (Isolation)
    독립성은 둘 이상의 Transaction이 동시에 병행 실행되고 있을 경우, 어느 하나의 transaction이라도 다른 transaction의 연산에 끼어들 수 없다.

  4. 지속성 (Durability)
    지속성은 transaction이 성공적으로 완료되었을 경우, 결과는 영구적으로 반영되어야 한다는 점 입니다.

Background

2000년도에 Roy Fielding 박사의 학위논문에 REST라는 개념이 처음 등장
REST는 ‘Representational State Transfer’의 약자로 자원을 이름으로 구분 하여
해당 자원의 상태(정보)를 주고 받는 것을 의미한다.
Roy Fielding은 Web Architecture의 요구사항과 해결해야할 문제를 설명하고 이를 해결하기 위한
접근 방법을 논문에서 제시 하며, 이를 위한 아키텍처 스타일인 REST를 소개 합니다.

REST는 기본적으로 Web의 기존 기술과 HTTP protocol을 그대로 활용하기 때문에 Web의 장점을 최대한
활용할 수 있는 아키텍처 스타일이다.

REST 구성

REST API는 다음과 같은 3가지 구분으로 구성 됩니다.

  • 자원(Resource): 자원은 Data, Meta Data, HATEOAS
  • 행위(Verb): HTTP Method로 표현됩니다.
  • 표현(Representations)

REST의 특징

  1. Uniform Interface
    구성 요소(Client, Server 등) 사이의 인터페이스는 균일(uniform) 해야한다.
    인터페이스를 일반화함으로써, 전체 시스템의 아키텍처가 단순해지고, 상호 작용의 가시성이 개선되며,
    구현과 서비스가 분리되므로 독립적인 진화가 가능해질 수 있다.

  2. Stateless
    클라이언트와 서버의 통신에는 상태가 없어야한다. 모든 요청은 필요한 모든 정보를 담고 있어야 한다.
    요청 하나만 봐도 뭔지 알 수 있으므로 가시성이 개선되고, task 실패시 복원이 쉬우므로 신뢰성이 개선되며,
    상태를 저장할 필요가 없으므로 규모 확장성이 개선될 수 있다.

  3. Cacheable
    캐시가 가능해야한다. 즉, 모든 서버 응답은 캐시가 가능한지 아닌지 알 수 있어야함
    효율, 규모 확장성, 사용자 입장에서의 성능이 개선 된다.

  4. Self-descriptiveness
    REST의 또 다른 큰 특징 중 하나는 REST API 메시지만 보고도 이를 쉽게 이해 할 수 있는 자체 표현 구조로 되어 있다는 것

  5. Client - Server 구조
    클라이언트 - 서버 스타일은 사용자 인터페이스에 대한 관심(concern)을 데이터 저장에 대한 관심으로 부터 분리
    함으로써 클라이언트의 이식성과 서버의 규모 확장성을 개선할 수 있다.

  6. Layered System
    REST 서버는 다중 계층으로 구성될 수 있으며 보안, 로드 밸런싱, 암호화 계층을 추가해 구조상의 유연성을 둘 수 있고 PROXY, 게이트웨이 같은 네트워크 기반의 중간 매체를 사용할 수 있게 합니다.

REST API 설계 가이드

REST API 설계시 가장 중요한 항목은 다음의 2가지로 요약 할 수 있다.
첫 번째, URI는 정보의 자원을 표현해야 한다.
두 번째, 자원에 대한 행위는 HTTP Method (GET, POST, PUT, DELETE)로 표현한다.

  1. URI는 정보의 자원을 표현해야 한다.
  2. 자원에 대한 행위는 HTTP Method (GET, POST, PUT, DELETE)로 표현 한다.
  3. URI에 HTTP Method가 들어가면 안된다.
  4. URI에 행위에 대한 동사 표현이 들어가면 안된다.
  5. 경로 부분 중 변하는 부분은 유일한 값으로 대체 한다.
  6. 슬래시 구분자(/)는 계층 관계를 나타내는데 사용한다.
  7. URI 마지막 문제로 (/)를 포함하지 않는다.
  8. URI에 포함되는 모든 글자는 리소스의 유일한 식별자로 사용되어야 하며,
    URI가 다르다는 것은 리소스가 다르다는 것이고, 역으로 리소스가 다르면 URI도 달라져야 한다.
  9. 하이픈(-)은 URI 가독성을 높이는데 사용할 수 있다.
  10. under score는 URI에 사용하지 않는다.
  11. URI 경로에는 소문자가 적합하다.
  12. 파일 확장자는 URI에 포함하지 않는다 Accept header를 사용하도록 한다.
  13. 리소스 간에 연관 관계가 있는 경우 다음과 같은 방법으로 표현 한다.
    ex) GET: /books/{bookid}/viewers (일반적으로 소유 ‘has’ 관계를 표현할 때)
  14. 자원을 표현하는 Collection과 Document
    Collection은 객체의 집합, Document는 객체라고 생각하면 된다. Collection과 Document모두 리소스로 표현 할 수 있으며 URI로 표현 할 수 있다.

-> REST API를 완벽하게 구현하지 못할 경우 Web API라 한다.

Java Collections Framework

  • java.utils에 속한 일련의 클래스로, 자료구조를 담당
  • 잘 짜여진 interface를 기반으로 다양한 자료구조를 구현
  • Generic class로 되어 있어, 다양한 객체를 요소로 담을 수 있다.

some are excerpted from Java collections overview

Advantages of a collections framework

  • Reduces programming effort
  • Increase performance
  • Provide interoperability between unrelated APIs
  • Reduces the effort required to learn APIs
  • Reduces the effort required to design and implement APIs
  • Fosters software reuse

Collection Interfaces

  • Collection
    • A group of objects. No assumptions are made about the order of the collection (if any) or whether it can contain duplicate elements.
  • Set
    • The familiar set abstraction. No duplicate elements permitted. May or may not be ordered. Extends the Collection interface.
  • List
    • Ordered collection, also known as a sequence. Duplicates are generally permitted. Allows positional access. Extends the Collection interface.
  • Queue
    • A collection designed for holding elements before processing. Besides basic Collection operations, queues provide additional insertion, extraction, and inspection operations.
  • Deque
    • A double ended queue, supporting element insertion and removal at both ends. Extends the Queue interface.
  • Map
    • A mapping from keys to values. Each key can map to one value.

Method of Collection Interfaces

Method Description
boolean add(E e) Ensures that collection contains the specified element.
boolean addAll(Collection<? exteionds E> c) Adds all of the elements in the specified collection to this collection.
void clear() Removes all of the elements from this collection.
boolean contains(Object o) Returns true if this collection contains the specified element.
boolean containsAll(Collection<?> c) Returns true if this collection contains all of the elements in the specified collection.
boolean equals(Object o) Compares the specified object with this collection for equality.
boolean isEmpty() Returns true if this collection contains no elements.
Iterator<E> iterator() Returns true if this collection contains on elements.
boolean remove(Object o) Remove a single instance of the specified elemtns from this collection, if it is present.
boolean removeAll(Collection<?> c) Removes all of this collection’s elements that are also contained in the specified collection.
boolean retainAll(Collection<?> c) Retains only the elements in this collection that are contained in the specified collection.
int size() Returns the number of elements in this collection.
T[] toArray(T[] a) Returns an array containing all of the elements in this collection; the runtime type of the returned array is that of the specified array.

Excerpts from Enum Types

An enum type is a special data type that enables for a variable to be a set of predefined constants. The variable must be equal to one of the values that have been predefined for it.

Common examples: Compass directions(NORTH, SOUTH, EAST, and WEST) and the days of the week.

Because they are constants, the name of an enum type’s field are in uppercase letters.

Java programming languague enum types are much more powerful than theirs conunterparts in other languages. The enum declaration defines a class (called and enum type which implicitly extends java.lang.Enum). The enum class body can include method and other fields.

The constructor for an enum type must be package-private or private access. It automatically creates the constants that are defined at the beginning of the enum body. You cannot invoke an enum constructor yourself.

Container

Application 코드를 작성할때, 특정 기능이 필요하면 Library를 호출하여 사용한다. 프로그램의 흐름을 제어아하는 주체가 Application code이다. Framework 기반의 개발에서는 Framework 자신이 흐름을 제어하는 주체가 되어, 필요할 때마다 Application 코드를 호출하여 사용한다.

컨테이너는 instance의 생명주기를 관리하며, 생성된 instance에게 추가적인 기능을 제공합니다.
예를 들어, Servlet을 실행해주는 WAS는 Servlet Contatiner를 가지고 있다고 말합니다.
WAS는 Web browser로부터 Servlet URL에 해당하는 요청을 받으면, Servlet을 메모리에 올린 후 실행 합니다.
개발자가 Servlet Class를 작성했지만 실제로 메모리에 올리고 실행하는 것은 WAS가 가지고 있는 Servlet Container 입니다.
Servlet Container는 동일한 Servlet에 해당하는 요청을 받으면, 또 메모리에 올리지 않고 기존에 메모리에 올라간 Servlet을 실행하여 그 결과를 Web browser에게 전달 합니다.
Container는 보통 instance의 생명 주기를 관리하며, 생성된 instance들에게 추가적인 기능을 제공하는 것을 말합니다.

DI(Dependency Injection)

DI는 의존성 주입이라는 뜻을 가지고 있다. Class사이의 의존 관계를 Bean 설정 정보를 바탕으로 Container가 자동으로 연결 해 주는 것을 말한다.
개발자들은 제어를 담당할 필요 없이 Bean 설정 파일에 의존관계가 필요하다는 정보만 추가해주면 된다.
Object reference를 Container로부터 주입 받아서, 실행 시에 동적으로 의존 관계가 생성된다.
Container가 흐름의 주체가 되어서 Application code에 의존관계를 주입해주는 것이 된다.
이것을 *eInversion of Control** 이라 부른다.

Spring Framework에서 DI 적용 예 2가지

xml 파일을 이용한 설정

  • src/main 아래에 UserBean class 작성

Bean class의 몇가지 특성

  1. 기본 생성자를 가지고 있다.
  2. 필드는 private하게 선언한다.
  3. getter, setter method를 가진다. getName(), setName() method를 name property라고 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package kr.or.connect.diexam01;

//빈클래스
public class UserBean {

//필드는 private한다.
private String name;
private int age;
private boolean male;

//기본생성자를 반드시 가지고 있어야 한다.
public UserBean() {
}

public UserBean(String name, int age, boolean male) {
this.name = name;
this.age = age;
this.male = male;
}

// setter, getter메소드는 프로퍼티라고 한다.
public void setName(String name) {
this.name = name;
}

public String getName() {
return name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public boolean isMale() {
return male;
}

public void setMale(boolean male) {
this.male = male;
}
}
  • src/main/reource folder에 applicationContext.xml을 생성

UserBean class 의 instance 를 userBean이라는 이름으로 생성 할 수 있도록 xml을 작성 합니다.

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="userBean" class="kr.or.connect.diexam01.UserBean"></bean>
</beans>
  • ApplicationContext를 이용해서 설정파일을 읽어들여서 실행
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package kr.or.connect.diexam01;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ApplicationContextExam01 {

public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext(
"classpath:applicationContext.xml");
System.out.println("초기화 완료.");

UserBean userBean = (UserBean)ac.getBean("userBean");
userBean.setName("kim");
System.out.println(userBean.getName());

UserBean userBean2 = (UserBean)ac.getBean("userBean");
if(userBean == userBean2) {
System.out.println("같은 인스턴스이다.");
}
}
}

xml을 이용한 설정으로 DI 확인

Car의 Engine class 작성

1
2
3
4
5
6
7
8
9
10
11
package kr.or.connect.diexam01;

public class Engine {
public Engine() {
System.out.println("Engine 생성자");
}

public void exec() {
System.out.println("엔진이 동작합니다.");
}
}

Car class 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package kr.or.connect.diexam01;

public class Car {
Engine v8;

public Car() {
System.out.println("Car 생성자");
}

public void setEngine(Engine e) {
this.v8 = e;
}

public void run() {
System.out.println("엔진을 이용하여 달립니다.");
v8.exec();
}
}

Spring container에게 engine 객체의 injection을 맡기기 위해 다음과 같이 xml에 작성합니다.

1
2
3
4
<bean id="e" class="kr.or.connect.diexam01.Engine"></bean>
<bean id="car" class="kr.or.connect.diexam01.Car">
<property name="engine" ref="e"></property>
</bean>

위의 xml파일을 읽어들여서 작동하게끔 작성하면 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package kr.or.connect.diexam01;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ApplicationContextExam02 {

public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext(
"classpath:applicationContext.xml");

Car car = (Car)ac.getBean("car");
car.run();
}
}

Java config를 이용한 설정

  • Annotaions
    • @Configuration
      • 스프링 설정 클래스를 선언하는 어노테이션
    • @Bean
      • bean을 정의하는 어노테이션
    • @ComponentScan
      • @Controller, @Service, @Repository, @Component 어노테이션이 붙은 클래스를 찾아 컨테이너에 등록
    • @Component
      • 컴포넌트 스캔의 대상이 되는 애노테이션 중 하나로써 주로 유틸, 기타 지원 클래스에 붙이는 어노테이션
    • @Autowired
      • 주입 대상이되는 bean을 컨테이너에 찾아 주입하는 어노테이션

Java Config 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package kr.or.connect.diexam01;
import org.springframework.context.annotation.*;

@Configuration
public class ApplicationConfig {
@Bean
public Car car(Engine e) {
Car c = new Car();
c.setEngine(e);
return c;
}

@Bean
public Engine engine() {
return new Engine();
}
}

@Configuration 은 스프링 설정 클래스라는 의미를 가집니다.
JavaConfig로 설정을 할 클래스 위에는 @Configuration가 붙어 있어야 합니다.
ApplicationContext중에서 AnnotationConfigApplicationContext는 JavaConfig클래스를 읽어들여 IoC와 DI를 적용하게 됩니다.
이때 설정파일 중에 @Bean이 붙어 있는 메소드들을 AnnotationConfigApplicationContext는 자동으로 실행하여 그 결과로 리턴하는 객체들을 기본적으로 싱글턴으로 관리를 하게 됩니다.

DI 적용 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package kr.or.connect.diexam01;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ApplicationContextExam03 {

public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class);

Car car = (Car)ac.getBean("car"); // Car.class로도 가능
car.run();

}
}

Annotation만을 활용한 ApplicationConfig 설정

1
2
3
4
5
6
7
package kr.or.connect.diexam01;
import org.springframework.context.annotation.*;

@Configuration
@ComponentScan("kr.or.connect.diexam01")
public class ApplicationConfig2 {
}

@ComponentScan이라는 annotation 추가
@ComponentScan annotation은 parameter로 들어온 package 이하에서 @Controller, @service, @Repository, @Component annotation이 붙어있는 class를 찾아서 메모리에 몽따 올린다.

기존에 작성한 Car class에 @Component annotation 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package kr.or.connect.diexam01;

import org.springframework.stereotype.Component;

@Component
public class Engine {
public Engine() {
System.out.println("Engine 생성자");
}

public void exec() {
System.out.println("엔진이 동작합니다.");
}
}

DI 가 필요한 부분에는 @Autowired annotation 적용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package kr.or.connect.diexam01;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Car {
@Autowired
private Engine v8;

public Car() {
System.out.println("Car 생성자");
}

public void run() {
System.out.println("엔진을 이용하여 달립니다.");
v8.exec();
}
}

DI 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package kr.or.connect.diexam01;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ApplicationContextExam04 {

public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig2.class);

Car car = ac.getBean(Car.class);
car.run();

}
}

Testing

테스팅이란 응용 프로그램 또는 시스템(구성요소)의 동작과 성능, 안정성이 요구 하는 수준을 만족하는지 확인하기 위해 결함을 발견하는 과정이라고 할 수 있다.

  • 정적 테스트: 프로그램을 개발하기 전에 요구사항 등을 리뷰 하는 것
  • 동적 테스트: 프로그램 개발 이후에 실제 실행하면서 테스트 하는 것

소프트웨어 개발, 유지보수, 운영 시 테스팅의 역할

  1. Testing을 통해 release 전에 발견 되지 않은 결함들이 수정된다면, 운영 환경 내에서 발생하는 risk를 줄이는데 기여 할 수 있으며 소프트웨어 품질에 도움을 준다.
  2. Testing은 개발 초기의 요구사항 분석 단계 부터 review 및 insfection을 통해 정적으로 이뤄질 수 있으며 각각의 개발 단계에 대응하는 test level에 따른 testing이 이루어진다.
  3. 기존에 운영되는 소프트웨어 시스템이 유지 보수 활동으로 변경 및 단종되거나 환경이 변하는 경우, 변경된 소스트웨어에 대한 testing과 변경된 환경에서 운영 testing이 요구된다.
  4. 소프트웨어 testing은 계약상(법적) 요구조건들, 또는 산업에 특화된 표준들을 만족 시키기 위해서 필요하다.

Testing의 일반적인 원리

  1. Testing이 결함이 존재함을 밝히는 활동이다.
    Testing은 결함이 존재함을 드러내지만, 결함이 없다는 것을 증명할 수 없다.
    즉, 프로그램이 완벽하다고 증명할 수 없다.
    이는 Test한 부분까지만 잘 동작한다고 말할 수 있고 테스트를 하지 않은 부분은 결함이 있는지 없는지 예측할 수 없다.

  2. Exhaustive Testing은 불가능 하다.

    모든 가능성(입력과 사전 조건의 모든 조합)을 Testing 하는 것은 지극히 간단한 소프트웨어를 제외하고 가능하지 않다.

    • 한 프로그램 내의 내부 조건이 무수히 많음
    • 입력이 가질 수 있는 보든 값의 조건이 무수히 많음
    • GUI이벤트 발생 순서에 대한 조합도 무수히 많음

    완벽한 Testing 대신, 리스크 분석과 결정된 우선순위에 따라 Testing 활동 노력을 집중 시켜야 한다. (Risk-based testing)
    완벽한 Testing은 우주항공, 의료 등 안전이 최우선(Safety critical)인 소프트웨어를 개발 할 때 고려 할 수 있으나 실제로 완벽한 것은 아니고 강력한 Testing으로 볼 수 있다.

Test Driven Development

  • 새로 추가 되는 기능에 해당하는 Test를 먼저 작성, 이때 각 test는 간결 해야함
  • 테스트를 수행하고 새 테스트가 실패하는지 확인한다.
    • 새 테스트는 항상 실패해야만 테스트가 잘 작성됐다고 할 수 있다.
  • 코드를 작성
  • 테스트 수행
  • 코드의 refactor
  • 반복

DAO(Data Access Object)

  • 실제로 DB에 접근 하는 객체
    • Persistance Layer(DB에 data를 CRUD하는 계층) 이다.
  • Service와 DB를 연결하는 고리의 역할을 한다.
  • SQL를 사용(개발자가 직접 코딩)하여 DB에 접근한 후 적절한 CRUD API를 제공한다.

DTO(Data Transfer Object)

  • 계층간 데이터 교환을 위한 객체(Java Beans)이다.
    • DB에서 데이터를 얻어 Service나 Controller 등으로 보낼 때 사용하는 객체를 말한다.
    • DB의 데이터가 Presentiation Logic Tier로 넘어오게 될 때 DTO의 구조로 바뀌게 된다.
    • Logic을 갖지 않는 순수한 데이터 객체, getter/setter method만을 갖는다.
    • DB에서 꺼낸 값을 변경할 필요가 없기 때문에 DTO class에는 setter가 없다.

Spring Framework

  • 복잡한 부분은 구현이 거의 되어 있고, 필요한 부분 입맛에 맞게 구현 할 수 있다.
  • 엔터프라이즈급 어플리케이션을 구축할 수 있는 가벼운 솔루션이자, One-Stop-Shop(모든 과정을 한번에 해결 할 수 있다.)
  • 원하는 부분만 가져다 사용할 수 있도록 모듈화가 잘 되어 있다.
  • IoC container
  • 선언적으로 트렌적션을 관리할 수 있다.
  • 완전한 기능을 갖춘 MVC Framework을 제공
  • AOP 지원
  • Spring은 Domain 논리 코드와 쉽게 분리될 수 있는 구조로 되어 있습니다.

Container

  • Container는 instance의 생명주기를 관리한다.
  • 생성된 instance들에게 추가적인 기능을 제공한다.
  • Servlet class instance 화 하는 것을 tomcat이 대신 해준다.
  • WAS(tomcat)은 Servlet conatainer를 가지고, Servlet URL을 받으면 Servlet을 메모리에 올린 후 실행해준다.
  • Container는 보통 instance의 생명주기를 관리하며, 생성된 instance들에게 추가적인 기능을 제공하는 것을 말합니다.

Inversion of Control

  • 개발자는 프로그램의 흐름을 제어하는 코드를 작성한다. 그런데, 이 흐름의 제어를 개발자가 하는 것이 아니라 다른 프로그램이 그 흐름을 제허하는 것을 IoC라 말한다.
  • Servlet class는 개발자가 만들지만, 그 Servlet의 method를 알맞게 호출 하는것은 WAS
  • 개발자가 만든 어떤 class나 method를 다른 프로그램이 대신 실행하주는 것을 제어의 역전이라 한다.

Dependency Injection

  • DI는 class사이의 의존관계를 Bean설정 정보를 바탕으로 컨테이너가 자동으로 연결해주는 것을 말한다.
  • DI가 적용 안된경우:
    • 개발자가 직접 인스턴스를 생성한다
  • Spring에서 DI 가 적용 된 경우, container가 instance를 할당해 준다.
    • Inversion of control

Spring에서 제공하는 IoC/DI container

  • BeanFactory: IoC/DI에 대한 기본 기능을 가지고 있다.
  • ApplicationContext: BeanFactory의 모든 기능을 포함하고 개선된 버전
    • BeanPostProcessor: 컨테이너의 기본로직을 오버라이딩하여 인스턴스화와 의존성 처리 로직 등을 개발자가 원하는 대로 구현 할 수 있도록 합니다.
    • BeanFactoryPostProcessor: 설정된 메타 데이터를 커스터마이징 할 수 있습니다.

Bean class

  • 스프링에서는 객체를 다룰때 Single tone pattern을 이용한다.
  • 예전에는 Visual한 컴포넌트를 Bean이라고 불렀지만, 근래 들어서는 일반적인 Java 클래스를 Bean클래스라고 보통 부른다.
  • Bean class의 3가지 특징
    • 기본 생성자를 가진다.
    • field는 private하게 선언한다.
    • getter, setter메소드를 가진다.
    • getName(), setName() 메소드를 name property라 한다.
  • xml을 이용한 설정
  • Java Config를 이용한 설정
    • @Configuration
      • 스프링 설정 클래스를 선언하는 annotation
    • @Bean
      • bean을 정의하는 annotation
    • @Component
      • component 스캔의 대상이 되는 annotation 중 하나로써 주로 유틸, 기타 지원 클래스에 붙이는 annotation
    • @Autowired
      • 주입 대상이 되는 bean을 컨테이너에 찾아 주입하는 annotation
  • Sprint에서 사용하기에 알맞게 @Controller, @Service, @Repository, @Component annotation이 붙어 있는 객체들은 ComponentScan을 이용해서 읽어들여 메모리에 올리고 DI를 주입하도록 하고, 이러한 annotation이 붙어 있지 않은 객체는 @Bean annotation을 이용하여 직접 생성해주는 방식으로 클래스들을 관리하면 편리하다.

JSTL

  • JSTL(JSP Standard Tag Library)은 JSP page에서 조건문 처리, 반복문 처리 등을 html tag 형태로 작성할 수 있게 도와준다.

set, remove

  • set

    1
    <c:set var="varName" scope="session" value="someValue"/>
    1
    2
    3
    <c:set var="varName" scope="request">
    someValue
    </c:set>
  • remove

    1
    <c:remove var="varName" scope="request"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<c:set var="value1" scope="request" value="kim"/>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
성: ${value1 }<br>
<c:remove var="value1" scope="request"/>
성: ${value1 }<br>
</body>
</html>

Property, Map

1
<c:set target="${some}" property="propertyName" value="anyValue"/>
  • some 객체가 Java bin일 경우
  • some 객체가 map일 경우: some.put(propertyName, anyValue);

if

1
2
3
4
<c:if test="조건">
...
...
</c:if>

choose

  • if - else 혹은 switch case statement 처럼 조건에 맞게 사용 될 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<c:choose>
<c:when test="${score >= 90}">
A학점 입니다.
</c:when>
<c:when test="${score >=80 }">
B학점입니다.
</c:when>
<c:when test="${score >=70 }">
C학점입니다.
</c:when>
<c:when test="${score >=60 }">
D학점입니다.
</c:when>
<c:otherwise>
F학점 입니다.
</c:otherwise>
</c:choose>

forEach

  • 배열 및 Collection에 저장된 요소를 차례대로 처리한다.
1
<c:forEach var="변수" items="아이템" [begin="시작번호"] [end="끝번호"]>

import

  • 지정한 URL에 연결하여 결과를 지정한 변수에 저장한다.
1
2
3
<c:import url="URL" charEncoding="캐릭터 인코딩" var="변수명" scope="scope">
<c:param name="파라미터 이름", value="파라미터값"/>
</c:import>
  • url: 결과를 읽어올 URL
  • charEncoding: 읽어온 결과를 저장할 때 사용할 character encoding
  • var: 읽어온 결과를 저장할 영역
  • scope: 변수를 저장할 영역
  • <c:param> 태그는 url 속성에 지정한 사이트에 연결할 때 전송할 파라미터를 입력한다.

redirect

response.sendRedirect() 와 비슷

1
2
3
<c:redirect url="리다이렉트할URL">
<c:param name="파라미터이름" value="파라미터값"/>
</c:redirect>
  • url: 리다이렉트 URL
  • <c:param>은 리다이렉트 할 페이지에 전달할 파라미터 지정

out

  • JspWriter에 데이터를 출력한다
1
<c:out value="value" escapeXml="{true|false}" default="defaultValue" />
  • excapeXml 값이 true 일경우 아래와 같이 문자를 변경한다. 생략할 수 있으며, 생략할 경우 기본 값은 true이다.
문자 변환된 형태
< &lt;
> &gt;
& &amp;
' &#039;
" &#034;