Everything has an expiration date
[Spring] 20240109 [프로그램 소스] 본문
SpringPrj05 - com.test.spr
■■■ AOP 개념 실습 01 ■■■
※ AOP 기법을 적용하기 이전 형태로 개념 실습
○ 실습 성격 및 주요사항
1. 콘솔 프로젝트
2. 기본적인 산술 연산 처리
3. AOP 기법을 적용하지 않은 상태로
보조 업무(시간 측정, 로그 기록 처리)를 수행하는 실습을 진행한다.
4. SpringPrj05
5. 로그 및 스톱워치 기능 사용을 위해 관련 jar 파일을 등록하여
실습을 진행할 수 있도록 한다.
( * 스프링 AOP를 적용하려는 api가 아니라
로그나 스톱워치를 등록하기 위한 api이다. )
○ 등록해야 할 jar 파일
- 경로 1 → C:\s-f-3.0.2-with-docs\dist
·파일1 → org.springframework.core-3.0.2.RELEASE.jar
- 경로 2 → C:\s-f-3.0.2-dependencies\org.apache.commons \com.springsource.org.apache.commons.logging\1.1.1
·파일2 → com.springsource.org.apache.commons.logging-1.1.1.jar
○ 물리적 파일 구성
1. Calculator.java → 인터페이스
2. CalculatorImpl.java → 클래스. 주 업무, 보조 업무 적용.
3. Main.java → 클래스. main() 메소드를 포함하는 클래스.
Calculator.java (인터페이스)
/*========================
Calculator.java
- 인터페이스
=========================*/
package com.test.spr;
// ※ 스프링 AOP 기법을 적용하기 위해서는
// 대상 객체가 인터페이스를 구현하고 있어야 한다.
public interface Calculator
{
// 주 업무(core concern)진행을 위한 (추상) 메소드 선언
//-- 덧셈, 뺄셈, 곱셈, 나눗셈
public int add(int x, int y);
public int sub(int x, int y);
public int mul(int x, int y);
public int div(int x, int y);
}
CalculatorImpl.java
/*================================================
CalculatorImpl.java
- 클래스.
- Calculator 인터페이스를 구현하는 클래스.
- 주 업무, 보조 업무가 함께 처리되는 형태.
==================================================*/
package com.test.spr;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StopWatch;
public class CalculatorImpl implements Calculator
{
// 주 업무(core concern) 진행을 위한 메소드 구현
@Override
public int add(int x, int y)
{
int result = 0;
// 보조 업무(cross-cutting concern) 설정
//-- 시간 측정 시작 및 로그 기록 처리
Log log = LogFactory.getLog(this.getClass());
//-- 현재 우리가 사용하고 있는 CalculatorImpl.java 클래스에서
// 로그를 생성해 내겠다는 의미이다.
// [LogFactory] : Log를 생성해 내는 공장과도 같다.
// 여러 개의 log를 간편하게 생성한다.
// getLog() → static 메소드, Log를 반환한다.
StopWatch sw = new StopWatch();
sw.start();
log.info("처리 시간 측정 시작 -------------");
//-- [log.info()] : 로그 정보를 등록하는 것이다.
// 주 업무(core concern) 실행 내용
result = x + y;
System.out.printf("%d + %d = %d%n", x, y, result);
// 보조 업무(cross-cutting concern) 설정
//-- 시간 측정 종료 및 로그 기록 처리
sw.stop();
log.info("처리 시간 측정 종료 -------------");
log.info(String.format("경과시간 : %s/10000초", sw.getTotalTimeMillis()));
//★★★★★★★★★★★★★★★★★★★★★★★★
// 로그 기록남기기(『보조업무』)를
// 『주 업무』의 앞에서도 하고 뒤에서도 하므로
// Around Advice 를 하고 있다고 볼 수 있다.
//★★★★★★★★★★★★★★★★★★★★★★★★
return result;
}
@Override
public int sub(int x, int y)
{
int result = 0;
// 보조 업무(cross-cutting concern) 설정
//-- 시간 측정 시작 및 로그 기록 처리
Log log = LogFactory.getLog(this.getClass());
StopWatch sw = new StopWatch();
sw.start();
log.info("처리 시간 측정 시작 -------------");
// 주 업무(core concern) 실행 내용
result = x - y;
System.out.printf("%d - %d = %d%n", x, y, result);
// 보조 업무(cross-cutting concern) 설정
//-- 시간 측정 종료 및 로그 기록 처리
sw.stop();
log.info("처리 시간 측정 종료 -------------");
log.info(String.format("경과시간 : %s/10000초", sw.getTotalTimeMillis()));
return result;
}
@Override
public int mul(int x, int y)
{
int result = 0;
// 보조 업무(cross-cutting concern) 설정
//-- 시간 측정 시작 및 로그 기록 처리
Log log = LogFactory.getLog(this.getClass());
StopWatch sw = new StopWatch();
sw.start();
log.info("처리 시간 측정 시작 -------------");
// 주 업무(core concern) 실행 내용
result = x * y;
System.out.printf("%d * %d = %d%n", x, y, result);
// 보조 업무(cross-cutting concern) 설정
//-- 시간 측정 종료 및 로그 기록 처리
sw.stop();
log.info("처리 시간 측정 종료 -------------");
log.info(String.format("경과시간 : %s/10000초", sw.getTotalTimeMillis()));
return result;
}
@Override
public int div(int x, int y)
{
int result = 0;
// 보조 업무(cross-cutting concern) 설정
//-- 시간 측정 시작 및 로그 기록 처리
Log log = LogFactory.getLog(this.getClass());
StopWatch sw = new StopWatch();
sw.start();
log.info("처리 시간 측정 시작 -------------");
// 주 업무(core concern) 실행 내용
result = x / y;
System.out.printf("%d / %d = %d%n", x, y, result);
// 보조 업무(cross-cutting concern) 설정
//-- 시간 측정 종료 및 로그 기록 처리
sw.stop();
log.info("처리 시간 측정 종료 -------------");
log.info(String.format("경과시간 : %s/10000초", sw.getTotalTimeMillis()));
return result;
}
}
Main.java
/*============================================
Main.java
- main() 메소드가 포함된 테스트 클래스
==============================================*/
package com.test.spr;
public class Main
{
public static void main(String[] args)
{
// 주 업무 실행을 할 수 있는 객체 준비
// 인터페이스 변수 = new 인터페이스구현클래스();
// List list = new ArrayList();
Calculator cal = new CalculatorImpl();
// 주 업무 실행에 대한 테스트
int add = cal.add(10, 20);
System.out.println(add);
int sub = cal.sub(10, 20);
System.out.println(sub);
int multi = cal.mul(10, 20);
System.out.println(multi);
int div = cal.div(10, 20);
System.out.println(div);
}
}
[실행 결과 콘솔창]
SpringPrj06 - com.test.spr
■■■ AOP 개념 실습 02 ■■■
※ AOP 기법을 적용하는 형태로 개념 실습
○ 실습 성격 및 주요사항
1. 콘솔 프로젝트
2. 기본적인 산술 연산 처리
3. AOP 기법을 적용하여
보조 업무(시간 측정, 로그 기록 처리)를 수행하는 실습을 진행한다.
4. SpringPrj06
5. 로그 및 스톱워치 기능 사용을 위해 관련 jar 파일을 등록하여
실습을 진행할 수 있도록 한다.
( * 스프링 AOP를 적용하려는 api가 아니라
로그나 스톱워치를 등록하기 위한 api이다. )
○ 등록해야 할 jar 파일
- 경로 1 → C:\s-f-3.0.2-with-docs\dist
·파일1 → org.springframework.core-3.0.2.RELEASE.jar
- 경로 2 → C:\s-f-3.0.2-dependencies\org.apache.commons \com.springsource.org.apache.commons.logging\1.1.1
·파일2 → com.springsource.org.apache.commons.logging-1.1.1.jar
○ 물리적 파일 구성
1. Calculator.java → 인터페이스(기존 소스코드 그대로 활용)
2. CalculatorProxy.java → 프록시(proxy) 클래스
(추가~!!!) 보조 업무 적용 및 주 업무 호출 과정
3. CalculatorImpl.java → 클래스. 주 업무 적용.
4. Main.java → 클래스. main() 메소드를 포함하는 클래스.
Calculator.java
/*========================
Calculator.java
- 인터페이스
=========================*/
package com.test.spr;
// ※ 스프링 AOP 기법을 적용하기 위해서는
// 대상 객체가 인터페이스를 구현하고 있어야 한다.
public interface Calculator
{
// 주 업무(core concern)진행을 위한 (추상) 메소드 선언
//-- 덧셈, 뺄셈, 곱셈, 나눗셈
public int add(int x, int y);
public int sub(int x, int y);
public int mul(int x, int y);
public int div(int x, int y);
}
CalculatorProxy.java
/*========================================
CalculatorProxy.java
- 프록시 클래스.
- 보조 업무 적용 및 주 업무 호출 과정.
==========================================*/
// ※ Proxy 클래스를 만드는 여러가지 방법들 중
// 비교적 쉽고 직관적인 방법은
// InvocationHandler 인터페이스를 구현하는 클래스를 만드는 것이다.
package com.test.spr;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StopWatch;
public class CalculatorProxy implements InvocationHandler
{
// 주요 속성 구성
// target → 가짜가... 진짜 행세를 하게 될 대상!
private Object target;
// 생성자 정의
public CalculatorProxy(Object target)
{
this.target = target;
}
// 보조 업무 적용 및 주 업무 호출 과정 추가
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
Object result = null;
// 보조 업무(cross-cutting concern) 설정
//-- 시간 측정 및 로그 기록(Around Advice)
Log log = LogFactory.getLog(this.getClass());
StopWatch sw = new StopWatch();
sw.start();
log.info("처리 시간 측정 시작-----------------");
// 주 업무(core concern) 실행 내용
result = method.invoke(target, args);
// target이 더욱 진짜 행세를 잘 할 수 있도록
// 진짜가 갖고 있는 요소들을 넣어주는 것.
// 보조 업무(cross-cutting concern) 설정
//-- 시간 측정 종료 및 로그 기록(Around Advice)
sw.stop();
log.info("처리 시간 측정 종료-----------------");
log.info(String.format("경과시간 : %s/10000초", sw.getTotalTimeMillis()));
return result;
}
}
CalculatorImpl.java
/*================================================
CalculatorImpl.java
- 클래스.
- Calculator 인터페이스를 구현하는 클래스.
- 주 업무만 적용되어 있는 형태.
==================================================*/
package com.test.spr;
public class CalculatorImpl implements Calculator
{
// 주 업무(core concern) 진행을 위한 메소드 구현
@Override
public int add(int x, int y)
{
int result = 0;
// 주 업무(core concern) 실행 내용
result = x + y;
System.out.printf("%d + %d = %d%n", x, y, result);
return result;
}
@Override
public int sub(int x, int y)
{
int result = 0;
// 주 업무(core concern) 실행 내용
result = x - y;
System.out.printf("%d - %d = %d%n", x, y, result);
return result;
}
@Override
public int mul(int x, int y)
{
int result = 0;
// 주 업무(core concern) 실행 내용
result = x * y;
System.out.printf("%d * %d = %d%n", x, y, result);
return result;
}
@Override
public int div(int x, int y)
{
int result = 0;
// 주 업무(core concern) 실행 내용
result = x / y;
System.out.printf("%d / %d = %d%n", x, y, result);
return result;
}
}
Main.java
/*============================================
Main.java
- main() 메소드가 포함된 테스트 클래스
==============================================*/
package com.test.spr;
import java.lang.reflect.Proxy;
public class Main
{
public static void main(String[] args)
{
// 주 업무 실행을 할 수 있는 객체 준비
// 인터페이스 변수 = new 인터페이스구현클래스();
// List list = new ArrayList();
Calculator cal = new CalculatorImpl();
// 주 업무 실행에 대한 테스트
// -------------------------------- → AOP 기법 적용 전
/*
int add = cal.add(10, 20);
System.out.println(add);
int sub = cal.sub(10, 20);
System.out.println(sub);
int multi = cal.mul(10, 20);
System.out.println(multi);
int div = cal.div(10, 20);
System.out.println(div);
*/
// 주 업무 실행에 대한 테스트
// -------------------------------- → AOP 기법 적용 후
Calculator proxy = (Calculator)Proxy.newProxyInstance
( cal.getClass().getClassLoader() //-- [loader] : 주 업무 실행 클래스에 대한 정보 전달(제공)
, cal.getClass().getInterfaces() //-- [interfaces] : 주 업무 실행 클래스의 인터페이스에 대한 정보 전달(제공)
, new CalculatorProxy(cal) //-- [h] : 보조 업무 실행 클래스에 대한 정보 전달(제공)
);
// ①
// cal(객체).getClass() : 객체의 설계도 → 이를 로드해야함.
// cal(객체).getClass().getClassLoader() : 진짜(별 객체)의 클래스를 로드
// ② 진짜에 해당하는 인터페이스를 가져와서 이를 흉내내자!!
// ③ 프록시가 따라해야 할 객체를 매개변수로 넘겨준다.
// ==============================================================
// 모든 정보가 구성이 된 후에는, 『Proxy.newProxyInstance』에서
// 흉내내기를 완료한 가짜 객체가 생성되므로
// 이를 반환받아서 흉내내려고 했던 타입의 객체로 받아낸다.
// ==============================================================
int add = proxy.add(10, 20);
System.out.println(add);
int sub = proxy.sub(10, 20);
System.out.println(sub);
int multi = proxy.mul(10, 20);
System.out.println(multi);
int div = proxy.div(10, 20);
System.out.println(div);
}
}
[실행 결과 콘솔창]
SpringPrj07 - com.test.spr
■■■ AOP 개념 실습 03 ■■■
※ Spring AOP 기법을 적용하는 형태로 개념 실습
○ 실습 성격 및 주요사항
1. 콘솔 프로젝트
2. 기본적인 산술 연산 처리
3. Spring AOP 기법을 적용하여
보조 업무(시간 측정, 로그 기록 처리)를 수행하는 실습을 진행한다.
4. SpringPrj07
5. 로그 및 스톱워치 기능 사용을 위해 관련 jar 파일을 등록하여
실습을 진행할 수 있도록 한다.
○ 등록해야 할 jar 파일
(로그 및 스톱워치 사용을 위해 등록해야 할 jar 파일
+ 스프링 AOP 기법을 적용하기 위한 jar 파일)
- 경로 1 → C:\s-f-3.0.2-with-docs\dist
·파일 1 → org.springframework.asm-3.0.2.RELEASE.jar
·파일 2 → org.springframework.beans-3.0.2.RELEASE.jar
·파일 3 → org.springframework.context-3.0.2.RELEASE.jar
·파일 4 → org.springframework.core-3.0.2.RELEASE.jar
·파일 5 → org.springframework.expression-3.0.2.RELEASE.jar
·파일 6 → org.springframework.aop-3.0.2.RELEASE.jar
- 경로 2 → C:\s-f-3.0.2-dependencies\org.apache.commons
\com.springsource.org.apache.commons.logging\1.1.1
·파일 7 → com.springsource.org.apache.commons.logging-1.1.1.jar
- 경로 3 → C:\s-f-3.0.2-dependencies\org.aopalliance
\com.springsource.org.aopalliance\1.0.0
·파일 8 → com.springsource.org.aopalliance-1.0.0.jar
○ 물리적 파일 구성
1. Calculator.java → 인터페이스(기존 소스코드 그대로 활용)
2. CalculatorAspect.java → 보조 업무 클래스
(추가~!!!) 보조 업무 적용 및 주 업무 호출 과정
3. CalculatorImpl.java → 클래스. 주 업무 적용. (기존 소스코드 그대로 활용)
4. Main.java → 클래스. main() 메소드를 포함하는 클래스.
5. config.xml → 스프링 환경 설정 파일.
(추가~!!!) 객체 생성 및 DI 설정.
Calculator.java
/*========================
Calculator.java
- 인터페이스
=========================*/
package com.test.spr;
// ※ 스프링 AOP 기법을 적용하기 위해서는
// 대상 객체가 인터페이스를 구현하고 있어야 한다.
public interface Calculator
{
// 주 업무(core concern)진행을 위한 (추상) 메소드 선언
//-- 덧셈, 뺄셈, 곱셈, 나눗셈
public int add(int x, int y);
public int sub(int x, int y);
public int mul(int x, int y);
public int div(int x, int y);
}
CalculatorAspect.java
/*=====================================
CalculatorAspect.java
- 보조 업무 수행 클래스.
- 보조 업무 적용 및 주 업무 호출
=======================================*/
// ※ Spring AOP Proxy 클래스를 만들기 위해서
// MethodIntercepter 인터페이스를 구현하는 클래스로 설계한다.
package com.test.spr;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StopWatch;
public class CalculatorAspect implements MethodInterceptor
{
@Override
public Object invoke(MethodInvocation method) throws Throwable
{
Object result = null;
// 보조 업무(cross-cutting concern) 설정
//-- 시간 측정 시작 및 로그 기록(Around Advice)
Log log = LogFactory.getLog(this.getClass());
StopWatch sw = new StopWatch();
sw.start();
log.info("처리 시간 측정 시작---------------");
// 주 업무(core concern) 호출 부분~!!!
result = method.proceed();
// 보조 업무(cross-cutting concern) 설정
//-- 시간 측정 종료 및 로그 기록(Around Advice)
sw.stop();
log.info("처리 시간 측정 종료----------------");
log.info(String.format("경과시간: %s/10000초", sw.getTotalTimeMillis()));
return null;
}
}
CalculatorImpl.java
/*================================================
CalculatorImpl.java
- 클래스.
- Calculator 인터페이스를 구현하는 클래스.
- 주 업무만 적용되어 있는 형태.
==================================================*/
package com.test.spr;
public class CalculatorImpl implements Calculator
{
// 주 업무(core concern) 진행을 위한 메소드 구현
@Override
public int add(int x, int y)
{
int result = 0;
// 주 업무(core concern) 실행 내용
result = x + y;
System.out.printf("%d + %d = %d%n", x, y, result);
return result;
}
@Override
public int sub(int x, int y)
{
int result = 0;
// 주 업무(core concern) 실행 내용
result = x - y;
System.out.printf("%d - %d = %d%n", x, y, result);
return result;
}
@Override
public int mul(int x, int y)
{
int result = 0;
// 주 업무(core concern) 실행 내용
result = x * y;
System.out.printf("%d * %d = %d%n", x, y, result);
return result;
}
@Override
public int div(int x, int y)
{
int result = 0;
// 주 업무(core concern) 실행 내용
result = x / y;
System.out.printf("%d / %d = %d%n", x, y, result);
return result;
}
}
config.xml (applicationContext.xml 에서 이름만 변경)
<?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-3.0.xsd">
<!-- ※ 스프링이 제공하는 환경 설정 XML 파일 샘플 활용 -->
<!-- → 스프링이 생성하고 관리해야 할 객체들에 대한 정보 전달 -->
<!-- CalculatorImpl 클래스의 객체 생성 및 관리를 위한 정보 전달 -->
<bean id="cal" class="com.test.spr.CalculatorImpl"></bean>
<!-- CalculatorAspect 클래스의 객체 생성 및 관리를 위한 정보 전달 -->
<bean id="aspect" class="com.test.spr.CalculatorAspect"></bean>
<!-- 스프링이 제공하는 가짜 객체(Proxy) 클래스의 객체 생성 및 관리를 위한 정보 전달 -->
<!-- → 『ProxyFactoryBean』 -->
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 주 업무 클래스의 인터페이스 정보 제공 -->
<!-- → 『proxyInterfaces』 -->
<property name="proxyInterfaces">
<list>
<value>com.test.spr.Calculator</value>
<!--===========================================================-->
<!-- bean 구성을 못하는 interface 이기 때문에 -->
<!-- 위와 같이 클래스의 주소와 클래스명을 작성해 준 것이다. -->
<!--===========================================================-->
</list>
</property>
<!-- 주 업무 클래스의 객체 정보 제공 -->
<!-- → 『target』 -->
<property name="target" ref="cal"></property>
<!-- 보조 업무 클래스의 객체 정보 제공 -->
<!-- → 『interceptorNames』 : 가로챈 아이들의 이름들... -->
<property name="interceptorNames">
<list>
<value>aspect</value>
</list>
</property>
</bean>
</beans>
Main.java
/*============================================
Main.java
- main() 메소드가 포함된 테스트 클래스
==============================================*/
package com.test.spr;
import java.lang.reflect.Proxy;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main
{
public static void main(String[] args)
{
// 주 업무 실행을 할 수 있는 객체 준비
// 인터페이스 변수 = new 인터페이스구현클래스();
// List list = new ArrayList();
// Calculator cal = new CalculatorImpl();
// 주 업무 실행에 대한 테스트
// -------------------------------- → AOP 기법 적용 전
/*
int add = cal.add(10, 20);
System.out.println(add);
int sub = cal.sub(10, 20);
System.out.println(sub);
int multi = cal.mul(10, 20);
System.out.println(multi);
int div = cal.div(10, 20);
System.out.println(div);
*/
// 주 업무 실행에 대한 테스트
// -------------------------------- → AOP 기법 적용 후
/*
Calculator proxy = (Calculator)Proxy.newProxyInstance
( cal.getClass().getClassLoader() //-- [loader] : 주 업무 실행 클래스에 대한 정보 전달(제공)
, cal.getClass().getInterfaces() //-- [interfaces] : 주 업무 실행 클래스의 인터페이스에 대한 정보 전달(제공)
, new CalculatorProxy(cal) //-- [h] : 보조 업무 실행 클래스에 대한 정보 전달(제공)
);
// ①
// cal(객체).getClass() : 객체의 설계도 → 이를 로드해야함.
// cal(객체).getClass().getClassLoader() : 진짜(별 객체)의 클래스를 로드
// ② 진짜에 해당하는 인터페이스를 가져와서 이를 흉내내자!!
// ③ 프록시가 따라해야 할 객체를 매개변수로 넘겨준다.
// ==============================================================
// 모든 정보가 구성이 된 후에는, 『Proxy.newProxyInstance』에서
// 흉내내기를 완료한 가짜 객체가 생성되므로
// 이를 반환받아서 흉내내려고 했던 타입의 객체로 받아낸다.
// ==============================================================
int add = proxy.add(10, 20);
System.out.println(add);
int sub = proxy.sub(10, 20);
System.out.println(sub);
int multi = proxy.mul(10, 20);
System.out.println(multi);
int div = proxy.div(10, 20);
System.out.println(div);
*/
// 주 업무 실행에 대한 테스트
// ------------------------------------- → Spring AOP 기법 적용 후
ApplicationContext context = new ClassPathXmlApplicationContext("config.xml");
//Calculator cal = new CalculatorImpl();
//Calculator cal = (Calculator) 객체 수신;
Calculator cal = context.getBean("proxy", Calculator.class);
int add = cal.add(10, 20);
System.out.println(add);
int sub = cal.sub(10, 20);
System.out.println(sub);
int multi = cal.mul(10, 20);
System.out.println(multi);
int div = cal.div(10, 20);
System.out.println(div);
}
}
[실행 결과 콘솔창]
SpringPrj08 - com.test.spr
■■■ AOP 개념 실습 04 ■■■
※ AOP Advice
·Advice
언제 어떤 공통 관심 사항(보조 업무, cross-cutting concern)을
적용할지 결정하는 방법.
Before Advice, After Advice, Around Advice 등이 있다.
·Before Advice
보조 업무가 주 업무 실행 전에 수행되는 경우.
·After Advice
보조 업무가 주 업무 실행 후에 수행되는 경우.
·Around Advice
보조 업무가 주 업무 실행 전과 후에 수행되는 경우.
○ 실습 성격 및 주요사항
1. 콘솔 프로젝트
2. 기본적인 산술 연산 처리
3. Spring AOP 기법을 Advice 로 구분하여
보조 업무(시간 측정, 로그 기록 처리)를 수행하는 실습을 진행한다.
4. SpringPrj08
5. 로그 및 스톱워치 기능 사용을 위해 관련 jar 파일을 등록하여 실습을 진행할 수 있도록 한다.
6. 추가로 Spring AOP 기법을 적용하기 위한 jar 파일을 등록하여 실습을 진행할 수 있도록 한다.
○ 등록해야 할 jar 파일
(로그 및 스톱워치 사용을 위해 등록해야 할 jar 파일
+ 스프링 AOP 기법을 적용하기 위한 jar 파일)
- 경로 1 → C:\s-f-3.0.2-with-docs\dist
·파일 1 → org.springframework.aop-3.0.2.RELEASE.jar
·파일 2 → org.springframework.asm-3.0.2.RELEASE.jar
·파일 3 → org.springframework.beans-3.0.2.RELEASE.jar
·파일 4 → org.springframework.context-3.0.2.RELEASE.jar
·파일 5 → org.springframework.core-3.0.2.RELEASE.jar
·파일 6 → org.springframework.expression-3.0.2.RELEASE.jar
- 경로 2 → C:\s-f-3.0.2-dependencies\org.apache.commons
\com.springsource.org.apache.commons.logging\1.1.1
·파일 7 → com.springsource.org.apache.commons.logging-1.1.1.jar
- 경로 3 → C:\s-f-3.0.2-dependencies\org.aopalliance
\com.springsource.org.aopalliance\1.0.0
·파일 8 → com.springsource.org.aopalliance-1.0.0.jar
○ 물리적 파일 구성
1. Calculator.java → 인터페이스(기존 소스코드 그대로 활용)
2. CalculatorAspect.java → 보조 업무 클래스
보조 업무 적용 및 주 업무 호출 과정
3. CalculatorImpl.java → 클래스. 주 업무 적용. (기존 소스코드 그대로 활용)
4. Main.java → 클래스. main() 메소드를 포함하는 클래스.
5. config.xml → 스프링 환경 설정 파일.
객체 생성 및 DI 설정.
6. CalculatorBeforeAdvice.java → 추가. BeforeAdvice
Calculator.java
/*========================
Calculator.java
- 인터페이스
=========================*/
package com.test.spr;
// ※ 스프링 AOP 기법을 적용하기 위해서는
// 대상 객체가 인터페이스를 구현하고 있어야 한다.
public interface Calculator
{
// 주 업무(core concern)진행을 위한 (추상) 메소드 선언
//-- 덧셈, 뺄셈, 곱셈, 나눗셈
public int add(int x, int y);
public int sub(int x, int y);
public int mul(int x, int y);
public int div(int x, int y);
}
CalculatorAspect.java
/*=====================================
CalculatorAspect.java
- 보조 업무 수행 클래스.
- 보조 업무 적용 및 주 업무 호출
=======================================*/
// ※ Spring AOP Proxy 클래스를 만들기 위해서
// MethodIntercepter 인터페이스를 구현하는 클래스로 설계한다.
package com.test.spr;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StopWatch;
public class CalculatorAspect implements MethodInterceptor
{
@Override
public Object invoke(MethodInvocation method) throws Throwable
{
Object result = null;
// 보조 업무(cross-cutting concern) 설정
//-- 시간 측정 시작 및 로그 기록(Around Advice)
Log log = LogFactory.getLog(this.getClass());
StopWatch sw = new StopWatch();
sw.start();
log.info("처리 시간 측정 시작---------------");
// 주 업무(core concern) 호출 부분~!!!
result = method.proceed();
// 보조 업무(cross-cutting concern) 설정
//-- 시간 측정 종료 및 로그 기록(Around Advice)
sw.stop();
log.info("처리 시간 측정 종료----------------");
log.info(String.format("경과시간: %s/10000초", sw.getTotalTimeMillis()));
return result;
}
}
CalculatorImpl.java
/*================================================
CalculatorImpl.java
- 클래스.
- Calculator 인터페이스를 구현하는 클래스.
- 주 업무만 적용되어 있는 형태.
==================================================*/
package com.test.spr;
public class CalculatorImpl implements Calculator
{
// 주 업무(core concern) 진행을 위한 메소드 구현
@Override
public int add(int x, int y)
{
int result = 0;
// 주 업무(core concern) 실행 내용
result = x + y;
System.out.printf("%d + %d = %d%n", x, y, result);
return result;
}
@Override
public int sub(int x, int y)
{
int result = 0;
// 주 업무(core concern) 실행 내용
result = x - y;
System.out.printf("%d - %d = %d%n", x, y, result);
return result;
}
@Override
public int mul(int x, int y)
{
int result = 0;
// 주 업무(core concern) 실행 내용
result = x * y;
System.out.printf("%d * %d = %d%n", x, y, result);
return result;
}
@Override
public int div(int x, int y)
{
int result = 0;
// 주 업무(core concern) 실행 내용
result = x / y;
System.out.printf("%d / %d = %d%n", x, y, result);
return result;
}
}
Main.java
/*============================================
Main.java
- main() 메소드가 포함된 테스트 클래스
==============================================*/
package com.test.spr;
import java.lang.reflect.Proxy;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main
{
public static void main(String[] args)
{
// 주 업무 실행을 할 수 있는 객체 준비
// 인터페이스 변수 = new 인터페이스구현클래스();
// List list = new ArrayList();
// Calculator cal = new CalculatorImpl();
// 주 업무 실행에 대한 테스트
// -------------------------------- → AOP 기법 적용 전
/*
int add = cal.add(10, 20);
System.out.println(add);
int sub = cal.sub(10, 20);
System.out.println(sub);
int multi = cal.mul(10, 20);
System.out.println(multi);
int div = cal.div(10, 20);
System.out.println(div);
*/
// 주 업무 실행에 대한 테스트
// -------------------------------- → AOP 기법 적용 후
/*
Calculator proxy = (Calculator)Proxy.newProxyInstance
( cal.getClass().getClassLoader() //-- [loader] : 주 업무 실행 클래스에 대한 정보 전달(제공)
, cal.getClass().getInterfaces() //-- [interfaces] : 주 업무 실행 클래스의 인터페이스에 대한 정보 전달(제공)
, new CalculatorProxy(cal) //-- [h] : 보조 업무 실행 클래스에 대한 정보 전달(제공)
);
// ①
// cal(객체).getClass() : 객체의 설계도 → 이를 로드해야함.
// cal(객체).getClass().getClassLoader() : 진짜(별 객체)의 클래스를 로드
// ② 진짜에 해당하는 인터페이스를 가져와서 이를 흉내내자!!
// ③ 프록시가 따라해야 할 객체를 매개변수로 넘겨준다.
// ==============================================================
// 모든 정보가 구성이 된 후에는, 『Proxy.newProxyInstance』에서
// 흉내내기를 완료한 가짜 객체가 생성되므로
// 이를 반환받아서 흉내내려고 했던 타입의 객체로 받아낸다.
// ==============================================================
int add = proxy.add(10, 20);
System.out.println(add);
int sub = proxy.sub(10, 20);
System.out.println(sub);
int multi = proxy.mul(10, 20);
System.out.println(multi);
int div = proxy.div(10, 20);
System.out.println(div);
*/
// 주 업무 실행에 대한 테스트
// ------------------------------------- → Spring AOP 기법 적용 후
ApplicationContext context = new ClassPathXmlApplicationContext("config.xml");
//Calculator cal = new CalculatorImpl();
//Calculator cal = (Calculator) 객체 수신;
Calculator cal = context.getBean("proxy", Calculator.class);
int add = cal.add(10, 20);
System.out.println(add);
int sub = cal.sub(10, 20);
System.out.println(sub);
int multi = cal.mul(10, 20);
System.out.println(multi);
int div = cal.div(10, 20);
System.out.println(div);
}
}
config.xml
<?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-3.0.xsd">
<!-- ※ 스프링이 제공하는 환경 설정 XML 파일 샘플 활용 -->
<!-- → 스프링이 생성하고 관리해야 할 객체들에 대한 정보 전달 -->
<!-- CalculatorImpl 클래스의 객체 생성 및 관리를 위한 정보 전달 -->
<bean id="cal" class="com.test.spr.CalculatorImpl"></bean>
<!-- CalculatorAspect 클래스의 객체 생성 및 관리를 위한 정보 전달 -->
<bean id="aspect" class="com.test.spr.CalculatorAspect"></bean>
<!-- check~!!! -->
<!-- 추가~!!! -->
<!-- CalculatorBefoerAdvice 클래스의 객체 생성 및 관리를 위한 정보 전달 -->
<bean id="before" class="com.test.spr.CalculatorBeforeAdvice"></bean>
<!-- 스프링이 제공하는 가짜 객체(Proxy) 클래스의 객체 생성 및 관리를 위한 정보 전달 -->
<!-- → 『ProxyFactoryBean』 -->
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 주 업무 클래스의 인터페이스 정보 제공 -->
<!-- → 『proxyInterfaces』 -->
<property name="proxyInterfaces">
<list>
<value>com.test.spr.Calculator</value>
<!--===========================================================-->
<!-- bean 구성을 못하는 interface 이기 때문에 -->
<!-- 위와 같이 클래스의 주소와 클래스명을 작성해 준 것이다. -->
<!--===========================================================-->
</list>
</property>
<!-- 주 업무 클래스의 객체 정보 제공 -->
<!-- → 『target』 -->
<property name="target" ref="cal"></property>
<!-- 보조 업무 클래스의 객체 정보 제공 -->
<!-- → 『interceptorNames』 : 가로챈 아이들의 이름들... -->
<property name="interceptorNames">
<list>
<!-- Around Advice -->
<value>aspect</value>
<!-- Before Advice -->
<value>before</value>
</list>
</property>
</bean>
</beans>
CalculatorBeforeAdvice.java
/*=================================
CalculatorBeforeAdvice.java
- Before Advice 구성
==================================*/
package com.test.spr;
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.util.StopWatch;
public class CalculatorBeforeAdvice implements MethodBeforeAdvice
{
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable
{
Log log = LogFactory.getLog(this.getClass());
log.info("Before Advice 수행 -------------------");
log.info("주 업무 처리 전에 수행되어야 하는 업무!");
log.info("------------------- Before Advice 수행 ");
}
}
[실행 결과 콘솔창]
■■■ AOP 개념 실습 05 ■■■
※ AOP Advice
·Advice
언제 어떤 공통 관심 사항(보조 업무, cross-cutting concern)을
적용할지 결정하는 방법.
Before Advice, After Advice, Around Advice 등이 있다.
·Before Advice
보조 업무가 주 업무 실행 전에 수행되는 경우.
·After Advice
보조 업무가 주 업무 실행 후에 수행되는 경우.
·Around Advice
보조 업무가 주 업무 실행 전과 후에 수행되는 경우.
○ 실습 성격 및 주요사항
1. 콘솔 프로젝트
2. 기본적인 산술 연산 처리
3. Spring AOP 기법을 Advice 로 구분하여
보조 업무(시간 측정, 로그 기록 처리)를 수행하는 실습을 진행한다.
4. SpringPrj09
5. 로그 및 스톱워치 기능 사용을 위해 관련 jar 파일을 등록하여 실습을 진행할 수 있도록 한다.
6. 추가로 Spring AOP 기법을 적용하기 위한 jar 파일을 등록하여 실습을 진행할 수 있도록 한다.
○ 등록해야 할 jar 파일
(로그 및 스톱워치 사용을 위해 등록해야 할 jar 파일
+ 스프링 AOP 기법을 적용하기 위한 jar 파일)
- 경로 1 → C:\s-f-3.0.2-with-docs\dist
·파일 1 → org.springframework.aop-3.0.2.RELEASE.jar
·파일 2 → org.springframework.asm-3.0.2.RELEASE.jar
·파일 3 → org.springframework.beans-3.0.2.RELEASE.jar
·파일 4 → org.springframework.context-3.0.2.RELEASE.jar
·파일 5 → org.springframework.core-3.0.2.RELEASE.jar
·파일 6 → org.springframework.expression-3.0.2.RELEASE.jar
- 경로 2 → C:\s-f-3.0.2-dependencies\org.apache.commons
\com.springsource.org.apache.commons.logging\1.1.1
·파일 7 → com.springsource.org.apache.commons.logging-1.1.1.jar
- 경로 3 → C:\s-f-3.0.2-dependencies\org.aopalliance
\com.springsource.org.aopalliance\1.0.0
·파일 8 → com.springsource.org.aopalliance-1.0.0.jar
○ 물리적 파일 구성
1. Calculator.java → 인터페이스(기존 소스코드 그대로 활용)
2. CalculatorAspect.java → 보조 업무 클래스
보조 업무 적용 및 주 업무 호출 과정
3. CalculatorImpl.java → 클래스. 주 업무 적용. (기존 소스코드 그대로 활용)
4. Main.java → 클래스. main() 메소드를 포함하는 클래스.
5. config.xml → 스프링 환경 설정 파일.
객체 생성 및 DI 설정.
6. CalculatorBeforeAdvice.java → 추가. Before Advice
7. CalculatorAfterThrowing.java → 추가. After Throwing Advice
Calculator.java
/*========================
Calculator.java
- 인터페이스
=========================*/
package com.test.spr;
// ※ 스프링 AOP 기법을 적용하기 위해서는
// 대상 객체가 인터페이스를 구현하고 있어야 한다.
public interface Calculator
{
// 주 업무(core concern)진행을 위한 (추상) 메소드 선언
//-- 덧셈, 뺄셈, 곱셈, 나눗셈
public int add(int x, int y);
public int sub(int x, int y);
public int mul(int x, int y);
public int div(int x, int y);
}
CalculatorAspect.java
/*=====================================
CalculatorAspect.java
- 보조 업무 수행 클래스.
- 보조 업무 적용 및 주 업무 호출
=======================================*/
// ※ Spring AOP Proxy 클래스를 만들기 위해서
// MethodIntercepter 인터페이스를 구현하는 클래스로 설계한다.
package com.test.spr;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StopWatch;
public class CalculatorAspect implements MethodInterceptor
{
@Override
public Object invoke(MethodInvocation method) throws Throwable
{
Object result = null;
// 보조 업무(cross-cutting concern) 설정
//-- 시간 측정 시작 및 로그 기록(Around Advice)
Log log = LogFactory.getLog(this.getClass());
StopWatch sw = new StopWatch();
sw.start();
log.info("처리 시간 측정 시작---------------");
// 주 업무(core concern) 호출 부분~!!!
result = method.proceed();
// 보조 업무(cross-cutting concern) 설정
//-- 시간 측정 종료 및 로그 기록(Around Advice)
sw.stop();
log.info("처리 시간 측정 종료----------------");
log.info(String.format("경과시간: %s/10000초", sw.getTotalTimeMillis()));
return result;
}
}
CalculatorImpl.java
/*================================================
CalculatorImpl.java
- 클래스.
- Calculator 인터페이스를 구현하는 클래스.
- 주 업무만 적용되어 있는 형태.
==================================================*/
package com.test.spr;
public class CalculatorImpl implements Calculator
{
// 주 업무(core concern) 진행을 위한 메소드 구현
@Override
public int add(int x, int y)
{
int result = 0;
// 주 업무(core concern) 실행 내용
result = x + y;
System.out.printf("%d + %d = %d%n", x, y, result);
return result;
}
@Override
public int sub(int x, int y)
{
int result = 0;
// 주 업무(core concern) 실행 내용
result = x - y;
System.out.printf("%d - %d = %d%n", x, y, result);
return result;
}
@Override
public int mul(int x, int y)
{
int result = 0;
// 주 업무(core concern) 실행 내용
result = x * y;
System.out.printf("%d * %d = %d%n", x, y, result);
return result;
}
@Override
public int div(int x, int y)
{
int result = 0;
// 주 업무(core concern) 실행 내용
result = x / y;
System.out.printf("%d / %d = %d%n", x, y, result);
return result;
}
}
config.xml
<?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-3.0.xsd">
<!-- ※ 스프링이 제공하는 환경 설정 XML 파일 샘플 활용 -->
<!-- → 스프링이 생성하고 관리해야 할 객체들에 대한 정보 전달 -->
<!-- CalculatorImpl 클래스의 객체 생성 및 관리를 위한 정보 전달 -->
<bean id="cal" class="com.test.spr.CalculatorImpl"></bean>
<!-- CalculatorAspect 클래스의 객체 생성 및 관리를 위한 정보 전달 -->
<bean id="aspect" class="com.test.spr.CalculatorAspect"></bean>
<!-- CalculatorBefoerAdvice 클래스의 객체 생성 및 관리를 위한 정보 전달 -->
<bean id="before" class="com.test.spr.CalculatorBeforeAdvice"></bean>
<!-- check~!!! -->
<!-- 추가 -->
<!-- CalculatorAfterThrowing 클래스의 객체 생성 및 관리를 위한 정보 전달 -->
<bean id="afterThrowing" class="com.test.spr.CalculatorAfterThrowing"></bean>
<!-- 스프링이 제공하는 가짜 객체(Proxy) 클래스의 객체 생성 및 관리를 위한 정보 전달 -->
<!-- → 『ProxyFactoryBean』 -->
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 주 업무 클래스의 인터페이스 정보 제공 -->
<!-- → 『proxyInterfaces』 -->
<property name="proxyInterfaces">
<list>
<value>com.test.spr.Calculator</value>
<!--===========================================================-->
<!-- bean 구성을 못하는 interface 이기 때문에 -->
<!-- 위와 같이 클래스의 주소와 클래스명을 작성해 준 것이다. -->
<!--===========================================================-->
</list>
</property>
<!-- 주 업무 클래스의 객체 정보 제공 -->
<!-- → 『target』 -->
<property name="target" ref="cal"></property>
<!-- 보조 업무 클래스의 객체 정보 제공 -->
<!-- → 『interceptorNames』 : 가로챈 아이들의 이름들... -->
<property name="interceptorNames">
<list>
<!-- Around Advice -->
<value>aspect</value>
<!-- Before Advice -->
<value>before</value>
<!-- check~!!! -->
<!-- 추가 -->
<!-- After Throwing Advice -->
<value>afterThrowing</value>
</list>
</property>
</bean>
</beans>
CalculatorBeforeAdvice.java
/*=================================
CalculatorBeforeAdvice.java
- Before Advice 구성
==================================*/
package com.test.spr;
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.util.StopWatch;
public class CalculatorBeforeAdvice implements MethodBeforeAdvice
{
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable
{
Log log = LogFactory.getLog(this.getClass());
log.info("Before Advice 수행 -------------------");
log.info("주 업무 처리 전에 수행되어야 하는 업무!");
log.info("------------------- Before Advice 수행 ");
}
}
CalculatorAfterThrowing.java
/*====================================
CalculatorAfterThrowing.java
- After Throwing Advice 구성
=====================================*/
package com.test.spr;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.ThrowsAdvice;
//**********************************************************
// 주 업무에서 예외가 발생했을 때만 동작하는 클래스이므로
// 예외가 발생하도록 처리를 해야 한다. (throw new (예외명))
//**********************************************************
public class CalculatorAfterThrowing implements ThrowsAdvice
{
//****************************************************
// 주 업무 처리 클래스에서 인위적으로 발생시킨 에러가
// IllegalArgumentException 이므로 매개변수로 전달.
//****************************************************
public void afterThrowing(IllegalArgumentException e) throws Throwable
{
/*
try
{
} catch (Exception e2)
{
}
*/
Log log = LogFactory.getLog(this.getClass());
log.info("After Throwing Advice ------------------------------");
log.info("주업무 처리과정에서 예외 발생시 실행되는 사후 업무");
log.info("------------------------------ After Throwing Advice");
}
}
Main.java
/*============================================
Main.java
- main() 메소드가 포함된 테스트 클래스
==============================================*/
package com.test.spr;
import java.lang.reflect.Proxy;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main
{
public static void main(String[] args)
{
// 주 업무 실행을 할 수 있는 객체 준비
// 인터페이스 변수 = new 인터페이스구현클래스();
// List list = new ArrayList();
// Calculator cal = new CalculatorImpl();
// 주 업무 실행에 대한 테스트
// -------------------------------- → AOP 기법 적용 전
/*
int add = cal.add(10, 20);
System.out.println(add);
int sub = cal.sub(10, 20);
System.out.println(sub);
int multi = cal.mul(10, 20);
System.out.println(multi);
int div = cal.div(10, 20);
System.out.println(div);
*/
// 주 업무 실행에 대한 테스트
// -------------------------------- → AOP 기법 적용 후
/*
Calculator proxy = (Calculator)Proxy.newProxyInstance
( cal.getClass().getClassLoader() //-- [loader] : 주 업무 실행 클래스에 대한 정보 전달(제공)
, cal.getClass().getInterfaces() //-- [interfaces] : 주 업무 실행 클래스의 인터페이스에 대한 정보 전달(제공)
, new CalculatorProxy(cal) //-- [h] : 보조 업무 실행 클래스에 대한 정보 전달(제공)
);
// ①
// cal(객체).getClass() : 객체의 설계도 → 이를 로드해야함.
// cal(객체).getClass().getClassLoader() : 진짜(별 객체)의 클래스를 로드
// ② 진짜에 해당하는 인터페이스를 가져와서 이를 흉내내자!!
// ③ 프록시가 따라해야 할 객체를 매개변수로 넘겨준다.
// ==============================================================
// 모든 정보가 구성이 된 후에는, 『Proxy.newProxyInstance』에서
// 흉내내기를 완료한 가짜 객체가 생성되므로
// 이를 반환받아서 흉내내려고 했던 타입의 객체로 받아낸다.
// ==============================================================
int add = proxy.add(10, 20);
System.out.println(add);
int sub = proxy.sub(10, 20);
System.out.println(sub);
int multi = proxy.mul(10, 20);
System.out.println(multi);
int div = proxy.div(10, 20);
System.out.println(div);
*/
// 주 업무 실행에 대한 테스트
// ------------------------------------- → Spring AOP 기법 적용 후
ApplicationContext context = new ClassPathXmlApplicationContext("config.xml");
//Calculator cal = new CalculatorImpl();
//Calculator cal = (Calculator) 객체 수신;
Calculator cal = context.getBean("proxy", Calculator.class);
int add = cal.add(10, 20);
System.out.println(add);
/*
int sub = cal.sub(10, 20);
System.out.println(sub);
int multi = cal.mul(10, 20);
System.out.println(multi);
int div = cal.div(10, 20);
System.out.println(div);
*/
// 예외 상황이 발생할 수 있도록 값을 넘기는 처리
int add2 = cal.add(100, 200);
System.out.println(add2);
}
}
[실행 결과 콘솔창]
'[Spring] > Program source (Spring)' 카테고리의 다른 글
[SpringMVC] 20240110 [프로그램 소스] (2) | 2024.01.10 |
---|---|
[Spring] 20240108 [프로그램 소스] (1) | 2024.01.08 |
[Spring] 20240105 - [프로그램 소스] (0) | 2024.01.07 |