지난 번글에서 스프링 빈과 스프링 컨테이너를 사용하는 이유, 작동원리에 대해 썼다.

이번 글에선 스프링 빈을 어떤식으로 등록하고 사용하는지 조금 더 자세하게 써보려고 한다.

 

인스턴스를 스프링 빈으로 등록하는 방법

1. 수동 등록

 

원하는 객체 인스턴스를 하나씩 직접 스프링 빈으로 등록하는 방법.

Xml, 지난 번에 사용했던 Configuration 어노테이션을 사용한 방법 등이 있다.

지난번 글에서 Xml 을 사용하는 방법은 최근에는 거의 쓰이지 않고 있으므로 Configuration 을 사용하는 방법만 간단하게 짚고 넘어갔다.

//AppConfig
@Configuration
public class AppConfig{
    // 설정 내용
    @Bean
    public MemberService memberService(){
        return new MemberServiceImpl(memberRepository());
    }
    @Bean
    public MemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }
    .
    .

Config 클래스를 만들고 Configuration 어노테이션을 붙인 다음 Bean 어노테이션과 메소드를 통해 빈을 등록하면 된다.

메소드 이름으로 빈 이름을 정할 수 있고 의존관계 또한 메소드를 그대로 사용하면 된다.

 

단점 : 빈을 개발자가 일일히 등록해야함 -> 규모가 커지면 누락 및 오류 발생 가능성 커짐

 

2. 자동 등록

 

수동 등록의 단점을 해결하기 위해 스프링에서 제공하는 기능이다.

//AutoAppConfig
package project;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import static org.springframework.context.annotation.ComponentScan.*;

@Configuration
@ComponentScan
public class AutoAppConfig {
}

설정 파일 앞에 ComponentScan 어노테이션을 붙여주기만 하면 된다.

AutoAppConfig라는 클래스를 만들고 Configuration과 ComponentScan 어노테이션을 붙여줬다.

컴포넌트 스캔은 이름 그대로 Component 어노테이션이 붙은 클래스를 스캔해서 스프링 빈으로 등록한다

//MemberServiceImpl
package project.member;
import org.springframework.stereotype.Component;

@Component
public  class MemberServiceImpl implements MemberService{
    private final MemberRepository memberRepository;
    
    public MemberServiceImpl(MemberRepository memberRepository){
        this.memberRepository = memberRepository;
    }
    
    public void join(Member member){
        memberRepository.save(member);
    }
    
    public Member findMember(Long id){
        return memberRepository.findById(id);
    }
}
//MemoryMemberRepository
package project.member;

import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.HashMap;

@Component
public class MemoryMemberRepository implements MemberRepository{
    private static Map<Long, Member> store = new HashMap<> ();
    
    @Override
    public void save(Member member){
        store.put(member.getId(), member);
    }
    @Override
    public Member findById(Long id){
        return store.get(id);
    }
}

빈으로 등록하길 원하는 클래스 앞에 Component 어노테이션을 붙여주기만 하면 끝이다.

이러면 스프링이 자동으로 Component 어노테이션이 붙은 클래스들을 찾아서 빈으로 등록한다.

이때 기본 이름은 클래스 이름에서 맨 앞글자만 소문자로 바꾼 형태이며

Component("beanName") 과 같은 방식으로 직접 등록할 수도 있다.

 

자동 등록 시의 의존관계 설정은 @Autowired 어노테이션을 사용한다.

Autowired와 자동 의존관계 설정에 관해선 다음 글에서 좀 더 자세히 다룰 예정이다.

 

스캔 범위 지정하기

모든 자바 클래스를 다 컴포넌트 스캔하면 시간이 오래 걸린다.

그래서 꼭 필요한 위치부터 탐색하도록 시작 위치를 지정할 수 있다.

//basePackages
//탐색할 패기지의 시작 위치 지정. 이 패키지를 포함한 하위 패키지를 모두 탐색
@ComponentScan(basePackages = "project")
//여러 시작 위치 지정도 가능
@ComponentScan(basePackages = {"project.core", "project.service"})

