Spring 기본

최창효·2022년 5월 19일
0

자바 이해하기

목록 보기
7/8
post-thumbnail
post-custom-banner

스프링이란

  • 스프링 이전에 EJB로 어플리케이션을 개발했었는데 배우기 어렵고 설정해야 하는 부분이 많았고 무거웠으며 테스트를 위해 EJB서버가 필요했으며.... 자바의 웹 프레임워크 입니다.
  • 프레임워크는 복잡하고 반복적인 Low Level 작업을 손쉽게 만들어 개발자가 Business Logic에 집중 할 수 있도록 도와주는 역할을 합니다.
  • 스프링은 자바로 웹을 손쉽게 만들 수 있도록 도와주는 도구입니다.

핵심내용

Spring의 핵심개념으로 IoC/DI, AOP, PSA, POJO가 있습니다. 지금부터 이들에 대해 살펴보겠습니다.

IoC: Inversion of Control

  • 제어의 역전이라는 뜻입니다. 개인적으로 처음 단어를 접했을 때 제어가 뭐지?, 역전되기 전에는 어떤 상황이지?, 왜 역전시켰지? 라는 의문이 들었었습니다.
  • IoC에서 제어란 자바의 객체를 생성하고 의존관계를 부여하는 권한을 말합니다.
  • 우리는 지금까지 Obj obj = new Obj();라는 코드를 입력해 객체를 생성했습니다.
    • 즉, 자바 객체를 생성하는 권한이 개발자에게 있었다는 말입니다.
    • 또한 A.class에서 B b = new B();라는 코드를 실행하기 때문에 A.class가 자신이 사용할 객체(B)를 직접 만들어서 사용하고 있다는 의미이기도 합니다.
  • IoC에서 제어를 역전한 주체는 Container 입니다. 즉, 우리가 가지고 있던 제어권컨테이너에게 넘어간 상황을 제어의 역전이라고 부릅니다.

여기까지만 보면 뭔가 중요한 걸 빼았겼다는 느낌이 들어 부정적으로 느껴질 수 있지만 그렇지 않습니다.
제어권이 넘어갔다는 건 귀찮은 작업을 누군가에게 떠넘길 수 있게 된 감사한 일입니다.

특징

  • 객체간의 연결 관계를 런타임 시점에 결정하게 됩니다.
    • 동적 바인딩은 유연한 코드설계가 가능하다는 장점이 있습니다.
  • 객체 간의 관계가 느슨하게 연결됩니다.(loose coupling)

IoC Container

  • Spring Container라고도 불립니다. // Spring Container는 Spring Core라고도 불립니다.
  • Container는 객체의 생성, 사용, 소멸에 해당하는 라이프사이클을 담당합니다.
  • IoC Container의 종류로 BeanFactory 인터페이스, ApplicationContext 인터페이스가 존재하는데 Beanfactory를 상속받아 기능을 확장한 인터페이스가 ApplicationContext이기 때문에 주로 ApplicationContext를 사용합니다.
  • ApplicationContext는 BeanFactory에 다음의 것들이 추가되어 있습니다.
    • Spring의 AOP기능과 더 쉽게 결합할 수 있게 구성되어 있습니다.
    • 국제화를 위한 Message resource 처리가 포함되어 있습니다.
    • Event publication이 포함되어 있습니다.
    • application-layer의 특정 컨텍스트가 추가되어 있습니다.

DI: Dependency Injection

  • IoC(제어의 역전)을 실천하는 방법으로 Dependency LookupDependency Injection이 있습니다.
  • DI는 직접 객체를 생성하지 않고 필요한 객체를 외부에서 넣어주겠다(Injection)는 의미입니다.
  • DI는 동일한 Container에게 관리받는 객체들 사이에서만 가능합니다.
  • 의존관계를 부여하기 위해 @Resource, @Autowired, @Inject Annotation이 사용됩니다.
  • 의존관계는 Setter method, Constructor, 멤버변수, 일반 method에서 정의할 수 있습니다.

DI 예시

  • 스프링에서 빈의 메타정보를 표현하는 방식: XML, Annotation, Java Code
  • 자바에서 DI를 하는 방법: Setter Injection, Constructor Injection, Method Injection
  • DI의 장점은 느슨한 결합입니다.
    객체는 인터페이스에 의한 의존관계만 알고있을 뿐 구현체에 대해서는 알 필요가 없어지기 때문에 의존도가 낮아집니다.

일반적으로 다음과 같이 A객체에서 직접 B객체를 선언하여 사용합니다.(의존성 주입X)

