우당탕탕 개발일지
63일차_AOP & JDBC (방법 3가지) 본문
[ AOP(Aspect-Oriented Programming) ]
관점(Aspect) 지향 프로그래밍
: 관점을 기준으로 다양한 기능을 분리하여 보는 프로그래밍
* 관점(Aspect)이란?
: 부가 기능과 그 적용처를 정의하고 합쳐서 모듈로 만든 것
1. AOP의 목적
OOP(객체 지향 프로그래밍) 보완한 개념
OOP에서는 핵심 비즈니스 로직이든 부가 기능의 로직이든 하나의 객체로 분리하여 사용한다. 여기서 객체의 기능을 나눠 사용할 필요가 있다고 느껴 나온 것이 AOP이다.
아래 사진과 같이 함수3개가 있다.
함수1과 함수2는 입실, 퇴실이라는 공통 부분이 존재한다. 이를 공통 코드라고 부른다.
또한, 수업과 카톡은 다른 부분이다. 이를 핵심 코드라고 부른다.
예시로는 DAO에서 모든 함수에서 드라이버 연결 후, sql 문 실행, 드라이버 닫기를 반복한다.
여기서 핵심 로직은 sql문 실행이 되면 함수에 따라 다른 문장을 실행한다.
하지만 드라이버 연결 및 닫기는 부가 기능으로 모든 함수에서 반복적으로 사용한다.
AOP를 사용하면 이를 핵심 기능만 구현하면 된다.
2. AOP 용어
1. Target : 부가기능을 부여할 대상, 핵심기능이 담긴 클래스
2. Advice : 부가기능을 담은 모듈
언제 공통 관심 기능을 핵심 로직에 적용할 지를 정의
- Before : 조인 포인트 이전
- After Returning : 조인 포인트 완료 후
- After Throwing : 함수에서 예외를 던지는 경우
- Around : 함수 호출 전후
3. Joinpoint : Advice를 적용 가능한 지점을 의미
메소드 호출, 필드 값 변경 등
스프링의 프록시 AOP에서 조인 포인트는 메소드의 실행 단계뿐이다.
타깃 오브젝트가 구현한 인터페이스의 모든 메소드가 조인 포인트가 된다
4. Pointcut : 조인 포인트를 선별하는 기능을 정의한 모듈
가능한 조인 포인트들 중에 실제로 부가기능을 적용할 것들을 선별한다. 즉, 핵심코드가 정의되어 있는 함수를 선별한다.
클래스를 선정하고, 그 안의 메소드를 선정하는 과정을 거친다 실제로 Advice가 적용되는 Joinpoint를 나타낸다
5. Proxy : 클라이언트와 타깃 사이에 존재하면서 부가기능을 제공하는 오브젝트
클라이언트는 타깃을 요청하지만, 클라이언트에게는 DI를 통해 타깃 대신 프록시가 주입된다
클라이언트의 메소드 호출을 대신 받아서 타깃에게 위임하며, 그 과정에서 부가기능을 부여한다.
스프링 AOP는 프록시를 이용한다
6. Advisor : 어드바이스와 포인트컷을 하나로 묶어 취급한 것
AOP의 가장 기본이 되는 모듈이다.
스프링은 자동 프록시 생성기가 어드바이저 단위로 검색해서 AOP를 적용한다.
7. Aspect : 다수의 포인트컷과 어드바이스의 조합으로 만들어진다.
보통 싱글톤 형태의 오브젝트로 존재한다.
어드바이저는 아주 단순한 애스펙트라고 볼 수 있다
8. Weaving : Advice를 핵심로직코드에 적용하는 것을 Weaving라고 한다.
3. 환경설정 : pom.xml
aop를 사용하기 위해서는 pom.xml 파일에 dependency 를 적용시켜줘야 한다.
[ AOP 예제 ]
예제1: XML
프로젝트명: Chapter03_XML
패키지명: sample01
1. Bean 등록
acQuickStart.xml
messageBeanImpl
- 클래스: sample01.MessageBeanImpl
- setter 주입: str
- 핵심 코드를 가지고 있는 클래스
loggingAdvice
- 클래스: sample01.LoggingAdvice
- 공통 코드를 가지고 있는 클래스
<bean id="messageBeanImpl" class="sample01.MessageBeanImpl">
<property name="str" value="Have a nice day!!"/>
</bean>
<bean id="loggingAdvice" class="sample01.LoggingAdvice"></bean>
2. AOP 설정
acQuickStart.xml
<aop:pointcut/>
: 타켓 클래스의 함수 정의
- execution(함수형태): 프로젝트가 실행될 때 적용
- 함수형태: 범위 반환값 클래스경로.함수명()
- 매개변수 있을 경우 >> 함수(..)
- 함수형태: 범위 반환값 클래스경로.함수명()
- id: 개발자가 설정한 포인트컷의 이름
<aop:aspect/>
: 부가기능 함수 정의
- ref: 부가 기능이 정의되어 있는 클래스
<aop:~~~/>
- before : 공통 코드 -> 핵심 코드
- after : 핵심 코드 -> 공통 코드
- around : 공통 코드 -> 핵심 코드 -> 공통 코드
- method : loggingAdvice 클래스에서 공통 코드가 정의되어 있는 함수
- pointcut-ref : 핵심 코드가 정의되어 있는 함수, 즉 포인트컷을 의미함.
Before & After & Around
MessageBean.java
package sample01;
public interface MessageBean {
public void showPrintBefore();
public void viewPrintBefore();
public void showPrintAfter();
public void viewPrintAfter();
public String showPrint();
public void viewPrint();
public void display();
}
MessageBeanImpl.java
핵심코드
Before
- showPrintBefore()
- viewPrintBefore()
After
- showPrintAfter()
- viewPrintAfter()
Around
- showPrint()
- viewPrint()
display() 함수는 일반 함수
package sample01;
import lombok.Setter;
@Setter
public class MessageBeanImpl implements MessageBean { // 타겟 클래스
private String str;
// join point & point cut
@Override
public void showPrintBefore() {
System.out.println("showPrintBefore 메세지: " + str);
}
// join point & point cut
@Override
public void viewPrintBefore() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("viewPrintBefore 메세지: " + str);
}
@Override
public void showPrintAfter() {
System.out.println("showPrintAfter 메세지: " + str);
}
@Override
public void viewPrintAfter() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("viewPrintAfter 메세지: " + str);
}
@Override
public String showPrint() {
System.out.println("showPrint 메세지: " + str);
return "Spring";
}
@Override
public void viewPrint() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("viewPrint 메세지: " + str);
}
// join point
@Override
public void display() {
System.out.println("display 메세지: " + str);
}
}
LoggingAdvice.java
부가기능
before
- beforeTrace()
after
- afterTrace()
around
- aroundTrace()
before, after 함수는 크게 특별한 것이 없이 xml 파일에서 정의한 것처럼 수행된다.
하지만 around는 ProceedingJoinPoint 객체를 사용하여 핵심코드를 언제 호출할지 위치를 나타낸다.
joinPoint.proceed() 함수를 사용하여 핵심코드를 호출한다.
ProceedingJoinPoint 객체를 사용하면 핵심 코드의 시작 시간, 종료 시간도 알 수 있다.
package sample01;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
public class LoggingAdvice {
public void beforeTrace() {
System.out.println("before Trace");
}
public void afterTrace() {
System.out.println("after Trace");
}
public void aroundTrace(ProceedingJoinPoint joinPoint ) throws Throwable {
System.out.println("1. around Trace");
String methodName = joinPoint.getSignature().toShortString();
System.out.println("메소드: " + methodName);
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object ob = joinPoint.proceed(); // 핵심 코드 호출
System.out.println("반환값: " + ob);
stopWatch.stop();
System.out.println("처리 시간: " + stopWatch.getTotalTimeMillis()/1000 + "초");
System.out.println("2. around Trace");
}
}
HelloSpring.java
함수 호출
package sample01;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class HelloSpring {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("acQuickStart.xml");
MessageBean messageBean = (MessageBean) context.getBean("messageBeanImpl");
System.out.println("*** Before ***");
messageBean.showPrintBefore();
System.out.println();
messageBean.viewPrintBefore();
System.out.println();
messageBean.display();
System.out.println("\n");
System.out.println("*** After ***");
messageBean.showPrintAfter();
System.out.println();
messageBean.viewPrintAfter();
System.out.println();
messageBean.display();
System.out.println("\n");
System.out.println("*** Around ***");
messageBean.showPrint();
System.out.println();
messageBean.viewPrint();
System.out.println();
messageBean.display();
}
}
결과 화면
예제2: 어노테이션
프로젝트명: Chapter03_ANNO
패키지명: sample01
예제1에서는 xml 파일에 모든 것을 정의했다면 이번에는 어노테이션을 가지고 같은 결과를 나타내보겠다.
예제1에서 어노테이션을 제외한 모든 코드가 동일하다.
1. 환경설정 파일
acQuickStart.xml
<aop:aspectj-autoproxy />
- aop를 사용하겠다고 알려줌
<context:component-scan/>
- 빈를 생성할 패키지를 등록
2. 공통 코드
LoggingAdvice.java
@Aspect
- 공통 코드가 정의되어 있는 함수가 있는 클래스를 의미함.
() : 핵심 코드 부분 정의 & 예제1에서 xml과 동일함.
@Before("execution(public void sample01.MessageBeanImpl.*Before())")
- 공통 코드 -> 핵심 코드
@After("execution(public void sample01.MessageBeanImpl.*After())")
- 핵심 코드 -> 공통 코드
@Around("execution(public * sample01.MessageBeanImpl.*Print())")
- 공통 코드 -> 핵심 코드 -> 공통 코드
package sample01;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
@Aspect // 공통 코드
@Component
public class LoggingAdvice {
@Before("execution(public void sample01.MessageBeanImpl.*Before())")
public void beforeTrace() {
System.out.println("before Trace");
}
@After("execution(public void sample01.MessageBeanImpl.*After())")
public void afterTrace() {
System.out.println("after Trace");
}
@Around("execution(public * sample01.MessageBeanImpl.*Print())")
public void aroundTrace(ProceedingJoinPoint joinPoint ) throws Throwable {
System.out.println("1. around Trace");
String methodName = joinPoint.getSignature().toShortString();
System.out.println("메소드: " + methodName);
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object ob = joinPoint.proceed(); // 핵심 코드 호출
System.out.println("반환값: " + ob);
stopWatch.stop();
System.out.println("처리 시간: " + stopWatch.getTotalTimeMillis()/1000 + "초");
System.out.println("2. around Trace");
}
}
3. 예제3: Configuration
프로젝트명: Chapter03_ANNO
패키지명: sample01
예제2에서는 어노테이션 사용했고, 예제3에서는 Java 환경 설정 파일을 사용한다.
예제1에서 어노테이션을 제외한 모든 코드가 동일하다.
1. 환경설정 파일
acQuickStart.xml
컨피그 파일 등록
2. @Configuration
SpringConfiguration.java
@EnableAspectJAutoProxy
- <aop:aspectj-autoproxy />와 같은 역할
- 클라이언트와 타깃 사이에 존재하면서 부가기능을 제공하는 오브젝트
Bean 등록
- MessageBeanImpl
- LoggingAdvice
package spring.conf;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import sample01.LoggingAdvice;
import sample01.MessageBeanImpl;
@Configuration
@EnableAspectJAutoProxy
public class SpringConfiguration {
@Bean
public LoggingAdvice loggingAdvice() {
return new LoggingAdvice();
}
@Bean
public MessageBeanImpl messageBeanImpl() {
return new MessageBeanImpl();
}
}
3. 공통 코드
LoggingAdvice.java
예제2와 동일
[ Spring & JDBC ]
스프링은 데이터베이스 연동을 위한 템플릿 클래스 제공
>> JDBC의 중복된 코드 줄임
스프링이 직접적으로 커넥션풀을 제공하진 않지만 DBCP(Jakarta Commons Database Connection Pool) API와 같은 커넥션 풀 라이브러리를 이용
환경 설정: pom.xml
[ JDBC 예제 ]
예제1: JDBC & XML
프로젝트명: Chapter04_XML
스프링이 직접적으로 커넥션풀을 제공 X
DBCP(Jakarta Commons Database Connection Pool) API와 같은 커넥션 풀 라이브러리를 이용
DBCP에는 풀링 기능을 제공하는 BasicDataSource 사용
1. JDBC 환경 설정
JDBC에 연결하는 방법 3가지
1. BasicDataSource 클래스의 setter 주입
2. p 사용
3. 변수화
2. 드라이버 연결
3. Bean 등록
applicationContext.xml
userDAOImpl
- jdbc와 연동되는 jdbcTemplate 객체를 사용하여 Bean 생성
<bean id="userDAOImpl" class="user.dao.impl.UserDAOImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<bean id="userDTO" class="user.bean.UserDTO"></bean>
<bean id="helloSpring" class="user.main.HelloSpring"></bean>
<!-- service -->
<bean id="userInsertService" class="user.service.UserInsertService">
<property name="userDTO" ref="userDTO"></property>
<property name="userDAO" ref="userDAOImpl"></property>
</bean>
<bean id="userSelectService" class="user.service.UserSelectService">
<property name="userDAO" ref="userDAOImpl"></property>
</bean>
<bean id="userUpdateService" class="user.service.UserUpdateService">
<property name="userDTO" ref="userDTO"></property>
<property name="userDAO" ref="userDAOImpl"></property>
</bean>
4. SQL문 수행
UserDAOImpl.java
JdbcTemplate
- 해당 객체를 사용하여 PreparedStatement 열기 & 닫기 자동으로 해줌
이전 자바 프로젝트에서는 DAO에서 싱글톤 설정, 모든 함수에서 드라이버 연결 및 해지 등의 작업을 진행했다.
하지만 spring의 bean 특징을 사용하여 싱글톤 설정을 하지 않아도 된다.
또한 JdbcTemplate 객체를 사용하여 드라이버 연결 및 해지 작업을 자동으로 해준다.
반복되는 코드가 줄어 시간 절약이 되고 코드가 간결해졌다.
package user.dao.impl;
import java.sql.PreparedStatement;
import java.util.List;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import com.mysql.cj.xdevapi.PreparableStatement;
import lombok.Setter;
import user.bean.UserDTO;
import user.dao.UserDAO;
public class UserDAOImpl implements UserDAO {
@Setter
private JdbcTemplate jdbcTemplate; // PreparedStatement 열기 & 닫기 자동으로 해줌
String sql;
@Override
public void write(UserDTO userDTO) {
sql = "insert into usertable values(?, ?, ?)";
jdbcTemplate.update(sql, userDTO.getName(), userDTO.getId(), userDTO.getPwd());
}
@Override
public List<UserDTO> getUserList() {
sql = "select * from usertable";
// new BeanPropertyRowMapper(UserDTO.class)
// >> 컬럼과 userDTO의 필드변수와 자동매핑
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<UserDTO>(UserDTO.class));
}
@Override
public boolean isFindId(String id) {
sql = "select count(*) from usertable where id = ?";
boolean isFind = false;
int result = jdbcTemplate.queryForObject(sql, Integer.class, id);
if(result==1) isFind = true;
return isFind;
}
@Override
public void isUpdate(UserDTO userDTO) {
sql = "update usertable set name = ?, pwd= ? where id = ?";
jdbcTemplate.update(sql, userDTO.getName(), userDTO.getPwd(), userDTO.getId());
}
}
결과 화면
예제2: JDBC & ANNO
프로젝트명: Chapter04_ANNO
* 예제1 프로젝트 복사하여 사용
1. Bean 환경 설정
applicationContext.xml
SpringConfiguration.java
DB 정보 설정
2. Bean 등록
user.main
user.bean
user.service
@Service와 @Component와 동일하게 Bean 등록하는 역할
다만 해당 클래스의 역할을 한눈에 알아볼 수 있게 @Service 사용
user.dao.impl
@Repository와 @Component와 동일하게 Bean 등록하는 역할
다만 해당 클래스의 역할을 한눈에 알아볼 수 있게 @Repository사용
결과 화면
* JDBC 설정 방법 3가지
1.JdbcTemplate객체 변수 선언
앞의 예제에서 사용했던 방법
UserDAOImpl 클래스에서 JdbcTemplate 객체를 통해 JDBC와 연결 및 해제
2. JdbcDaoSupport 클래스 상속
이번에는 JdbcTemplate 객체를 가지고 있는 JdbcDaoSupport 클래스를 상속받는다.
JdbcDaoSupport 클래스를 살펴보면 setDataSource 함수를 통해서 dataSource를 설정할 수 있다.
setDataSource 함수가 final로 되어 있어 오버라이드는 불가능하다 따라서 부모 setDataSource 함수 사용하여야 한다.
xml에서 빈 등록 할 때, setter주입을 통해서 dataSource를 초기화한다.
JdbcTemplate 객체를 사용할 때에는 JdbcDaoSupport 클래스의 getJdbcTemplate() 함수를 호출하여 사용한다.
3. NamedParameterJdbcDaoSupport 클래스 상속
NamedParameterJdbcDaoSupport클래스 상속받아 JdbcTemplate 객체를 사용한다.
해당 클래스를 상속받으면 getNamedParameterJdbcTemplate() 함수를 통해 JdbcTemplate 객체를 사용할 수 있다.
getNamedParameterJdbcTemplate() 함수는 map의 Key 값을 통해 value를 가져와 매핑한다.
NamedParameterJdbcDaoSupport 클래스는 JdbcDaoSupport 클래스 상속 받기 때문에 getJdbcTemplate() 함수도 사용 가능하다.
'비트캠프 > 이론 및 정리' 카테고리의 다른 글
65일차_Spring MVC(2) & Spring Project(1) (0) | 2024.10.10 |
---|---|
64일차_MyBatis & Spring MVC (0) | 2024.10.07 |
61일차_DI (Dependency Injection) (1) | 2024.10.01 |
60일_Spring Framwork (0) | 2024.09.30 |
59일차_클라우드 버킷에 이미지 저장 (0) | 2024.09.26 |