//basePackageClasses
//지정한 클래스의 패키지를 탐색 시작 위치로 지정한다
@ComponentScan(basePackagesClasses = MemberServiceImpl.Class)

만약 시작 위치를 지정하지 않으면 @ComponentScan이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.

최근 스프링 부트의 기본 제공 방법은 패키지 위치를 지정하지 않고, 설정 정보 클래스 위치를 프로젝트 최상단에 두는 것이다.

 

필터

includeFilters : 컴포넌트 스캔 대상을 추가로 지정한다.

excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정한다.

@ComponentScan(
	includeFilters = {
		@Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
	},
	excludeFilters = {
		@Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
	}
)

FilterType은 5가지 옵션이 존재한다.

ANNOTATION: 기본값, 애노테이션을 인식해서 동작한다.

ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작한다.

ASPECTJ: AspectJ 패턴 사용

REGEX: 정규 표현식

CUSTOM: TypeFilter 이라는 인터페이스를 구현해서 처리

 

 

중복 등록과 충돌

만약 컴포넌트 스캔에서 같은 빈 이름을 사용해 두 개 이상의 빈이 등록된다면 어떻게 될까?

1. 자동 빈 등록 방법에서만 같은 빈 이름이 두 개 생성됐을때 -> 스프링이 오류를 띄운다.

2. 수동으로 등록한 빈과 자동으로 등록한 빈의 이름이 겹칠때 -> 수동 등록한 빈이 우선권을 가진다.

그러나 최근에는 그냥 오류를 띄우도록 바뀌었다.

 

 

 

 

 

 

 

 

 

 

지난번 글에선 순수 JAVA 코드로 예재를 구성하고

IOC와 DI 그리고 DI 컨테이너에 대해 다뤘다.

 

이번 글에선 스프링을 이용해서 예재 코드를 조금 수정해보려고 한다.

 

AppConfig 코드를 다음과 같이 수정하자

//AppConfig
package project;

import project.member.MemberService;
import project.member.MemberServiceImpl;
import project.member.MemoryMemberRepository;
import project.member.MemberRepository;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig{
    // 설정 내용
    @Bean
    public MemberService memberService(){
        return new MemberServiceImpl(memberRepository());
    }
    @Bean
    public MemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }
    .
    .

AppConfig 에 우선 Configuration 이라는 어노테이션을 달아줬고,

MemberService와 memberRepository 위에 Bean 이라는 어노테이션을 달아줬다.

 

그리고 MemberService를 사용하는 MemberApp 파일을 수정해주자

//MemberApp
package project;