class A{
    private B b;
    public A(){
        b = new B();
    }
}

외부에서 의존성을 주입하는 방법은 다음과 같습니다.

class A{
    private B b;
    
    // setter로 주입
    public void setB(B b){ 
        this.b = b;
    }

    // 생성자로 주입
    public A(B b){
        this.b = b;
    }

}

xml과 annotation는 의존성을 어떻게 주입해 달라는 주문서
컨테이너는 주문서에 따라 의존성을 조립해주는 주체로 이해할 수 있습니다.

자바코드로 의존성 주입하기

public static void main(String[] args){
    Exam exam = new Exam();
    ExamConsole console = new InlineExamConsole(exam); // 생성자를 통해 exam객체를 inlineExamConsole에다가 주입하고 있습니다.
    // ExamConsole console = new GridExamConsole(exam);
    console.print();
}
  • 현재 상태에서 ExamConsole을 InlineExamConsole에서 GridExamConsole로 바꾸려면 해당 파일을 직접 변경해야 합니다.

스프링으로 DI하기 - XML방식

<beans>
  	<!-- Exam exam = new NewlecExam();과 동일한 역할을 합니다. -->
	<bean id = "exam" class = "spring.di.entity.NewlecExam"/> 
  	<!--  Examconsole console = new GridExamConsole();과 동일한 역할을 합니다. -->
	<bean id = "console' class ="spring.di.ui.GridExamConsole" > 
        <!-- console.setExam();과 동일한 역할을 합니다. 주입하는 객체를 value또는 ref로 선언해주면 됩니다. 
             이때 ref의 값은 같은 beans의 있는 bean의 id를 값으로 받습니다. -->       
		<property name="exam" ref = "exam" />
	</bean>
<beans>

만들어진 XML은 ApplicationContext에 의해 실행됩니다.

ApplicationContext

  • 스프링에서 DI 또는 지시서를 읽어 객체를 생성 및 조립해주는 행위의 주체입니다. ApplicationContext는 xml을 인자로 받는인터페이스 입니다.
  • 넘겨받는 xml의 위치에 따라 구현체가 달라집니다.
    • ClassPathXmlApplicationContext: Application의 root로부터 xml을 찾습니다.
    • FileSystemXmlApplicationContext: 파일로 된 xml을 찾습니다.
    • XmlWebApplicationContext: 웹과 관련된 xml을 찾습니다.
    • AnnotationConfigApplicationContext: xml이 아닌 어노테이션을 지시서로 사용합니다. scan이 필요합니다.
  • ApplicationContext안의 bean은 getBean()메서드를 통해 얻을 수 있습니다.

사용예시1) 기본선언

Public static void main(String[] args){ 
    ApplicationContext context = new ClassPathXmlApplicationContext("패키지 경로/파일이름.xml"); 
}

사용예시2) 이름으로 객체 꺼내기

Public static void main(String[] args){ 
    ApplicationContext context = new ClassPathXmlApplicationContext("spring/di/setting.xml"); 
    // 이름으로 객체를 가져올 때에는 해당 객체가 어떤 타입인지를 모르기 때문에 형변환이 필요합니다.
    ExamConsole console = (ExamConsole) Context.getBean(“console”);

}

사용예시3) 자료형으로 객체 꺼내기

Public static void main(String[] args){ 
    ApplicationContext context = new ClassPathXmlApplicationContext("spring/di/setting.xml");  
    ExamConsole console = context.getBean(ExamConsole.class); 

}
  • ExamConsole이라는 인터페이스를 참조할 수 있는 클래스를 가져와라는 의미입니다.

값 주입하기

  • property의 value속성 이용합니다.
  • 해당 클래스는 각 변수에 대한 setter메서드가 선언되어 있어야 합니다.
<bean id = "exam" class ="spring.di.entity.NewlecExam">  
    <property name="kor" value = "20" /> 
    <property name = "kor"> 
        <value> "20" </value> 
    <property> 
</bean>

생성자 주입하기

  • property의 constructor-arg 사용합니다.
  • 오버로딩된 생성자가 선언되어 있어야 합니다.
<bean id = "exam" class ="spring.di.entity.NewlecExam"> 
    <constructor-arg value="30" />
    <constructor-arg index = "1" value="50" />
    <constructor-arg name="kor" value="50" /> 
    <constructor-arg name="kor" type = "float" value="50" /> 
