- Spring framework Workbook 스터디하면서 두서없이 정리한 내용입니다. 조금씩 정리하도록 하겠습니다.
Spring framework
- 프로젝트 초기에 기업 내 시스템 통합과 수월한 유지보수 등 많은 고민거리를 해결할 수 있는 framework 에 대한 선정을 두고 많은 고민을 하곤 합니다. 예전부터 각 대기업이나 중소 SI업체에서 framework를 자체 개발하여 프로젝트에 적용하고 있으나 framework 자체가 특정 부분에 특화되었거나 투입된 프로젝트에서 커스텀마이징되면서 상이한 버젼의 관리가 힘들정도로 어려운 사항에 처한 경우도 발생하였습니다. 이 같은 실정에서 interface12.com 이라는 컨설팅 회사에서 개발한 Spring framework 는 위와 같은 이슈를 해결할 수 있는 능력을 보여주면서부터 전세계 자바 관련 프로젝트에서 많이 사용되고 있습니다. 국내 프로젝트에서 적용된 사례는 아직 미비하지만 현재 많은 개발자들이 이 훌륭한 framework의 존재를 인지하고 있으며 앞으로 많이 사용될 것으로 추측됩니다.
- 또다른 사항은 최근 풍부한 오픈소스로 인해 기업에서 손수 framework를 개발할 필요가 없게 됨에 따라 프로젝트에 적용할 오픈소스를 어떻게 조합할 것인가? 에 대한 새로운 문제상황이 발생하였습니다. Spring framework는 그 자체로도 훌륭한 모듈(AOP, Spring MVC, Transaction, JDBC...)을 포함하고 있을 뿐 아니라 다양한 여러 오픈 소스(Struts, webwork, jsf, hibernate, ibatis ...)를 결합할 수 있는 유연한 설정 및 추상 클래스를 제공하고 있습니다. 또한 기존의 OOP 개발 방식에 횡단적 개발 방식인 AOP 개념을 포함하여 프로젝트 중간에 정책(로깅, 트랜젝션 설정 등)이 변경되거나 추가되었을 때 소스 수정없이 설정만으로 변경 가능하게 구현되어 있습니다. 단, 개발 초기에 위 사항을 고려하여 변경가능성이 있는 정책에 관한 설정을 소스코드에서 분리하는 방안 등을 고려하여 개발하여야 합니다.
EJB 와 Lightweight Container(Non EJB)
- 정형화된 비즈니스 계층을 제공
- 선언적인 트랜젝션 관리를 EJB container 가 제공(ejb deploy descriptor 파일 필요)
- 분산 기능을 지원(business 객체를 여러서버에 분산시켜 서비스 가능)
- 분산 환경의 서비스 호출로 인한 객체 직렬화 과정으로 실행속도 저하
- 개발 사이클, 소스 수정, 빌드, 배포, 테스트의 과정이 복잡하므로 생산성 저하
- EJB container에 종속적이므로(ejb deploy descriptor 파일이 벤더에 따라 다름) application의 이식성이 떨어짐
- Lightweight Container(Non EJB) architecture 의 장점 :
- 경량 container 에 POJO(Plain Old Java Object)로 개발되므로 생산성과 실행속도 향상, 테스트 용이
- EJB에 비해 배우기 쉬우며, descriptor 설정 방법도 쉬움
- servlet container 에서 실행하는 것이 가능하므로 이식성 뛰어남
- EJB container 가 지원하는 transaction 처리, Security, bean life cycle 관리 기능을 지원
- Ligntweight Container(Non EJB) architecture 의 단점 :
- 분산 기능을 지원하지 않음. 단, 분산 기능 필요 시 웹서비스 또는 EJB 와 연동
- Lightweight container에 대한 표준 없음
- Lightweight container architecture를 새로 배워야 하는 부담감 존재
Spring IoC (Inversion of Control)
- Inversion of Control 이란 제어의 역행, 역제어라는 뜻
- 초창기 자바 프로그램에서는 객체 생성 및 의존관계에 대한 모든 제어권이 개발자에게 있었으나 최근 Servlet, EJB의 등장하면서 객체의 생성 및 의존관계, Life Cycle 에 대한 제어권이 Container가 전담하게 됨
- EJB Container는 EJB를 관리, Servlet Container는 Servlet을 관리, Spring에서는 POJO(Plain Old Java Object)의 생성 및 Life cycle을 관리함. 이를 IoC Container 라 일컬음
- Spring Container 에서는 일부 POJO. 즉, 각 Layer의 인터페이스를 구현한 클래스에 대한 제어권을 가지며 내부에서 사용하는 클래스의 생성에 대한 일부 제어권은 개발자에게 있음
IoC
|-------> DL (EJB, Spring)
|-------> DI (Spring)
|-----------> Setter Injection
|-----------> Constructor Injection
|-----------> Method Injection
- IoC : Inversion of Control
- DL : Dependency Lookup
- DI : Dependency Injection
DL(Dependency Lookup)
- 모든 IoC Container는 관리해야 하는 객체들을 관리하기 위한 별도의 repository를 가짐
- Servlet Container 는 web.xml 에서 Servlet을 관리하며 EJB Container는 ejb-jar.xml에 설정된 정보들이 JNDI repository에 저장되어 관리됨
- Spring 에는 POJO를 관리하기 위해 xml 또는 properties 파일을 이용함
- 위처럼 repository 에 저장된 Bean 에 접근하기 위하여 해당 Container에서 제공하는 API를 이용하여 Bean을 Lookup하는 것을 DL(Dependency Lookup) 이라 함
Context ctx = new InitialContext();
UserServiceHome home = (UserServiceHome)ctx.lookup("JNDI_BEAN_NAME");
UserService service = home.create();
WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(servletCtx);
UserService service = (UserService)wac.getBean("KEY_BEAN_NAME");
- DL을 사용할 경우 Container 의 API를 사용해야하는 의존관계가 발생하므로 이를 해결하기 위해 DI(Dependency Injection)을 사용하여 Container 의 종속성을 줄일수 있음
DI (Dependency Injection)
- DI는 각 클래스 사이의 의존관계를 설정파일에 정의된 빈 설정정보를 토대로 Container가 자동으로 연결해 주는 것을 의미하며 이로써 Container API 에 대한 종속성을 줄일 수 있음
- 개발자는 단지 빈 설정파일에 의존관계에 대한 정보를 추가해주기만 하면 됨
- Spring 에서 클래스 사이의 의존관계를 관리하기 위한 방법으로 Setter Injection, Constructor Injection, Method Injection 의 세가지 유형으로 처리함
Setter Injection
- 클래스 사이의 의존관계를 연결시키기 위하여 setter 메소드를 이용
public class UserServiceImpl implements UserService {
private UserDAO userDAO;
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
public int addUser(User user) {
...
return userDAO.insert(user);
}
...
}
- applicationContext.xml 의 내용
<beans>
<bean id="userDAO"
class="net.javajigi.user.dao.MySQLUserDAO" />
<bean id="userService"
class="net.javajigi.user.service.UserServiceImpl" >
<property name="userDAO" >
<ref local="userDAO" />
</property>
</bean>
...
</beans>
Constructor Injection
- 클래스 사이의 의존관계를 연결시키기 위하여 Constructor를 이용
public class UserServiceImpl implements UserService {
private UserDAO userDAO;
public UserServiceImpl(UserDAO userDAO) {
this.userDAO = userDAO;
}
public int addUser(User user) {
...
return userDAO.insert(user);
}
...
}
- applicationContext.xml 의 내용
<beans>
<bean id="userDAO"
class="net.javajigi.user.dao.MySQLUserDAO" />
<bean id="userService"
class="net.javajigi.user.service.UserServiceImpl" >
<constructor-arg>
<ref local="userDAO" />
</constructor-arg>
</bean>
...
</beans>
Method Injection
- Setter Injection, Constructor Injection의 한계점을 극복하기 위하여 Spring 1.1 부터 지원된 DI의 한 종류임
- Spring에서 기본적으로 빈의 인스턴스를 관리하는 방식은 Singleton 인데 Non Singleton 과의 의존관계를 연결할 필요성 발생
- Method Injection은 Singleton 인스턴스와 Non Singleton 인스턴스의 의존관계를 연결시키기 위해서 사용하는 DI 임
http://springframework.org/docs/reference/beans.html#beans-factory-method-injection
http://openframework.or.kr/framework_reference/spring/ver1.2.2/html/beans.html#beans-factory-method-injection
Spring framework의 초기화 및 Bean의 life cycle
- Spring framework이 빈 설정파일 정보를 바탕으로 POJO 빈의 life cycle을 관리하는 과정은 다음과 같음
- 빈 설정파일의 XML DTD 유효성 체크
- 빈 인스턴스 생성하면서 의존관계에 있는 빈이 존재하는지 여부 체크, 존재하지 않는다면 빈의 초기화가 실패
- 의존관계에 있는 빈의 체크가 완료되면 Dependency Injection 빈을 초기화함
- 생성한 빈이 BeanNameAware 인스턴스이면 setBeanName() 메소드 호출
- 생성한 빈이 BeanFactoryAware 인스턴스이면 setBeanFactory() 메소드 호출
- 생성한 빈이 ApplicationContextAware 인스턴스이면 setApplicationContext() 메소드 호출
- 생성한 빈이 InitializingBean 인스턴스이면 afterProperties() 메소드 호출
- 생성한 빈의 설정에 init-method 가 설정되어 있다면 init-method 에 정의된 메소드 호출
- 초기화 과정이 끝나면 빈은 사용 가능한 준비 상태가 되고 BeanFactory의 getBean() 메소드를 이용하여 접근하는 것이 가능
- BeanFactory의 getBean() 메소드를 이용하여 접근할 때 Non Singleton 빈이라면 getBean()메소드가 호출될 때마다 위 그림의 2~8까지의 빈 초기화 과정을 거치게 됨
- Application 이 종료될 때 빈의 종료메소드가 호출되면서 소멸됨
- 생성한 빈이 DisposableBean 인스턴스이면 destory() 메소드 호출
- 생성한 빈의 설정 파일에 destroy-method 가 설정되어 있다면 destory-method 에 정의된 메소드 호출
- POJO의 초기화 지원 방법은 두가지로 InitializingBean 상속 후 afterPropertiesSet() 구현하거나 POJO에 메소드 구현 후 bean태그의 init-method="메소드" 속성 추가함
- POJO의 소멸화 지원 방법 두가지로 DisposableBean 상속 후 destory() 구현하거나 POJO에 메소드 구현 후, bean태그의 destroy-method="메소드" 속성 추가함
- Spring 관리 하의 POJO 빈은 디폴트로 Singleton 이지만 Non Singlton 으로 설정가능함 (bean태그의 singleton="false" 로 설정) 단, 위에서 설명하였듯이 매번 인스턴스를 초기화하면서 의존 상태 검사 등의 로드가 걸림
- Spring 내 Singleton 사용하는 경우 :
- 생성되는 인스턴스 내 공유되는 상태가 없는 경우(예: member variables)
- 생성되는 인스턴스 내 read-only 상태만 있는 경우
- 생성되는 인스턴스 내 공유되는 상태가 있더라도 동기화가 보장되는 경우
public class UserServiceImpl
implements UserService, ApplicationContextAware {
private ApplicationContext ctx;
public void setApplicationContext(ApplicationContext ctx)
throws BeansException {
this.ctx = ctx;
}
...
public boolean login(String userId, String password)
throws PassworkdMismatchException, UserNotFoundException {
...
User user = findUser(userId);
if(!user.isMatchPassword(password)) {
throw new PasswordMismatchException(
ctx.getMessage("password.mismatch.exception", new Object[] { userId }, null));
}
return true;
}
...
}
<beans>
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>Messages</value>
</list>
</property>
</bean>
...
</beans>
public class UserServiceImpl
implements UserService {
private MessageSourceAccessor msAccessor;
public void setMessageSourceAccessor(MessageSourceAccessor msAccessor) {
this.msAccessor = msAccessor;
}
...
public boolean login(String userId, String password)
throws PassworkdMismatchException, UserNotFoundException {
...
User user = findUser(userId);
if(!user.isMatchPassword(password)) {
throw new PasswordMismatchException(
msAccessor.getMessage("password.mismatch.exception", new Object[] { userId }, null));
}
return true;
}
...
}
<beans>
...
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>Messages</value>
</list>
</property>
</bean>
<bean id="messageSourceAccessor"
class="org.springframework.context.support.MessageSourceAccessor">
<constructor-arg>
<ref local="messageSource" />
</constructor-arg>
</bean>
<bean id="userService"
class="net.javajigi.user.service.UserServiceImpl" >
<property name="userDAO" >
<ref local="userDAO" />
</property>
<property name="messageSourceAccessor" >
<ref local="messageSourceAccessor" />
</property>
</bean>
...
</beans>
- 한글 Messages.properties 를 사용하기 위해서는 native2ascii utility 를 사용하여 ascii코드로 변경하여야 함(ant의 사용)
Spring 기동을 위한 web.xml 설정
<web-app>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext*.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
...
</web-app>
- XmlWebApplicationContext : 웹 애플리케이션에서 일반적으로 사용됨
- FileSystemXmlApplicationContext : 일반 애플리케이션 및 테스트 클래스 작성 시 많이 사용
- ClassPathXmlApplicationContext : 일반 애플리케이션 및 테스트 클래스 작성 시 많이 사용
String[] path = {"/WEB-INF/applicationContext.xml"};
ApplicationContext ctx = new new ClassPathXmlApplicationContext(paths);
UserDAO userDAO = (UserDAO)ctx.getBean("userDAO");
String[] path = {"project/web/WEB-INF/applicationContext.xml"};
ApplicationContext ctx = new new FileSystemXmlApplicationContext(paths);
UserDAO userDAO = (UserDAO)ctx.getBean("userDAO");
Spring AOP(Aspect Oriented Programming)
AOP(Aspect Oriented Programming) 개념
- OOP의 종단적인 것이 아니라 횡단적인 개념의 적용
- business 인스턴스의 로직에서 핵심로직을 제외한 Logging, Exception 처리를 분리
- 분리된 Core, Logging, Exception 을 설정파일로 통합
AOP에서 사용되는 용어
- JoinPoint : 클래스의 인스턴스 생성 시점, 메소드를 호출하는 시점, Exception 이 발생하는 시점과 같이 애플리케이션을 실행할 때 특정 작업이 실행되는 시점을 의미
- Advice : Logging 과 SendEmail 과 같이 core logic 이외 추가적인 기능을 제공하는 것
- Pointcut : 분리된 기능들을 결합시키기 위한 규칙(패턴)
- Aspect : Advice + Pointcut 합쳐서 Aspect 라 함. 즉, 일정한 패턴을 가지는 클래스에 Advice를 적용하도록 지원할수 있는 것을 Aspect 라 함
- Weaving : 분리된 UserServiceCore 와 Loggin, SendEmail 을 결합시키는 것을 의미
- Target : core logic 을 구현한 UserServiceCore 클래스를 의미. 즉, 핵심 비지니스 로직을 구현하는 클래스를 의미함
Target클래스와 Advice 클래스와의 weaving(결합)
- Spring에서는 Target 클래스와 Advice 클래스와의 결합(weaving)을 지원하기 위하여 ProxyFactory API 지원
- ProxyFactory는 디플트로 Pointcut 을 지원하기 때문에 target클래스의 모든 메소드가 실행될 때 Advice를 적용하도록 지원
ProxyFactory pf = new ProxyFactory();
pf.addAdvice(new SUserLoggingAdvice());
pf.addAdvice(new EmailNotificationThrowsAdvice());
pf.setTarget(targetObject);
UserService userService = (UserService)pf.getProxy();
...
다섯가지 Advice 개념 :
- MethodBeforeAdvice : target 클래스의 메소드가 실행되기 전에 기능 추가를 위해 before() 메소드를 구현, 단 target 클래스의 메소드에 대한 직접적인 제어는 할수 없음
- AfterReturningAdvice : afterReturning() 메소드를 구현하여 target 클래스의 메소드 실행이 완료된 다음 후처리 작업을 수행. 즉, target 클래스의 메소드 실행 후 반환되는 결과 값에 대한 접근이 가능함
- AroundAdvice : method interceptor, target 클래스의 특정 메소드 접근, 전, 후처리까지 모두 가능, org.aopalliance.intercept.MethodInterceptor 의 invoke() 메소드를 구현
- ThrowsAdvice : Target 클래스의 메소드 내 Exception 이 발생하였을 때 적용되는 Advice, catch 할 필요가 있는 Exception 에 따라 여러 개의 afterThrowing() 메소드를 가질수 있음
- IntroductionAdvice : target 클래스에 완전히 새로운 기능을 추가하는 것이 가능하도록 지원. spring reference 참조
Pointcut 과 Advisor
- Pointcut은 target 클래스와 Advice 클래스가 결합(weaving)될 때 둘 사이의 결합 규칙을 정의. 예를 들어 UserServiceImpl 에 UserLoggingAdvice 가 적용될 때 addUser() 메소드만 적용하고 나머지 메소드는 제외해야 하는 요구사항이 발생할 경우 이를 정의하는 것이 Pointcut 임
public class UserLogginPointcut
extends StaticMethodMatcherPointcut {
public boolean matches(Method method, Class cls) {
if("addUser".equals(method.getName()) {
return true;
}
return false;
}
public ClassFilter getClassFilter() {
return new ClassFilter() {
public boolean matches(Class cls) {
return (cls == UserServiceImpl.class || cls == MySQLUserDAO.class);
}
}
}
}
- Advisor는 AOP의 Aspect 와 같은 개념을 가지는 것으로 Pointcut 과 Advice 를 결합된 것을 하나의 Advisor 라고 함
ProxyFactory pf = new ProxyFactory();
Pointcut logginPC = new UserLogginPointcut();
Advisor loggingAdvisor = new DefaultPointcutAdvisor(loggingPC
, new UserLoggingAdvice());
pf.addAdvisor(loggingAdvisor);
Pointcut emailPC = new EmailNotificationPointcut();
Advisor emailAdvisor = new DefaultPointcutAdvisor(emailPC
, new EmailNotificationThrowsAdvice());
pf.addAdvisor(emailAdvisor);
pf.setTarget(target);
UserService userService = (UserService)pf.getProxy();
Spring에서 제공되는 Pointcut
- target 클래스의 정적인 정보(클래스명, 메소드명)을 기반으로 Pointcut를 만들고자 할 때 사용.
- matches(Method method, Class targetClass) 메소드 구현
- target 클래스의 정적인 정보를 포함하여 동적인 정보(메소드 호출 시 전달되는 인자값)를 기반으로 Pointcut을 만들고자 할 때 사용.
- 정규표현식을 이용하는 것이 가능하도록 지원.
- 첫번째 : Perl5 형식의 정규표현식, 두번째 : JDK에서 지원하는 정규표현식을 지원.
- 이 Pointcut 에 전달되는 메소드 이름에 해당하는 메소드에 대해서만 Advice를 적용되도록 지원. 메소드 이름을 여러개 전달하는 것이 가능.
- union(), intersection() 을 지원함으로써 두 개 이상의 Pointcut 을 결합하기 위해 사용.
- target 클래스 메소드 하위에서 특정 패턴에 일치하는 메소드에 대해서만 Advice를 적용하도록 지원.
Spring 에서 POJO 빈 생성하는 방법
자바의 new 예약어를 이용
- new 로 Object를 생성후 BeanFactory에 캐쉬하여 getBean() 메소드로 가져다 사용
<bean id="userDAO"
class="net.javajigi.user.dao.MySQLUserDAO" />
Factory 메소드를 이용
- 다른 벤더에 의하여 지원되는 라이브러리나 이미 개발되어 있는 애플리케이션의 경우 인스턴스 생성을 위해 지원하는 메소드를 이용할 수 있도록 다음과 같이 지원됨
<bean id="aspect"
class="net.javajigi.advice.LoggingAspect" factory-method="aspectOf" />
- 위와 같이 빈 정의 시 factory-method 속성을 이용하여 인스턴스를 생성하기 위한 메소드명을 지정하는 것이 가능
- FactoryBean 인터페이스를 구현하여 POJO빈을 생성하는 방법으로 다른 프레임워크나 라이브러리를 통합하기 위한 용도로 사용됨
package org.springframework.beans.factory;
public interface FactoryBean {
Object getObject()
throws Exception;
Class getObjectType();
boolean isSingleton();
}
public class AOPFactoryBean
implements FactoryBean, BeanFactoryAware, InitializingBean {
private boolean singleton = true;
private Object instance;
private String[] interceptorNames;
private Object target;
private BeanFactory beanFactory;
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public void setInterceptorNames(String[] interceptorNames) {
this.interceptorNames = interceptorNames;
}
pulic void setTarget(Object target) {
this.target = target;
}
public void afterPropertiesSet() throws Exception {
... Setter Injection 된 인스턴스 체크...
}
public Advisor getAdvisor(String interceptorName) {
Object advObject = beanFactory.getBean(interceptorName);
if(advObject instanceof Advisor) {
return (Advisor)advObject;
} else if(advObject instanceof Advice) {
return new DefaultPointcutAdvisor((Advice)advObject);
} else {
return null;
}
}
public Object getObject() throws Exception { ... }
public Class getObjectType() { ... }
public boolean isSingleton() { ... }
}
- 빈 설정파일에 AOPFactoryBean 클래스 설정
<beans>
...
<bean id="userDAOTarget">
...
</bean>
<bean id="userServiceTarget">
...
</bean>
<bean id="userLoggingadvisor">
...
</bean>
<bean id="userLoggingAdvice">
...
</bean>
<bean id="emailNotificationAdvisor">
...
</bean>
<bean id="meailNotificationThrowsAdvice">
...
</bean>
<bean id="userDAO"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref local="userDAOTarget" />
</property>
<property name="interceptorNames">
<list>
<value>userLoggingadvisor</value>
</list>
</property>
</bean>
<bean id="userService"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref local="userServiceTarget" />
</property>
<property name="interceptorNames">
<list>
<value>userLoggingadvisor</value>
<value>emailNotificationAdvisor</value>
</list>
</property>
</bean>
...
</beans>
Spring Automatic Proxy 사용
- 모든 Target 클래스에 ProxyFactoryBean 의 적용 부분을 추가한다면 귀찮은 작업임(Logging 의 경우)
- Spring 에서 제공되는 Automatic Proxy 클래스를 사용.
- ApplicationContext에서 관리되고 있는 모든 빈에 관현된 Advisor를 적용하도록 하는 것이 가능함.
- 아래 applicationContext.xml 에 보면 위 설정에서 ProxyFactoryBean 을 설정하는 부분이 없어짐.
- 주의점 : ApplicationContext 의 모든 빈에 적용되는 것을 원칙으로 하기 때문에 Pointcut을 적절하게 사용하지 않을 경우 Advice 가 원하는 대로 적용되지 않을 수 있음.
<beans>
...
<bean id="userDAO">
...
</bean>
<bean id="userService">
...
</bean>
<!-- Default Advisor를 기준으로 해당 패턴에 일치하는 모든 빈에 대해서 Advice 적용 -->
<bean id="proxyCreator"
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
<bean id="userLoggingadvisor">
...
</bean>
<bean id="userLoggingAdvice">
...
</bean>
<bean id="emailNotificationAdvisor">
...
</bean>
<bean id="meailNotificationThrowsAdvice">
...
</bean>
...
</beans>
- Bean 설정 파일의 빈 이름을 기준으로 aspect를 적용할지의 여부를 결정
<beans>
...
<bean id="userDAO">
...
</bean>
<bean id="userService">
...
</bean>
<!-- BeanNameAutoProxyCreator 를 설정 -->
<bean id="proxyCreator"
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>*DAO</value>
<value>*Service</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>userLoggingadvisor</value>
<value>emailNotificationAdvisor</value>
</list>
</property>
</bean>
<bean id="userLoggingadvisor">
...
</bean>
<bean id="userLoggingAdvice">
...
</bean>
<bean id="emailNotificationAdvisor">
...
</bean>
<bean id="meailNotificationThrowsAdvice">
...
</bean>
...
</beans>
- Spring 은 automatic proxy를 지원하기 위하여 소스 레벨에 메타 데이터를 추가하여 AOP를 지원하는 것이 가능함
- spring reference 참조
- Spring 과 AspectJ 와의 통합 : p 194 ~ 199
Spring JDBC
- JdbcTemplate 클래스의 주요 메소드 : Template Method Pattern 을 기반으로 함.
# insert, update, delete 관련 메소드
int update(String sql)
int update(String sql, Object[] args)
int update(String sql, Object[] args, int[] argTypes)
# select 관련 메소드
int queryForInt(String sql)
int queryForInt(String sql, Object[] args)
long queryForLong(String sql)
long queryForLong(String sql, Object[] args)
Object queryForObject(String sql, Class requiredType)
Object queryForObject(String sql, Object[] args, RowMapper rowMapper)
List queryForList(String sql)
LIst queryForList(String sql, Object[] args)
# PreparedStatementSetter 사용
PreparedStatementSetter psSetter = new PreparedStatementSetter() {
public void seValues(PreparedStatement ps)
throws SQLException{
.. 구현 ..
}
}
ResultReader resultReader = new ResultReader() {
List list = new ArrayList();
public void processRow(Result rs)
throws SQLException {
.. 구현 ..
}
public List getResults() {
return list;
}
}
List list = getJdbcTemplate().query(sql, psSetter, resultReader);
return list;
- SQLObjects 를 이용하여 구현 : springframework.jdbc.object 패키지의 상위 클래스를 상속하여 구현
public class SelectByNo extends MappingSqlQuery {
public SelectByNo(DataSource ds, String sql) {
super(ds, sql);
declareParameter(new SqlParameter(Types.INTEGER));
}
public Object mapRow(ResultSet rs, int rownum)
throws SQLException {
.. 구현 ..
}
}
public Insert extends SqlUpdate {
public Insert(DataSource ds, String sql) {
super(ds, sql);
declareParameter(new SqlParameter(Types.VARCHAR));
declareParameter(new SqlParameter(Types.VARCHAR));
.. declareParameter() 정의 ..
}
}
//DAO 클래스 내에서
public class SpringDAO
extends SpringDAOSupport
implements ISpringDAO {
private SelectByNo selectByNo;
private Insert insert;
public void initDAO() throws Exception {
super.initDAO();
this.selectByNo = new SelectByNo(getDataSource(), selectByNoSql);
this.insert = new Insert(getDataSource(), insertSql);
}
public int insert(int no, SpringVO vo)
throws Exception {
insert.update(new Object[] { vo.getName(), vo.getTitle(), .... });
int[] i = insert.flush();
return i[0];
}
public List selectByNo(int vono)
throws Exception {
return selectByNo.execute(vono);
}
...
}
- Stored Procedure 구현 방법은 reference 참조
- abstract class SpringDAOSupport 클래스 구현 후 하위 클래스에서 상속할 때 beans.xml 에 설정방법.
- abstract class 에서 Dependency Insection 해야 되므로 함께 설정함, 단, abstract="true" 로 설정하고 상속하는 클래스의 설정에서 parent="SpringDAOSupport" 로 지정하여야 함
<bean id="myJdbcDAOSupport" abstract="true" class="~~~~" >
<property name="dataSource" >
<ref local="dataSource" />
</property>
</bean>
<bean id="userDAO" parent="myJdbcDAOSupport" class="~~~" >
...
</bean>
<bean id="boardDAO" parent="myJdbcDAOSupport" class="~~~" >
...
</bean>
- 프로젝트 규모에 따라 수평적 분리, 수직적 분리로 나눈다.
- 수평적 분리 :
User 컴퍼넌트 : applicationContext-user.xml ( UI, Business, Persistence 설정)
Board 컴퍼넌트 : applicatoinContext-board.xml ( UI, Business, Persistence 설정)
- 수직적 분리 :
UI, Business, Persistence 계층별로 설정
- 위 두가지 방법다 장단점 내포
- 그러므로 빈설정파일 관리 전략에서 공통 모듈의 UI, Business, Persistence 계층의 abstract class 의 설정, 하위는 업무별(user, board)로 빈 설정파일을 관리함(수평적 분리 적용)
Spring Transaction
- Transaction 처리의 계층? business Layer? Persistence Layer?
- business Layer에서 처리하도록 함. 모듈의 독립성 및 재사용성. DAO 의 코드량 줄임.
- Atomicity(원자성) : 트랜젝션 내 있는 모든 작업은 완료되거나 완료되지 않아야 함(commit or rollback)
- Consistencey(일괄성) : 트랜젝션 중에 오류없이 유효한 데이터만 db에 저장되어야 함
- Isolation(격리성) : 트랜젝션 중에 변경된 내용이 트랜젝션 완료 전까지 다른 트랜젝션에 영향끼치면 안됨
- Durability(지속성) : 트랜젝션이 완료된 경우 시스템 고장, 네트워크 에러 등에 데이터가 유실되지 않고 정상적으로 기록되어야 함
- TransactionDefinition.ISOLATION_DEFAULT : default isolation level.
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED : isolation level 중 가장 낮음. commit 되지 않은 transaction에서 변경된 데이터 확인가능. transaction 기능을 거의 수행하지 않음.
- TransactionDefinition.ISOLATION_READ_COMMITTED : 대부분 db에서 디폴트로 지원하는 isolation level, commit 되지 않은 transaction 에서 변경된 데이터를 볼 수 없음. 다른 transaction에 의해 입력 또는 수정된 데이터는 조회할 수 있음.
- TransactionDefinition.ISOLATION_REPEATABLE_READ : COMMITTED 보다 상위 isolation level
- TransactionDefinition.ISOLATION_SERIALIZABLE : 가장 많은 비용이지만 가장 신뢰할만한 isolation level, 하나의 transaction 완료 후 다른 transaction 실행
- TransactionDefinition.PROPAGATION_REQUIRED : 이미 활성화된 트랜젝션이 존재한다면 그 트랜젝션을 지원, 없다면 새로운 트랜젝션을 시작.
- TransactionDefinition.PROPAGATION_SUPPORTS : 이미 활성화된 트랜젝션이 존재한다면 그 트랜젝션을 지원, 없다면 비-트랜젝션 형태로 수행.
- TransactionDefinition.PROPAGATION_MANDATORY : 이미 활성화된 트랜젝션이 존재한다면 그 트랜젝션을 지원, 없다면 예외를 던짐.
- TransactionDefinition.PROPAGATION_REQUIRES_NEW : 이미 활성화된 트랜젝션이 존재한다면 일시 정지, 언제나 새로운 트랜젝션을 시작함
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED : 이미 활성화된 트랜젝션을 지원하지 않음, 언제나 비-트랜젝션으로 수행, 존재하는 트랜젝션은 일시 정지.
- TransactionDefinition.PROPAGATION_NEVER : 활성화된 트랜젝션이 존재하더라도 비-트랜젝션적으로 수행, 활성화된 트랜젝션이 존재한다면 예외를 던짐.
- TransactionDefinition.PROPAGATION_NESTED : 활성화된 트랜젝션이 존재한다면 내포된 트랜젝션으로 수행, 작업 수행은 PROPAGATION_REQUIRED 으로 세팅된 것처럼 수행.
package org.springframework.transaction;
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition)
throws TransactionException;
void commit(TransactionStatus status)
throws TransactionException;
void rollback(TransactionStatus status)
throws TransactionException;
}
PlatformTransactionManager
|
AbstractPlatformTransactionManager
| | | |
HibernateTransactionManager | | |
DataSourceTransactionManager | |
JmsTransactionManager |
JtaTransactionManager
- 빈설정파일의 선언적 트랜젝션 처리 : p 288 ~ p 294
=== applicationContext-jdbc.xml ===
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="txProxyTemplate" abstract="true"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" >
<property name="transactionManager" >
<ref bean="transactionManager" />
</property>
<property name="preInterceptors" >
<list>
<ref bean="loggingAdvice" />
<ref bean="emailNotificationThrowsAdvice" />
</list>
</property>
<property name="transactionAttributes" >
<props>
<prop key="add*">PROPAGATION_REQUIRED</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="remove*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED, readOnly</prop>
</props>
</property>
</bean>
=== applicationContext-board.xml ===
<bean id="boardServiceTarget"
class="net.javajigi.board.service.BoardServiceImpl" >
<property name="boardDAO" >
<ref local="boardDAO" />
</property>
<property name="boardFileDAO" >
<ref local="boardFileDAO" />
</property>
</bean>
<bean id="boardService" parent="txProxyTemplate" >
<property name="target" >
<ref local="boardServiceTarget" />
</property>
<property name="transactionAttributes" >
<props>
<prop key="add*">PROPAGATION_REQUIRED</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="remove*">PROPAGATION_REQUIRED</prop>
<prop key="findBoardWithView">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED, readOnly</prop>
</props>
</property>
</bean>
- 빈설정파일에 선언적으로 트랜젝션을 설정할 때 트랜젝션 속성을 정의하는 방법 :
PROPAGATION, ISOLATION, readOnly, -Exceptions, +Exceptions
전달행위(필수) 격리레벨(선택) readOnly유무(선택) Rollback규칙(선택)
- 전달행위 : 위 전달행위 중 하나 선택
- 격리레벨 : 위 격리레벨 중 하나 선택
- readOnly : select 쿼리 수행 시 설정
- Rollback 규칙 : Spring 에서는 디폴트 설정이 RuntimeException 이 발생하는 경우 Rollback, Checked Exception 이 발생하는 경우 Commit 되도록 하고 있음 (-) 로 시작되는 Exception 에 대해서는 무조건 Rollback, (+)로 시작되는 Exception 에 대해서는 무조건 Commit 되도록 규칙을 변경할 수 있음.
- ex) PROPAGATION_REQUIRED, ISOLATION_SERIALIZABLE, -net.javajigi.user.PasswordMismatchException
Spring MVC
Spring 프로젝트 개발 전략
공통 표준코딩 구현 방안
- Commons Lang (jakarta apache) 의 Builder를 사용하여 VO 의 BaseObject를 구현
toString(), equals(), hashCode() 를 override. 단, toString()의 경우 출력할 필드 filtering 기능필요
- Spring 사용 시 Exception, Logging, 국제화 메시지 처리 방법에 대한 논의(개발 초기에 정책 수립 필요)
- Checked Exception 보다 UnChecked Exception 을 사용
- Checked Exception 은 명시적으로 호출자의 예외발생에 따른 뭔가를 처리해 주어야 할때만 사용
AOP를 이용한 효율적인 개발 전략
- 프로젝트 초반에 구현한느 애플리케이션에 대한 명명규칙을 확립하고 이에 따라 프로젝트를 진행해야 함. 초반에 명명규칙을 확립하고 개발자들이 명명규칙을 따르도록 교육하는 단계가 필요.
- p 201 AOP 의 사용이 프로젝트에 미치는 영향 그래프 참조
테스트 전략
- JUnit 으로 테스트 클래스 작성법 숙지 (p 137 ~ p 146)
- persistence Layer의 테스트 : JUnit 으로 단위 테스트 클래스 작성 시 메소드 당 테스트 메소드를 작성하는 것이 아니라 하나의 CRUD 테스트를 하나의 테스트 메소드에 구현하여 작성하는 것이 적절함( testUserDAO() 작성 시 user 생성, 조회, 수정, 삭제)
- business Layer 의 테스트 : 데이터베이스와의 연결이 없는 상태에서 비지니스 수행로직의 모든 경우를 테스트를 할수 있도록 구현함
- UserDAO 인터페이스를 구현한 MockUserDAO 테스트 클래스를 생성하여 테스트 수행
- EasyMock framework 숙지(p 144, 145)
Comments (0)
You don't have permission to comment on this page.