import project.member.Member;
import project.member.MemberService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MemberApp {
	public static void main(String[] args) {
    	//AppConfig appConfig = new AppConfig();
        //MemberService memberService = appConfig.memberService();
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberService memberService = applicationContext.getBean("memberService",MemberService.class);
        .
        .

AppConfig에서 직접 memberService를 호출하는 것이 아니라

ApplicationContext 라는 클래스와 getBean이라는 메소드를 이용해서 memberService를 사용하고 있다.

 

무슨 차이점이 있는걸까?

스프링을 사용하면 무슨 장점이 존재할까?

 

스프링 빈과  스프링 컨테이너

코드에서 사용된 ApplicationContext를 스프링 컨테이너라고 부른다.

ApplicationContext 는 자바 인터페이스이다.

스프링 컨테이너는 XML을 기반으로 만들 수 있고, Configuration 어노테이션을 이용해서도 만들 수 있다.

지금 사용하는 방식이 어노테이션 기반의 자바 설정 클래스로 스프링 컨테이너를 만든 것이다.

 

스프링 컨테이너는 Configuration 어노테이션이 붙은 설정 클래스의 메소드를 분석한다.

그 후 설정 클래스 정보를 활용해 객체 인스턴스를 스프링 빈으로 등록해준다.

이때 빈 이름과 빈 객체로 나눠서 저장하게 된다.

빈 이름 빈 객체
memberService MemberServiceImpl@x01..
memberRepository MemoryMemberRepository@x02..

이때 빈 이름은 메소드 이름으로 자동 저장되며 @Bean(name="memberService2")와 같이 직접 지정할 수도 있다.

 

그렇다면 객체를 스프링 빈으로 지정하고, 컨테이너에서 관리하는 이유는 뭘까?

바로 싱글톤 패턴을 지원하기 위해서다.

 

싱글톤 패턴이란?

싱글톤 패턴은 클래스의 인스턴스가 딱 한개만 생성되는 것을 보장하는 패턴이다.

객체를 한번만 생성하고 그 이후에는 공유해서 사용하기 때문에 객체를 효율적으로 사용 가능하다.

 

예를 들면

기존의 AppConfig 클래스 코드에선 MemberApp 객체를 사용할때마다 새로운 MemberServiceImpl 객체를 생성해서 주입시켜준다.

3명의 사용자가 MemberApp을 사용한다면 3개의 객체를, 100명이면 100개를 만명이면 객체 만개를 생성하고 관리해야한다.

그러나 싱글톤 패턴을 이용한다면 하나의 객체만 생성한 후 사용자들에게 같은 객체를 전달하면 된다.

//참고
//싱글톤 패턴을 자바 코드로 구현하기
public class SingletonService {
	private static final SingletonService instance = new SingletonService();
	
	private SingletonService(){
	}
	public SingletonService getInstance(){
		return instance;
	}
}

 

스프링 컨테이너는 객체 인스턴스를 빈으로 저장해 싱글톤 패턴으로 관리해준다.

한 번 클래스를 빈으로 등록해놓으면 해당 클래스를 사용할 일이 생겼을때

getBean 메소드를 사용한다면 항상 같은 객체 인스턴스를 반환하도록 스프링이 알아서 관리해준다.

 

물론 싱글톤 패턴의 단점도 존재한다.

  1. 패턴을 구현하는데 시간이 오래걸림
  2. 의존관계상 클라이언트가 구현에 의존 -> DIP 위배, OCP위배 가능성 높음
  3. 테스트하기 어려움
  4. 자식 클래스 만들기 어려움

그러나 스프링을 활용하면 싱글톤 패턴의 문제점은 해결하며 객체 인스턴스를 싱글톤으로 관리할 수 있다.

그렇다면 스프링 컨테이너는 어떻게 동작하는걸까?

스프링 컨테이너의 동작 원리

void configurationDeep() {
	ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
	MemberService bean = ac.getBean(MemberService.class);
 
 System.out.println("bean = " + bean.getClass());
 //출력: bean = class project.member.MemberService$$EnhancerBySpringCGLIB$$bd479d70
}

만약 bean이 순수한 MemberService 클래스라면 다음과 같이 출력되야한다.

bean = class project.member.MemberService

그러나 실제 출력값은 $$와 함께 xxx..CGLIB가 붙어서 나오는 것을 볼 수 있다.

 

이는 스프링이 자바의 바이트 코드를 조작하여 싱글톤 패턴을 유지하기 때문이다.

스프링이 CGLIB라는 라이브러리 사용해 MemberService를 상속받은 다른 가짜 클래스를 만들고 빈으로 등록한 것이다.

이때 MemberService를 상속받았기 때문에 MemberService 클래스로도 호출이 가능하다.

 

그리고 MemberService를 호출한다면 가짜 클래스를 불러내는데 가짜 클래스에는 다음과 같은 메소드가 추가되어있다.

1. MemberService 가짜 객체가 이미 존재한다면 -> 존재하는 객체를 반환

2. 객체가 존재하지 않는다면 -> 새로 생성해서 반환

 

그래서 스프링은 다음과 같은 방식으로 싱글톤 패턴을 지원한다.

1. Configuration 어노테이션이 붙은 클래스 안의 메소드를 읽어낸다.

2. 메소드의 클래스를 상속받는 가짜 클래스 인스턴스를 만들어 빈으로 등록한다.

3. 위와 같이 가짜 클래스를 활용해서 싱글톤 패턴을 지원

 

싱글톤 방식을 사용할 때 주의할 점

싱글톤 패턴이든, 스프링 같은 싱글톤 컨테이너를 사용하든, 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태를 유지(stateful)하게 설계하면 안된다.

 

무상태(stateless)로 설계해야 한다!

 

특정 클라이언트에 의존적인 필드가 있으면 안된다.

특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다!

가급적 읽기만 가능해야 한다.

필드 대신에 자바에서 공유되지 않는, 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다.

 

스프링 빈의 필드에 공유 값을 설정하면 정말 큰 장애가 발생할 수 있다!!

 

라고 하는데 이건 나중에 공부를 더 해봐야 할 것 같다.

 

 

이번 글에선 스프링 빈이 무엇인지와 어떤 원리로 작동하는지를 써봤다.

다음 글에선 스프링 빈 활용법에 대해 써보려고 한다.

 

 

 

 

 

 

 

1. 스프링 사용 없이 구현해보기

강의에서 제공하는 회원 서비스와 다이어그램은 다음과 같다.

간략하게 회원 서비스만 다뤄보자

//MemberServiceImpl
package project.member;

public  class MemberServiceImpl implements MemberService{
    private final MemberRepository memberRepository = new MemoryMemberRepository();
    
    public void join(Member member){
        memberRepository.save(member);
    }
    
    public Member findMember(Long id){
        return memberRepository.findById(id);
    }
}
//MemoryMemberRepository
package project.member;

import java.util.Map;
import java.util.HashMap;

public class MemoryMemberRepository implements MemberRepository{
    private static Map<Long, Member> store = new HashMap<> ();
    
    @Override
    public void save(Member member){
        store.put(member.getId(), member);
    }
    @Override
    public Member findById(Long id){
        return store.get(id);
    }
}

MemberRepository 라는 인터페이스를 만들고 MemoryMemberRepository 라는 구현 클래스를 만들었다.

저번에 설명한 역할과 구현이라는 설계 방식을 잘 따르는 것처럼 보인다.

 

그러나 만약에 외부 DB에 연결을 한 후 Member 정보를 DB에 저장하기로 결정했다면?

DbMemberRepository를 만든 후 MemoryMemberRepository와 교체하면 된다.

이는 OCP 원칙을 준수하는 것 처럼 보이지만 다른 문제점이 생긴다.

public  class MemberServiceImpl implements MemberService{
    //private final MemberRepository memberRepository = new MemoryMemberRepository();
    private final MemberRepository memberRepository = new DbMemberRepository();
    .
    .
    .

바로 MemberServiceImpl 클래스를 변경해야지만 변경사항을 적용할 수 있다는 것이다.

Repository를 변경하기 위해 그걸 의존하는 클라이언트(Service) 클래스까지 변경해야 하고

이는 전체가 부분에 의존함을 의미한다. 즉 DIP, OCP 원칙을 위배한다고 볼 수 있다.

 

어떻게 해결 할 수 있을까??

 

관심사의 분리

어플리케이션을 공연, 인터페이스를 배역, 구현 객체를 배우라고 비유해보자.

지금 구현한 코드는 주연 배우가 본인의 원래 책임 뿐 아니라 조연 배우를 섭외하는 책임까지 가지고 있는 셈이다.

책임 즉 관심사의 분리가 필요하다.

 

공연 기획자의 역할을 하는, 즉 객체를 생성하고 의존관계를 연결하는 별도의 설정 클래스를 만들어보자

//MemberServiceImpl
package project.member;

public  class MemberServiceImpl implements MemberService{
    private final MemberRepository memberRepository;
    //생성자 주입
    public MemberServiceImpl(MemberRepository memberRepository) {
		this.memberRepository = memberRepository;
	}
    .
    .
}
//AppConfig
package project;

import project.member.MemberService;
import project.member.MemberServiceImpl;
import project.member.MemoryMemberRepository;
import project.member.MemberRepository;

public class AppConfig{
    // 설정 내용
    public MemberService memberService(){
        return new MemberServiceImpl(memberRepository());
    }
    public MemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }
}

우선 MemberServiceImpl 객체에 생성자를 만든 후 생성자의 매개변수를 통해 MemberRepository를 주입받도록 한다.

이것을 memberServiceImpl 입장에서 보면 의존관계를 마치 외부에서 주입해주는 것 같다고 해서 DI(Dependency Injection) 우리말로 의존관계 주입 또는 의존성 주입이라 한다.

이후 AppConfig라는 설정 클래스를 만든 후 AppConfig에서 객체들을 생성하면 된다.

 

이렇게 바꾼다면 MemverServiceImpl은 더 이상 MemoryMemberRepository에 의존하지 않게 된다.

단지 MemberRepository 인터페이스에만 의존하면 된다.

즉 구현이 아닌 역할에 의존하게 된다.

 

//MemberApp
package project;

import project.member.Member;
import project.member.MemberService;

public class MemberApp {
	public static void main(String[] args) {
		AppConfig appConfig = new AppConfig();
		MemberService memberService = appConfig.memberService();
        .
        .
        .

나중에 MemberService를 사용할 일이 생긴다면 다음과 같이 설정 파일에서 memberService를 불러오면 된다.

이렇게 구현한다면 DbMemberRepository로 교체할때도 오직 설정파일만

return new MemoryMemberRepository(); -> return new DbMemberRepository();

으로 교체하면 된다.

 

이렇게 변경한 코드는 스프링의 핵심 원리인 IOC와 DI를 사용하고 있다.

 

1. IOC(Inversion of Control , 제어의 역전)

 

기존 프로그램은 클라이언트 구현 객체가 스스로 서버 구현 객체를 생성하고 관리했다.

즉 구현 객체가 제어 프름을 스스로 조종했다.

AppConfig를 만든 후에 구현 객체는 자신의 로직만을 담담한다. 즉 프로그램의 제어 흐름을 AppConfig가 가져간다.

MemberServiceImpl 입장에서 제어 흐름의 권한은 모두 AppConfig가 관리하고 있다.

이처럼 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IoC)이라
한다.

 

2. DI(Dependency Injection, 의존관계 주입)

 

 MemberServiceImpl은 MemberRepository 라는 인터페이스에 의존한다.

즉 프로그램이 실행되기 전까지 어떤 구현 객체가 사용될지 알 수 없다.

애플리케이션 실행 시점(런타임)에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서
클라이언트와 서버의 실제 의존관계가 연결 되는 것을 의존관계 주입이라 한다.

의존관계 주입을 사용하면 정적인 클래스 의존관계를 변경하지 않고, 동적인 객체 인스턴스 의존관계를
쉽게 변경할 수 있다.

 

AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해 주는 것을
IoC 컨테이너 또는 DI 컨테이너라 한다.
의존관계 주입에 초점을 맞추어 최근에는 주로 DI 컨테이너라 한다.

 

 

하지만 몇가지 찜찜한 점이 존재한다.

1. 결국 Repository를 변경한다면 AppConfig의 코드를 바꿔야한다

2. 서비스가 커진다면 설정파일에서 하나하나 등록하고 관리하기 힘들지 않을까?

 

이런 문제점을 Spring이 제공하는 기능들을 통해 해결해보자.

 

 

 

 

 

스프링이란? 

자바 플랫폼을 위한 오픈 소스 애플리케이션 프레임워크

동적인 웹 사이트를 개발하기 위한 여러 가지 서비스를 제공하고 있다.

 

스프링 부트는 뭔가요?

스프링을 편리하게 사용할 수 있도록 지원해주는 프레임워크, 최근에는 기본으로 사용하는 추세

 

스프링을 왜 만들었나요?

자바 언어의 가장 큰 특징인 객체지향을 활용하기 위해

스프링은 좋은 객체 지향 애플리케이션을 개발할 수 있게 도와주는 프레임워크

 

객체지향 프로그래밍이란?

컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 "객체"들의 모임으로 파악하고자 하는 것이다.

객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용된다

 

유연하고 변경에 용이하다는 점이 핵심

- 프로그램이 유연하다는 것이 뭘까?

->프로그램을 변경할 일이 생겼을 때 마치 부품을 갈아끼우듯이, 레고 블럭을 조립하듯이 쉽고 유연하게 변경하면서 개발할 수 있는 프로그램

 

 

객체지향 프로그래밍의 4가지 특징

 

1. 캡슐화

서로 연관있는 속성과 기능들을 하나의 캡슐(capsule)로 만들어 데이터를 외부로부터 보호하는 것

외부에서 객체가 어떻게 동작하는지 모르게 하는 것

(마치 자동차 운전자가 자동차가 엔진이 어떻게 움직이는지 알 필요가 없듯이)

이는 객체를 자율적으로 만들고 객체의 독립성, 책임 영역을 지킨다.

접근 제어자(private, public) 등을 활용해 구현한다

 

2. 상속

현실의 상속과는 많이 다름

기존의 클래스를 재활용하여 새로운 클래스를 작성하는 문법 요소

아버지-아들 같은 관계를 이야기하는 것이 아님

동물 - 펭귄 같은 사이의 관계에서 사용한다.

이를 is a 관계라고도 한다. (펭귄 is a 동물 -> 말이 된다면 상속을 올바르게 사용한 것)

객체지향의 사실과 오해 책에선 is a kind of가 좀 더 적절하다고도 이야기한다 (펭귄 is a kind of 동물)

 

3. 추상화

“사물이나 표상을 어떤 성질, 공통성, 본질에 착안하여 그것을 추출하여 파악하는 것”

-> 필요한 부분만 모아서 추출해내는 것을 말한다. ex) 지하철 노선도

객체의 공통적이고 필요한 부분만 모아서 추출하는 것을 이야기함

자동차, 오토바이 모두 사람을 태울 수 있고, 이동이 가능함 -> 두 객체 모두 Vichle(탈 것) 으로 추상화 가능하다.

자바의 interface 또는 추상 클래스(abstract class)로 구현 가능하다.

 

interface와 상속의 차이?

interface는 껍데기만 놔두고 구현 강요

상속은 구현해놓은 것 재사용 가능

  • 상위 클래스의 물려줄 특성이 많을수록 좋다
  • 인터 페이스는 구현을 강제할 메서드가 적을수록 좋다

 

4. 다형성

역할과 구현으로 세상을 구분하자 (로미오 - 배우)

이렇게 한다면 세상이 단순해짐 ->  유연, 변경에 용이해짐

클라이언트가 대상의 역할만 알면 됨 (마치 배우가 바뀌어도 로미오 역할만 할 줄 알면 상대역은 영향이 없다) 

클라이언트는 대상이 변경되어도 영향 없음

자바에선 오버라이딩, 인터페이스, 상속 등으로 구현한다.

 

메소드 오버라이딩과 오버로딩의 차이는 뭘까?

- 오버라이딩 - 부모 클래스나 상속받은 인터페이스의 메소드를 새로 재정의해 덮어씌우는 것

- 오버로딩 - 같은 이름의 메소드를 두 개 이상 정의하는 것 (매개변수의 타입이나 개수를 다르게 하여)

 

 

스프링 강의에선 이 중 가장 중요한것이 다형성이라고 이야기한다.

 

강의에선 객체를 설계할때

역할 - 인터페이스 

구현 - 인터페이스를 구현한 클래스,객체

방식으로 설계하라고 한다.

즉 역할을 먼저 부여한 후, 그 역할을 수행하는 구현 객체를 만들라고 이야기한다.

 

다형성의 본질 : 클라이언트를 변경하지 않고 기능을 유연하게 변경할 수 있도록 하는 것

이를 위해선 인터페이스를 안정적으로 설계하는 것이 정말 중요하다.

스프링은 다형성을 극대화해서 이용하도록 도와줌

 

좋은 객체지향 설계의 5원칙 - SOLID

  1. SRP (Single responsibility principle) - 단일 책임 원칙
    1. 한 클래스는 하나의 책임만 가져야한다.
    2. 변경이 있을때 파급이 적으면 단일 책임 원칙을 잘 따른 것
  2. OCP (Open/closed principle) - 개방 폐쇄 원칙
    1. 확장에는 열려있으나 변경에는 닫혀있어야함
    2. 새로운 기능 구현 -> 새 코드 생성 YES, 기존 코드 변경은 NO(인터페이스 구현한 새로운 클래스를 제작하자)
  3. LSP (Liskov substitution priciple) - 리스코프 치환 원칙
    1. 부모 클래스는 자식클래스로 대체 가능해야함
    2. 하위 클래스는 인터페이스 규약을 전부 지켜야함
    3. ex) 자동차 클래스의 엑셀 메소드 → 앞으로가라, 자식 클래스가 엑셀 밟으면 뒤로 간다면 → 원칙 위배
  4. ISP (Interface segregation principle) - 인터페이스 분리 원칙
    1. 특정 클라이언트를 위한 인터페이스 여러개가 범용 인터페이스보다 좋음
    2. 사용자 클라이언트 → 운전자 클라이언트, 정비사 클라이언트로 분리
    3. 분리하면 정비 인터페이스 자체가 변경되어도 영향 없음
  5. DIP (dependency inversion priciple) - 의존성 역전 원칙
    1. 추상화에 의존해야지 구체화에 의존하면 안된다
    2. 클라이언트는 인터페이스만 바라봐야한다
    3. 역할에 의존해야한다. (구현에 의존하면 안됨)

 

그래서 이 객체지향의 원리가 스프링에선 어떻게 구현될까?

를 다음편에 써보려고 한다.

 

 

 

 

 

 

 

인프런 김영한 님의 "스프링 입문 - 코드로 배우는 스프링부트, 웹 MVC, DB 접근" 기술 강의를 듣고 정리해보려 합니다.

 

개발환경 - GOORM IDE

 

1. 구름 IDE에서 컨테이너 생성

 

구름 IDE 접속 -> 새 컨테이너 생성 -> 소프트웨어 스택 ->

Spring Boot 선택 -> Template Gradle로 설정 후 컨테이너를 생성한다.

강의에선 h2 데이터베이스를 사용한 예재를 진행하는데 h2 데이터베이스를 우분투 환경에 설치하기 귀찮다면

mysql 설치 항목에 체크한 후 mysql로 바꿔서 진행해도 된다.

2. JAVA 버전 & Gradle 업그레이드 

강의가 2020년에 처음 나온 강의라서

spring boot - 2.3.1

JAVA - 11

버전을 사용하고 있다.

 

그런데 구름 ide가 지원하는 spring boot 컨테이너는 놀랍게도 자바 8과 spring boot 2.0.1 을 기본 설정해줘서 업그레이드를 해줘야한다.

# JAVA 11 설치
# apt 업데이트
$ sudo apt-get update && sudo apt-get upgrade

#자바 다운로드
$ sudo apt-get install openjdk-11-jdk

#vi 편집기 사용
$ sudo vi /etc/environment

# 아래 내용 입력 후 저장
$JAVA_HOME="/usr/lib/jvm/java-11-openjdk-arm64"

# 자바 11 버전에 맞는 번호 입력 후 엔터
$ sudo update-alternative --config java

 

https://velog.io/@phjppo0918/%EC%8B%9C%EA%B0%84%ED%91%9C-%EC%9E%90%EB%8F%99%EC%83%9D%EC%84%B1-%EC%84%9C%EB%B9%84%EC%8A%A4-1.-goorm-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EC%83%9D%EC%84%B1-%EB%B0%8F-Gradle-%EC%84%A4%EC%B9%98

 

[시간표 자동생성 서비스] 1. goorm 컨테이너 생성 및 Gradle 설치

이게 안되네;; 구름 댕같다 ㄹㅇ참조 링크: https://jjeongil.tistory.com/1402nano창이 뜨면 아래 두 줄을 복사헤서 gradle.sh에 복붙하면 된다.

velog.io

#Gradle 업데이트

#nano 설치
$ sudo apt-get install nano

# gradle 6.5.1 설치
$ wget https://services.gradle.org/distributions/gradle-6.5.1-bin.zip -P /tmp
$ sudo unzip -d /opt/gradle /tmp/gradle-6.5.1-bin.zip

# 환경설정 파일 열기
$ sudo nano /etc/profile.d/gradle.sh

# 아래 두 줄 복사해서 gradle.sh에 입력하고 저장
export GRADLE_HOME=/opt/gradle/gradle-7.1.1
export PATH=${GRADLE_HOME}/bin:${PATH}

# 스크립트 실행 및 환경변수 로드
$ sudo chmod +x /etc/profile.d/gradle.sh
$ source /etc/profile.d/gradle.sh

spring 2.3.x 버전에 맞게 gradle 을 업그레이드 해주자

아래 두 줄은 Goorm ide 컨테이너를 실행할때마다 반복적으로 실행해줘야하는 것 같다.

 

3. build.gradle 파일 변경

build.gradle 파일이 스프링 2.0.1 버전에 맞게 생성되어있을텐데 바꿔줘야한다.

// build.gradle

plugins {
	id 'java'
	id 'org.springframework.boot' version '2.3.1.RELEASE'
	id 'io.spring.dependency-management' version '1.0.9.RELEASE'
}
 
group = 'io.goorm'
version = '0.0.1'
sourceCompatibility = '11'
 
repositories {
	mavenCentral()
}
 
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.assertj:assertj-core:3.11.1'
    testImplementation ('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    
    // JPA 및 DB 연결을 위한 라이브러리
    //implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    //implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    //runtimeOnly 'mysql:mysql-connector-java'
    //compileOnly 'org.projectlombok:lombok'
    //annotationProcessor 'org.projectlombok:lombok'
}

test {
    useJUnitPlatform()
}

spring-boot-starter-web : 웹서버(tomcat)과 웹 mvc 기능을 포함한다.

spring-boot-stater-thymleaf : 타임리프 엔진(html 파일 뷰 엔진)

implementation 'org.assertj:assertj-core:3.11.1 : 강의에는 없는데 test 파일 작성중 assertThat을 호출했더니 오류가 생겨서 추가한 라이브러리.

spring-boot-stater-test : 테스트 파일 작성용

org.springframework.boot:spring-boot-devtools : html파일 수정시 실시간 반영을 위한 라이브러리 .. 라고 하는데 작동을 안한다.

 

4. application.properties 수정 및 url과 포트 설정

server.port=${PORT:8080}

# 이 설정을 추가 안해주면 나중에 빈 추가 및 오버라이딩 관련 오류가 발생했다.
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

이후 상단의 프로젝트 -> 실행 url과 포트 에서

실행용 url이 있는지  연결 포트가 8080으로 설정 되어있는지 확인하고 없으면 새로 생성한다.

 

5. Spring 서버 시작해보기

$ gradle build
$ cd build/libs
$ java -jar spring_boot-0.0.1.jar

서버가 잘 열리면 성공이고 잘 안되면

 

1. gradle --version , java --version 을 통해 제대로 설치되었는지 확인해보자.

2. build.gradle 파일이 제대로 수정됐는지 확인해보자.

 

+ Recent posts