</bean>
  • index,name을 부여하지 않으면 적은 순서대로 생성자 인자가 됩니다.

  • index를 사용하면 임의의 순서를 지정할 수 있습니다.

  • name을 사용하면 특정 인자라는걸 명시할 수 있습니다.

  • 매개변수는 동일하고 매개변수의 타입만 다른 생성자가 오버로딩 되어 있다면 type을 명시해 어떤 생성자를 사용할지 지정할 수 있습니다.

  • p:를 통해 변수를 넘겨주는 방법도 있습니다.

<bean id = "exam" class ="spring.di.entity.NewlecExam" p:kor="10" p:eng="20"> 
</bean>

주입을 통한 Collections 선언

주입이 아닌 일반적인 방법

List<Exam> exams = new ArrayList<>();
exams.add(new NewlecExam(1,1,1,1));

for(Exam e : exams){
    System.out.println(e); 

}
Public static void main(String[] args){ 
    List<Exam> exams = (List<Exam>)context.getBean("exams");
}

주입1. constructor-arg에 list안에 bean을 넣습니다.

<bean id = "exams" class "java.util.ArrayList"/> 
    <constructor-arg>
        <list>
            <bean id = "exam" class = "spring.di.entity.NewlecExam" p:kor="10" p:eng="20"> // 빈을 직접 생성
                <ref bean="exam"/> // 같은 .xml에 있는 bean을 참조 
        </list>
    <constructor-arg>
</bean>

주입2. <util:>태그 이용합니다.

<util:list id="exams" list-class="java.util.ArrayList"> 
    <bean id = "exam" class ="spring.di.entity.NewlecExam" p:kor="10" p:eng="20" 
    <ref bean="exam"/>  
</util:list>

빈(Bean)

  • IoC Container의 관리를 받는 객체를 빈(Bean)이라고 부릅니다.
  • IoC Container는 기본적으로 싱글톤으로 객체(빈)를 생성합니다.
    • 그래서 컨테이너가 제공하는 모든 빈의 인스턴스는 항상 동일합니다
    • scope 설정을 통해 범위를 변경할 수 있습니다.
      • "singleton": 컨테이너 당 하나의 인스턴스만 생성
      • "prototype": 요청마다 새로운 인스턴스 생성
      • "request": HTTP Request별로 새로운 인스턴스 생성
      • "session": HTTP Session별로 새로운 인스턴스 생성
  • 스프링 빈의 생명주기
    • 빈 생성 -> 의존성 주입 -> init method 실행 -> 빈 사용 -> destroy method 실행 -> 빈 소멸

빈의 메타정보를 설정하는 방법

XML 방식

  • 앞선 DI예시들이 XML방식에 해당합니다.
<?xml>
<beans>
	<bean id="" class="">
    	<property>
    </bean>
<beans>

Annotation 방식

  • component-scan범위 내에서 @Component annotation을 부여 받은 객체를 IoC Container에서 관리합니다.
    • component-scan: Container가 관리하는 객체의 범위입니다.
    • @Component: Container에게 관리받기를 희망하는 객체입니다.
      • 기능에 따라 @Repository, @Service, @Controller Annotation을 사용하기도 합니다.
    • component-scan 범위에 있는 모든 객체를 Container가 메모리에 올려준다(생성해 준다)? -> X
    • component-scan 범위에 있는 @Component를 지닌 객체는 모두 Container가 메모리에 올려준다? -> O
  • @Autowired가 설정되어 있는 변수, Setter메서드, 생성자, 일반 메서드는 IoC Container가 인스턴스를 주입해 줍니다.
    • @Autowired는 타입을 이용해 의존성을 주입합니다. 만약 동일한 Bean타입 객체가 여러개 있다면 원하는 Bean을 찾기 위해 @Qualifier를 같이 사용해야 합니다.
      • @Qualifier(value="빈의 이름") 형식으로 사용됩니다.
      • 비교)@Resource는 이름을 이용해 의존성을 주입합니다.
    • @Autowired를 이용한 주입은 동일한 IoC Container에 의해 관리받고 있어야 가능합니다.
<xml>
<beans>
	<context:component-scan base-package=""/>
</beans>
@Component
public class Service{
	@Autowired
    private DAO dao;
}

Java Code 방식

  • @Bean어노테이션으로 빈을 정의합니다.

AOP: Aspect Oriented Programming

  • 관점지향 프로그래밍이라는 뜻으로 우리가 구현하는 기능을 핵심 관심 사항공통 관심 사항으로 분리(관심사의 분리)하겠다는 의미입니다.
  • 공통 관심 사항을 부가기능, Aspect라고 정의합니다.
    • Aspect는 핵심 관심 사항의 시작 또는 끝에서 실행됩니다.
  • AOP의 구현은 주로 proxy를 활용하며, 스프링 역시 프록시 기반으로 AOP를 지원하고 있습니다.

POJO AOP

프록시 생성

MainProcess proxy = (MainProcess) Proxy.newProxyInstance(loader, interfaces, h);
	(MainProcess): 형변환
	loader: 실제 객체의 정보
	interfaces: 배열형태의 인터페이스 정보
	h: 보조업무(InvokationHandler())

프록시를 이용한 AOP 구현

import java.lang.reflect.InvocationHandler; 
import java.lang.reflect.Method; 
import java.lang.reflect.Proxy; 
public class Sub { 
	public static void main(String[] args) { 
		MainProcess realObj = new MainProcessImpl(1,2,3); // 실제객체 
		MainProcess Proxy_Obj = (MainProcess)Proxy.newProxyInstance(MainProcessImpl.class.getClassLoader(), 
			new Class[] {MainProcess.class}, 
			new InvocationHandler() { 
				@Override 
				public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
					// 보조업무 
					long start = System.currentTimeMillis(); 
					Thread.sleep(1000); 

					// 핵심 업무 호출 
					Object result = method.invoke(realObj, args);

					// 보조업무 
					long end = System.currentTimeMillis(); 
					long time = (end-start); 
					System.out.println(time); 

					return result; 
					} 
				} 
		); // 프록시 객체 
		System.out.println(Proxy_Obj.sum()); // 실제객체가 아니라 프록시에서 메서드를 호출 
//		System.out.println(realObj.sum()); // 물론 실제객체에서 호출하는 것도 문제없음 
	} 
}

Spring AOP

  • Spring은 보조업무의 수행 위치에 따라 Before, After Running, After Throwing, Around의 proxy를 제공하고 있습니다.
  • MethodInterceptor를 implements하며, invoke를 오버라이딩 하고, invocation.proceed()로 핵심 업무를 호출합니다.
  • xml에서 ProxyFactoryBean 객체를 생성하며 핵심업무와 보조업무를 property로 가집니다.
    아래는 Around proxy에 대한 예시입니다.

setting.xml

<beans>
    <bean id="MainProcess" class="MainProcess"></bean>
    <bean id="SubProcess" class="AroundAdvice"></bean>
    <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean"> // proxy빈을 생성합니다.
        <property name="target" ref="MainProcess"/> // 핵심업무를 정의합니다.
        <property name="SubProcess"> // 보조업무를 정의합니다.
            <list>
                <value>SubProcess</value>                
            </list>
        </property>
    </bean>
<beans>

AroundAdvice.java

public class AroundAdvice implements MethodInterceptor{
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable{
    	// 보조 업무
        long start = System.currentTimeMillis();
        
        // 핵심 업무
        Object result = invocation.proceed(); 
        
        // 보조 업무
        long end = System.currentTimeMillis();
        int time = end-start;
        System.out.println(time);
        return result;
    }
}

test.java

public class test{
    public static void main(String[] args){
        ApplicationContext context = new CalssPAthXmlApplicationContext("setting.xml");
        MainProcess proxy = (MainProcess)context.getBean("proxy");
        System.out.println(proxy.sum());
    }
}

AOP 주요 용어

  • Advice: 언제 공통 관심 기능을 핵심 로직에 적용할 지를 정의
  • Joinpoint: Advice를 적용 가능한 지점
  • Pointcut: 실제 Advice가 적용되는 Joinpoint
  • Weaving: Advice를 핵심 로직 코드에 적용하는 것
  • Aspect: 여러 객체에 공통으로 적용되는 기능

PSA: Portable Service Abstraction

  • 환경 변화에 관계 없이 일관된 방식으로 기술로의 접근 환경을 제공하는 추상화 구조입니다.
    하나의 추상화로 여러 서비스를 묶어둔 걸 PSA라고 합니다.
  • 잘 만들어진 인터페이스가 곧 PSA입니다.

POJO: Plain Old Java Object

  • 특정 프레임워크나 기술에 의존적이지 않는 객체지향 원리에 충실한 객체를 의미합니다.
  • POJO를 사용하면 객체지향의 원리에 충실해지며 객체들 사이의 연결이 느슨해 집니다.
profile
기록하고 정리하는 걸 좋아하는 개발자.
post-custom-banner

0개의 댓글