'JAVA/SPRING'에 해당되는 글 23건
- 2013.09.25 :: spring @Scheduled 중복 수행
- 2013.05.20 :: Spring 3.1 TestCase (MockServletContext 사용)
- 2013.05.15 :: Error querying database. Cause: java.lang.NumberFormatException
- 2011.06.29 :: AOP(Aspect Oriented Programming) in Java
- 2009.07.04 :: sts
- 2009.05.07 :: Spring XML설정파일을 위한 12가지 가장 좋은 선택
- 2009.05.06 :: 스프링 샘플
- 2009.05.06 :: 스프링 > 스트럿츠2
- 2009.05.06 :: [스프링]Spring Mapping Patterns
- 2009.05.06 :: 스프링 handlerMapping
- 2009.05.06 :: 스프링2.5 - 커스텀 태그
- 2009.05.06 :: 스프링 Controller
- 2009.05.06 :: 스프링MVC
- 2009.05.06 :: 스프링 DI (2.5)
- 2009.05.06 :: 스프링 AOP (2.5)
- 2009.05.06 :: [Spring] XML 네임스페이스
- 2009.05.06 :: 스프링MVC 개발단계
- 2009.05.06 :: Spring AOP - 4 (POJO 기반 Aspect)
- 2009.05.06 :: Spring AOP - 3 (@AspectJ 애스펙트 이용한 오토프록싱)
- 2009.05.06 :: Spring AOP - 2 (Classic AOP)
- 2009.05.06 :: 스프링 AOP - 1
- 2009.05.06 :: 스프링 DI - 2
- 2009.05.06 :: 스프링 DI - 1
cafe24 jsp 호스팅 받고 있다.
spring @Scheduled 중복 수행...
많이 찾아봤다...
톰캣 스타트 하면....
[ INFO] [2013-09-25 00:06:49,249][main] AbstractApplicationContext.java.prepareRefresh()-[495 Line] Refreshing WebApplicationContext for namespace 'appServlet-servlet': startup date [Wed Sep 25 00:06:49 KST 2013]; parent: Root WebApplicationContext
이렇게 두번 올라간다.(안올라가면 좋은건지 나쁜건지...모르겟다.
문제는 @Scheduled 이 두번 실행된다.
아주 약간의 시간차를 두고....
인터넷 찾아봐도 없다.
그래서....
우회?적인 방법으로 처리함.
DB 처리 방식.
CREATE TABLE `TBCRON01` (
`CLASSIFICATION` varchar(50) NOT NULL COMMENT '크론작업구분',
`JOBDATE` varchar(20) NOT NULL COMMENT '작업등록시간',
PRIMARY KEY (`CLASSIFICATION`,`JOBDATE`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
@Resource(name="sqlSession")
private SqlSession session;
HashMap<String, String> mp = new HashMap<String, String>();
mp.put("classification", "DatabaseBackupExecutor");
mp.put("jobdate", CommonUtil.getDateFormat("yyyy_MM_dd_HH_mm"));
session.update("SqlCronMapper.cronInsert", mp);
꼼수긴 해도...어쩔 도리가 없다....
svn 다운로드 주소 : https://faz.googlecode.com/svn/trunk/springtestcase3.1
좀 틀린부분도 있습니다. 너그러이 용서를....
package kr.faz.testcase;
import javax.inject.Inject;
import kr.faz.util.StringUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:kr/faz/test-servlet-context.xml" )
@Transactional
public class MockControllerTest {
@Inject
private RequestMappingHandlerAdapter handlerAdapter;
@Inject
private RequestMappingHandlerMapping handlerMapping;
MockHttpServletRequest request ;
MockHttpServletResponse response;
@Before
public void setUp() {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
}
@Test //주문서로 이관
public void urlTest() throws Exception {
setRequest("/test.do?companycode=2&infolidx=2&filelidx=2&orderdate=20130413", "POST");
}
public void setRequest(String uri, String method) throws Exception {
int questionIndex = uri.indexOf("?");
String url = null;
String param = null;
if ( questionIndex != -1 ){
url = uri.substring(0, questionIndex);
param = uri.substring(questionIndex+1);
} else {
url = uri;
}
request.setRequestURI(url);
if ( StringUtils.isNotEmpty(param)) {
String params[] = param.split("&");
for ( String value : params) {
//파라미터 값이 있으면 셋팅
String[] v = value.split("=");
if ( v.length == 2) {
request.addParameter(v[0], v[1]);
}
}
}
System.out.println("URI : "+ request.getRequestURI());
System.out.println("URL : "+ request.getRequestURL());
System.out.println("QueryString : "+ request.getQueryString());
request.setMethod(method);
Object handler = handlerMapping.getHandler(request).getHandler();
handlerAdapter.handle(request, response, handler);
System.out.println("response data : "+ response.getContentAsString());
request.close();
}
}
Mybatis 에서 나는 오류
<if test="section != null && section != '' && section != 'X' " >
->
<if test='section != null && section != "" && section != "X" ' >
변경하면 된다.
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: java.lang.NumberFormatException: For input string: "X"
### Cause: java.lang.NumberFormatException: For input string: "X"
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:73)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:364)
at $Proxy12.selectList(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:194)
at kr.faz.goodsinout.dao.impl.GoodsinoutDaoImpl.list(GoodsinoutDaoImpl.java:26)
at kr.faz.goodsinout.service.impl.GoodsinoutServiceImpl.list(GoodsinoutServiceImpl.java:26)
at kr.faz.goodsinout.controller.GoodsinoutController.adminGoodsinoutList(GoodsinoutController.java:70)
at kr.faz.goodsinout.controller.GoodsinoutController$$FastClassByCGLIB$$b9c3d7bc.invoke(<generated>)
at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.invokeJoinpoint(Cglib2AopProxy.java:689)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:80)
at kr.faz.aop.SessionCheck.adminSessionCheck(SessionCheck.java:58)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:621)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:610)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:65)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:90)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:622)
at kr.faz.goodsinout.controller.GoodsinoutController$$EnhancerByCGLIB$$a5f0006b.adminGoodsinoutList(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:213)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:126)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:96)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:617)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:578)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:923)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:852)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:789)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:710)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:263)
at org.apache.coyote.http11.Http11AprProcessor.process(Http11AprProcessor.java:852)
at org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:584)
at org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:1508)
at java.lang.Thread.run(Thread.java:619)
Caused by: org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: java.lang.NumberFormatException: For input string: "X"
### Cause: java.lang.NumberFormatException: For input string: "X"
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:23)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:107)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:98)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:354)
... 55 more
Caused by: java.lang.NumberFormatException: For input string: "X"
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1224)
at java.lang.Double.parseDouble(Double.java:510)
at org.apache.ibatis.ognl.OgnlOps.doubleValue(OgnlOps.java:259)
at org.apache.ibatis.ognl.OgnlOps.compareWithConversion(OgnlOps.java:143)
at org.apache.ibatis.ognl.OgnlOps.isEqual(OgnlOps.java:186)
at org.apache.ibatis.ognl.OgnlOps.equal(OgnlOps.java:578)
at org.apache.ibatis.ognl.ASTNotEq.getValueBody(ASTNotEq.java:51)
at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170)
at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:210)
at org.apache.ibatis.ognl.ASTAnd.getValueBody(ASTAnd.java:56)
at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170)
at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:210)
at org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:333)
at org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:413)
at org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:395)
at org.apache.ibatis.scripting.xmltags.OgnlCache.getValue(OgnlCache.java:45)
at org.apache.ibatis.scripting.xmltags.ExpressionEvaluator.evaluateBoolean(ExpressionEvaluator.java:29)
at org.apache.ibatis.scripting.xmltags.IfSqlNode.apply(IfSqlNode.java:30)
at org.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:29)
at org.apache.ibatis.scripting.xmltags.IfSqlNode.apply(IfSqlNode.java:31)
at org.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:29)
at org.apache.ibatis.scripting.xmltags.TrimSqlNode.apply(TrimSqlNode.java:47)
at org.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:29)
at org.apache.ibatis.scripting.xmltags.DynamicSqlSource.getBoundSql(DynamicSqlSource.java:37)
at org.apache.ibatis.mapping.MappedStatement.getBoundSql(MappedStatement.java:265)
at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:79)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:104)
... 61 more
ERROR: org.springframework.web.servlet.tags.form.HiddenInputTag - Neither BindingResult nor plain target object for bean name 'm' available as request attribute
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'm' available as request attribute
at org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:141)
at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getBindStatus(AbstractDataBoundFormElementTag.java:178)
at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getPropertyPath(AbstractDataBoundFormElementTag.java:198)
at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getName(AbstractDataBoundFormElementTag.java:164)
at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.autogenerateId(AbstractDataBoundFormElementTag.java:151)
at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.resolveId(AbstractDataBoundFormElementTag.java:142)
at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.writeDefaultAttributes(AbstractDataBoundFormElementTag.java:126)
at org.springframework.web.servlet.tags.form.AbstractHtmlElementTag.writeDefaultAttributes(AbstractHtmlElementTag.java:421)
at org.springframework.web.servlet.tags.form.HiddenInputTag.writeTagContent(HiddenInputTag.java:77)
at org.springframework.web.servlet.tags.form.AbstractFormTag.doStartTagInternal(AbstractFormTag.java:102)
at org.springframework.web.servlet.tags.RequestContextAwareTag.doStartTag(RequestContextAwareTag.java:79)
at org.apache.jsp.WEB_002dINF.views.admin.goodsinout.goodsinoutList_jsp._jspx_meth_form_005fhidden_005f0(goodsinoutList_jsp.java:313)
at org.apache.jsp.WEB_002dINF.views.admin.goodsinout.goodsinoutList_jsp._jspService(goodsinoutList_jsp.java:212)
at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:393)
at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:320)
at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:266)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:654)
at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:445)
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:379)
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:292)
at org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel(InternalResourceView.java:238)
at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:262)
at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1180)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:950)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:852)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:789)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:710)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:263)
at org.apache.coyote.http11.Http11AprProcessor.process(Http11AprProcessor.java:852)
at org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:584)
at org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:1508)
at java.lang.Thread.run(Thread.java:619)
서브 출처 : http://blog.naver.com/ypark197?Redirect=Log&logNo=90093937410
AOP(Aspect Oriented Programming) in Java - Part I
Enterprise Java 환경에서 최근 가장 주목받는 기술 중 하나는 AOP, 즉 Aspected Oriented Programming 이다. AOP가 주목받는 이유는 Enterprise Java 시스템이 맞닥뜨리고 있는 복잡성때문이다.
(AOP의 정의에 대해서는 Wikipedia를 참조하기 바란다)
AOP는 전통적인 객체 지향의 프로그래밍(Object Oriented Programming. 이하 OOP)이 실패한 곳에서 진가를 발휘한다. OOP 언어들은 견고한 철학과 다양하고 유연한 프로그래밍 기법, 다양한 패턴들의 체계화등을 제공한다. 프로그래머들은 이로 인해 절차식 프로그래밍 언어들을 사용할 때에 비해 훨씬 직관적이고 체계적이고 모듈화된 구현을 할 수 있게 되었다. 만세!!!
하지만,불행히도 OOP 언어가 해결하지 못하는 몇가지 중요한 문제점들이 대두되었다.
가장 간단하고 직관적인 예로 로깅(Logging) 기능을 생각해 보자. OOP 언어의 대표격인 Java에서의 전통적인 Logging 구현 방법은 다음과 같다.
public class OrderManager {
public void orderItem(OrderItem oi) {
Logger log = Logger.getLogger("order");
log.info("orderItem started...");
// do something
try {
doSomething(oi);
} catch(Exception ex) {
log.info("Error : " + ex.getMessage());
} finally {
log.info("orderItem finished...");
}
}
Logger라는 객체를 이용해서 로깅 기능을 잘 모듈화했지만, 실제 이 객체를 사용하다보면 몇가지 심각한 문제를 만나게 된다.
- 로직을 구현하는 핵심 소스에 많은 수의 로깅 관련 소스가 삽입됨으로써 소스의 가독성과 간결함을 심하게 훼손한다.
- 로깅을 남기는 방법이나 시점, 위치 등을 바꾸려면 거의 모든 소스파일을 변경해야 한다.
위와 같이 "어떻게 하면 로깅을 남기는 기능을 핵심 로직과 무관하게 구분해서 구현할 수 있을까"라는 간단하고 명확한 요구에 대해 OOP는 적절한 대답을 하지 못한다.여기가 AOP가 개입되는 부분이다.
AOP는 객체를 프로그래밍 단위로 하지 않고, 특정 사건의 발생 양상(또는 상황/국면)을 프로그래밍의 단위로 한다. 이런 의미에서 AOP를 한글로 번역하자면상황 지향 프로그래밍이 가장 적절한 것으로 생각된다.
위의 로깅 예제에서는 다음과 같은 상황들이 펼쳐진다.
- orderItem 메소드가 시작하는 상황에 로그를 기록하라.
- orderItem 메소드가 끝나는상황에 로그를 기록하라.
- orderItem 메소드내에서 Exception이 발생하는 상황에 로그를 기록하라.
AOP에서는 위의 상황들을 정의할 수 있고, 이런 상황이 발생했을 때 수행할 작업을 정의할 수 있다. 즉, 핵심 로직과 무관하게 핵심 로직을 수행하는 과정에서 발생하는 상황들을 적절하게 다룰 수 있다.
Java AOP의 대표적인 언어인 AspectJ를 이용하면위의 소스를 다음과 같이 변경할 수 있다.
// 놀랍게도 핵심 로직에서는 Logging 관련된 부분이 완전히 제거된다.
public class OrderManager {
public void orderItem(OrderItem oi) {
// do something
try {
doSomething(oi);
} catch(Exception ex) { }
}
// 핵심 로직에서 발생하는 상황에 대한 로직을 구현한다.
public aspect LoggingAspect {
// OrderManger내에서 발생하는 모든 Method Call에 대해
pointcut methodExec() :
call(* *.*(..)) &&within(OrderManager);
//Method Call 시작시수행할 일
before() : methodExec() {
Logger log = Logger.getLogger("order");
log.info(thisJoinPointStaticPart.getSignature() + " started...");
}
//Method Call 종료시 수행할 일
after() returning : methodExec() {
Logger log = Logger.getLogger("order");
log.info(thisJoinPointStaticPart.getSignature() + " finished...");
}
// Exception 발생시
after() throwing(RuntimeException ex) : methodExec() {
Logger log = Logger.getLogger("order");
log.info("Error : " + ex.getMessage());
}
}
위와 같이 AOP를 이용하면 로깅과 같은 Aspect라는 이름으로 비핵심 로직을 핵심 로직에서 완전히 분리할 수 있으며, 이후 로깅과 관련된 변경 사항은 핵심 로직을 담당하는 소스의 변경이 아닌 Aspect의 변경만으로 이루어진다.
지금까지 AOP의 등장 배경에 대해 간략하게 살펴보았다. 다음 글을 통해 AOP의 개념 및 AOP의 대표 구현체인 AspectJ에 대해 좀 더 상세하게 논의해보자.
AOP의 개념 - part2
AOP에서는 위와 같이 핵심 로직 구현에서 부가적으로 처리해야 하는 작업들을 Concern(걱정/관심)이라고 부른다. Concern의 특징은 핵심 로직을 구현하는 여러 객체들에 걸쳐져 있다는 것이다. 가령 로깅 Concern은 핵심 로직을 구현하는 모든 객체와 관련되어 있다. 이런 의미에서는 흔히 Cross-cutting Concern이라는 용어를 쓴다. 우리말로 번역하면 "횡단-절단 관심" 정도가 될 것이다.
그림으로 표현하면 아래와 같지 않을까...?
즉 AOP는 객체가 구현하는 핵심 로직에 존재하는 여러 가지 Concern(관심거리, 혹은 걱정거리)을 구현하기 위한 프로그래밍 방법론으로 이해할 수 있다. 핵심 로직을 수행하는 과정에서 발생하는 로그 기록이라는 걱정 거리, 데이터베이스와의 통신 과정에서 발생하는 트랜잭션 관리라는 걱정 거리, 핵심 로직을 수행하는 과정에서 발생하는 성능을 측정하고자 하는 걱정 거리 등... 여러 객체와 행위를 관통하는(Cross-cutting)하는 걱정거리를 보다 손쉽게 구현하고자 하는 것이 바로 AOP의 핵심인 것이다.
AOP의 용어
AOP에 대해 더 상세하게 논의하기 전에 AOP에서 사용하는 용어에 대해 간략하게 정리해보자. 용어를 정확하게 이해해야만 AOP의 기법을 이해할 수 있다.
(정확하게 말하면 아래 용어는 AOP의 용어가 아니라 AspectJ의 용어이다. 하지만 AspectJ가 AOP의 사실상의 표준이기 때문에 무방하다고 간주한다)
- joinpoint : 프로그램 수행 과정에서의 특정 지점. 생성자의 호출, 메소드의 호출, 오브젝트 필드에 대한 접근 등의 대표적인 joinpoint들이다.
- pointcut : joinpoint와매칭하고자 하는 조건과 그 상황에서의 값들
- advice : pointcut에 의해매칭된 joinpoint에서 실행할 작업
- aspect : pointcut과 advice의 집합체. 즉 특정 상황(pointcut)과 그 상황에서 수행할 작업(advice)의 집합
- weaving : aspect과 핵심 로직을 섞는(weave) 것을 의미
이를 그림으로 표현하면 다음과 같다.
- 컴파일시 Weaving : Java/AspectA 소스파일을 컴파일하는 시점에 Weaving을 수행
- 로딩타임시 Weaving: Java Class 파일을 ClassLoader에 의해 로딩하는 시점에 Weaving을 수행
다음으로 계속...
AspectJ - part3
AspectJ는 Java에서의 AOP 언어의 사실상의 표준이다. Spring AOP, JBoss AOP 등 새로운 AOP 컴포넌트들이 있지만, 모두 AspectJ의 서브셋으로 보아도 무방할 것이다.
AspectJ의 특징과 역사는 Wikipedia에 잘 기술되어 있다. (고맙게도 ...)
현재 AspectJ은 Eclipse Project의 서브 프로젝트로 관리되고 있으며, IBM 영국 연구소의 개발자들이 핵심 멤버로 활동하고 있다. 따라서 Eclipse가 계속 존재하는 한, AspectJ 또한 계속 유지보수가 이루어질 것을 기대할 수 있다. ^^
AspectJ는 1.5 이후에 큰 변화를 겪었는데, 바로 AspectWerkz라는 이름의 AOP 프로젝트를 흡수한 것이다. 이 흡수로 인해 AspectJ는 1) 로드타임시 Weaving(Load Time Weaving. LTW) 지원, 2) Java 5 스타일의 Annotation 지원이라는 새롭고 강력한 특징을 가지게 되었다.
이 중, 특히 LTW 기능에 주목할 필요가 있다. 이전 버전의 AspectJ에서는 반드시 AspectJ가 제공하는 컴파일러를 이용해 사용자가 작성한 Class 파일이나 Jar 파일에 대해 Weaving 작업을 수행해야 했다. 한 프로젝트에서 모든 소스를 다 스스로 작성하는 경우는 모르겠지만, 다른 써드 파티가 제공하는 라이브러리를 사용하는 경우에는 확실히 번거로운 점이 있다.
LTW 기능을 이용하면 사용자의 Class 파일을 로딩하는 시점에(즉 실행시) Aspect를 Class와Weaving할 수 있다. 따라서 별도의번거로운 컴파일 과정을 거치지 않아도된다.
하지만!!! 속도 면에서는 컴파일시Weaving이 더유리할 수 있다는 점만은 기억해두자.특히 프로그램 실행 초기에 클래스가 로딩되는 속도가 눈에띄게느려질 수 있다는 점은 기억해둘 필요가 있다.
AspectJ 다운받고 사용하기
AspectJ는 http://www.eclipse.org/aspectj/index.php에서 다운받을 수 있다. 다음 두가지를 다운받아야 한다.
- AspectJ 1.5.3 - AspectJ 메인 릴리즈
- AJDT - AspectJ Development Tools. Eclipse Project내에서 AspectJ를 사용할 수 있는 플러그인을 제공
AJDT를 사용하면 Eclipse의 풍부한 기능과 함께 비주얼하게 AspectJ를 사용할 수 있으므로 보다 손쉽게 AspectJ에 접근할 수 있다.
AJDT를 설치하고 나면, 아래 그림과 같이 AspectJ를 기본으로 하는 프로젝트와 Aspect를 손쉽게 생성할 수 있다.
<< AspectJ 프로젝트 생성>>
<< Aspect 생성>>
다음 파트에서 AJDT를 이용해 간단한 AOP 샘플을 구현할텐데, 다음과 같은 Concern을 해결하는 것을 목표로 한다.
" 현재 운영 중인 시스템에서 코드상의 오류로 인해 Exception이 계속해서 발생한다. Exception이 발생할 때마다 발생한 Exception을 파악하고, Exceptioon의 발생 시점, 발생 이유, 발생 시의 Stack Trace 등을 기록하고자 한다."
즉, Exception 처리라는 걱정 거리를 좀 더 효과적으로 처리할려고 한다. 언뜻 어려워 보이는 이 걱정거리가 AOP에서는 얼마나 쉽게 해결가능한지 살펴보게 될 것이다.
AspectJ에서 Concern 구현 하기 - part4
아래에 심플한(?) 비지니스 로직을 구현하는 객체가 있다. 우리의 걱정 거리는 비지니스 로직에서 Exception이 발생할 때마다 상세한 발생 상황을 기록하는 것이다.
우리의 비지니스 로직은 다음과 같다.
이 비지니스 로직에 대한 우리의 Concern을 처리해야 하는 상황은 다음과 같다.
- ExceptionGenerator의 로직을 수행하는 과정에서 Exception이 발생하면 이것을 캡쳐해서 기록하고 싶다.
- 이 때 어떤 메소드를 호출하다가 Exception이 발생했는지, Exception의 종류는 무엇인지 등의 정보가 종합적으로 기록하고 싶다.
이 상황을 AOP 없이 처리하려면 제 아무리 자바의 고수라고 하더라도 다음과 같은 방식으로 일일이 소스를 변경해야 한다.
try { doSomething1() } catch(Exception ex) {
logger.log("Error " + ex + " occurred when executing ExceptionGenerator.doSomething1()...");
}
비록 Java Logging API나 Log4j 같은 라이브러리들이 이러한 작업을 처리하는데 상당히 도움이 되지만, 핵심 로직안에 우리의 Concern을 처리하는 로직을 넣어야 한다는 기본적인 사실에는 전혀 변화가 없다.
하지만, AspectJ를 사용하면... ? 핵심 로직에는 Exception Handling에 관련된 소스를 전혀 추가할 필요없이 다음과 같은 형태의 Aspect만을 만들어주면 된다.
Exception Aspect Version 1
매우 심플한 Aspect지만 두 가지의 핵심적인 정보를 담고 있는 완전한 형태의 Aspect이다.
- pointcut : 모든객체의 메소드콜을 횡단으로 매치시키는 call (* *.*(..)) 이라는 pointcut이 callpoint라는 이름으로 정의되어 있다.
- after advice : callpoint pointcut에서 Exceptoin이 발생한 이후(after + throwing) 수행할 advice가 정의되어 있다.
AspectJ에서는 대부분의 Concern이 pointcut과 advice의 조합으로 이루어진다. 즉 어떤 지점(pointcut)에서 어떤 일(advice)를 수행할 지가 바로 AspectJ가 구현하는 Concern에 대한 해결책이 된다.
위의 ExceptionAspect와 ExceptionGenerator를 Weaving해서 수행하면 다음과 같은 결과가 나온다.
결과1
이제 ExceptionAspect를 좀 더 다듬어서 보다 완전한 형태의 정보를 얻을 수 있도록 해보자.
Exception Aspect Version 1
더욱 세련된 모양의 Aspect가 구현되었음을 확인할 수 있다. Version 1에 비해 다음과 같은 특징들이 추가되었다.
- After advice에서 Exception발생시 Exception 객체를 받는다. 이렇게 받은 객체를 이용해서 필요한 정보를 추출한다.
- thisJoinPointStaticPart (또는 thisJoinPoint.getStaticPart())를 이용해 어떤 지점에서 발생한 Exception인지를 알아낸다.
위의 Aspect를 보고 "아... 정말 내가 원하던 방법론이다"라고 감탄을 했다면 이미 일류 프로그래머이거나 일류 프로그래머가 될 잠재력을 가지고 있는 사람일 것이다.
AspectJ 혹은 AOP를 현재 프로젝트에 사용하고 싶은 욕구가 이는가...!!!
AspectJ의 Load Time Weaving - part5
ApsectJ 1.5는 AspectWerkz라는 신흥 AOP 컴포넌트를 흡수하면서 Load Time Weaving 기능을 크게 향상시켰다. Load Time Weaving이란 말 그대로 클래스가 로드되는 시점에 Weaving 작업을 수행하는 것을 의미한다.
전통적으로 AspectJ에서는 ajc(AspectJ Compiler)라는 컴파일러를 사용해서 사용자가 작성한 Class 파일이나 Jar 파일을 컴파일 시간에 Weaving하는 방식을 지원했다. 비록 이 방법이 아직까지도 가장 보편적이고 또 편리한 방법이긴 하지만, 컴파일시 Weaving은 역시 불편한 방법이다. 하지만!!! 성능 면에서는 가장 유리한 방법이라는 것은 다시 한번 염두에 두자
AspectJ에서 Load Time Weaving이 적용되는 방식은 아래 그림과 같다.
aop.xml
aop.xml 파일은 LTW의 룰을 지정하는 역할을 한다. 즉 어떤 Aspect를 어떻게 Weaving 할 것인지 지정할 수 있다.
아래에 aop.xml의 간단한 예제가 있다.
위의 aop.xml 파일은[aop.ltw.SimpleLTWAspect]라는 이름의 Aspect를 사용하며, 이 Aspect를 이용해서 Weaving을 수행할 것을 지정한다.
AspectJ의 LTW 모듈은 [클래스패스(Classpath)/META-INF]에 있는 모든 aop.xml 파일을 불러와서 Weaving 작업을 수행한다.
aop.xml이 제공하는 문법은 매우 다양하고 강력하다. 어떤 Aspect를 어떤 타겟(비지니스 로직)에 대해 어떤 조건(pointcut)으로 사용할지를 자유롭게 지정할 수 있다. 예를 들어 Abstract Aspect를 만든 후 aop.xml에서pointcut을 정의할 수도 있다. aop.xml을 사용하는 상세한 방법은 AspectJ Manual을 참조한다.
LTW의 간단한 예제
아래에 간단한 Aspect가 있다.
이 Aspect의 역할은 Method의 시작과 끝을 잡아서 수행 시간을 측정하는 것이다. 어플리케이션 성능 측정을 위한 가장 기본적인 기능을 구현할 것이라고 볼 수 있다.
이 Aspect를 다음과 같이 ajc를 이용해서 컴파일한다.
c:aspectj1.5binajc -1.5 -cp ../..;c:aspectj1.5libaspectjrt.jar SimpleLTWAspect.aj
컴파일에 성공하면 SimpleLTWApsect.class 파일이 생긴다. 이 Aspect 파일과 위에서 샘플로 사용한 aop.xml 파일을 이용해서 LTW을 수행하는 명령어는 다음과 같다.
(SimpleLTW 객체는 몇 개의 메소드를 반복적으로 호출하는 단순한 객체이다)
java -javaagent:aspectjweaver.jar -cp ../.. aop.ltw.SimpleLTW
아래 결과를 보면 우리가 원하는 대로 각 메소드를 실행하는데 걸린 시간이 계산되어 나오는 것을 알 수 있다.
위에서 본 간단한예제만으로도 AspectJ에서 제공하는 LTW의 유연함과 강력함을 느낄 수 있으리라 믿는다.
PS)
Java 5 (JDK 1.5)부터는 java.lang.instrument 패키지를 이용해서 Byte Code Instrumentation(BCI)을 직접적으로 지원한다. 더 이상 BCI가 어둠의 자식이 아니라는 것을 의미한다.오히려 BCI가 Sun에서도 인정하는 보편적인 방법론임을 의미한다.
자연스럽게, AspectJ 1.5의 LTW도 이 기능을 이용한다. 위의 예에서 "-javaagent:aspectjweaver.jar" JVM 옵션이 java.lang.instrument 패키지를 이용한다는 것을 의미한다. 즉, aspectjweaver.jar 내에 클래스 로드 타임시 실시간으로 클래스를 Weaving하는 기능을 제공하는 Class Transformer가 존재한다.
JDK 1.4에서는 VM 레벨에서 BCI가 지원되지 않는다.JDK 1.4라면 아래와 같은 형식으로 사용가능하다.
java -classpath[aspectjweaver.jar]
-Djava.system.class.loader=org.aspectj.weaver.loadtime.WeavingURLClassLoader
-Daj.class.path=. -Daj.aspect.path=.[YourClass]
또는 JRockit에서는 다음과 같은 JVM옵션을 사용할 수 있다.
-Xmanagement:class=org.aspectj.weaver.loadtime.JRockitAgent
AOP(Aspect Oriented Programming) in Java - Part 6
AOP와 AspectJ를 이용해서 할 수 있는 일은 실로 무궁구진하다.
비지니스 로직을 처리하는 과정에서 발생하는 관심거리나 걱정거리를 좀 더 효율적이고 체계적으로 처리하고자 한다면 AOP가 바로 대답이다.
AOP를 이용해서 구현 가능한 몇 가지 사례를 끝으로 AOP에 관한 블로깅을 끝맺기로 한다.
- 로깅 : 각 조인포인트마다 적절한 로그를 남길 수 있다.
- 프로파일링 : 각 조인포인트마다 수행되는 메소드 정보를 기록해서 프로파일링을 구현할 수 있다. 가령 자바 어플리케이션에서 수행되는 모든 메소드에 대해 수행 시간을 측정하고자 한다면 AOP의 before/after/around advice를 이용해서 손쉽게 구현할 수 있다.
- 트랜잭션 관리 : 트랜잭션 관리에 관련된 모는 기능을 비지니스 로직에서 제거하고 AOP로 구현할 수 있다. EJB는 AOP의 개념을 사용하지 않고 EJB 컨테이너를 이용해서 트랜잭션을 관리하는 기능을 제공하는 것으로 이해할 수 있다. 반면 Spring과 같은 경량 프레임웍들을 EJB 컨테이너와 같은 무거운 방법대신 AOP를 사용해서 사용자가 작성한 비지니스 로직에 트랜잭션 관리에 필요한 Bytecode를 직접 삽입하는 방식을 사용한다.
- 코딩컨벤션 관리:클 래스의 필드명을 항상 m_ 로 시작하게끔 규칙을 부여한다고 하자. 여러 명의 개발자들이 개발을 진행할 경우 이 규칙이 지켜지기는 거의 불가능하다. 하지만 AOP의 필드 조인트포인트를 이용하면 컴파일 시점에 이러한 오류들을 모두 걸러낼 수 있다. AOP를 이용하면 매우 복잡한 코딩 컨벤션 관리를 거의 자동화 할 수 있다.
- 기타 등등... 오직 우리의 상상력에 의해서만 제약을 받는다!!!
국내 개발 프로젝트에서 AOP를 많이 사용하지 않는다고 해서 AOP가 무용하거나 현실과 동떨어진 것이라고 생각한다면 큰 오산이라는 것을 다시 한번 명심하자.
Spring 프레임웍이 내부적으로 트랜잭션 관리를 위해 AOP를 사용하고 있다. 따라서 여러분이 만일 Spring을 사용하고 있다면 이미 AOP를 사용하고 있는 것이다. 그 외에도 대부분의 Application Server가 AOP를 이용해 사용자가 작성한 클래스 파일에 특정 기능을 부여하는 기능을 곳곳에서 사용하고 있다.
앞서 다섯 편의 글을 통해 AOP의 강력함과 편리함을 조금이라도 느낄 수 있었기를 바라며, 국내의 개발 프로젝트에서 AOP를 사용하는 실제적인 사례를 목격하게 되기를 바래 본다.
[퍼옴 : http://openframework.or.kr/blog/?p=40 ]
Spring XML설정파일을 위한 12가지 가장 좋은 선택
원제는 Twelve Best Practices for Spring XML Configuration Files 이다.
원문의 위치는 http://lizjason.com/blog/?p=12
ONJava.com의 Twelve Best Practices For Spring XML Configurations 글을 기반으로 예제및 설명에 대한 일부 수정
Spring은 강력한 자바 애플리케이션 프레임워크이고 자바 애플리케이션의 넓은 범위에서 사용된다. 이것은 단순함과 테스트의용이성을 달성하기 위해 의존성삽입(Dependency Injection)을 사용한다. 의존성과 bean생성은 XML설정파일에대개 명시된다. XML설정은 장황하고 큰 프로젝트에서는 관리하기가 어려울수도 있다. 설정파일의 가독성과 관리의 용이성이 고려되는만큼, 나는 다음의 사항이 매우 유용하리라고 생각한다.
1. autowire를 사용하지 말라.
내 의견에서, autowire는 시장광고용(marketing) 기능이다. 이것은 실제 프로젝트에서 결코 사용되지 말아야 한다. 이것은 몇몇 타이핑의 수고와 설정조각을 줄이지만, 명백함과 설정의 유지보수성을 희생한다.
trollswagen, naimdjon, Johannes Brodwall, 토지님께서 autowire는 오히려 xml이커지면 커질수록 xml의 구조를 쉽게 파악하게 해주는 정말 좋은 기능중에 하나라는 의견을 주셨습니다. autowire에 관련된사항은 프로젝트 도입시 장,단점을 다시 살펴 사용하길 권합니다.
2. 명명 규칙을 사용하라.
이것은 자바코드와 같은 의도이다. 예를 들면 bean id를 위해, 당신은 자바 클래스 필드 명명규칙을 따를수 있다. OrderServiceDAO의 인스턴스를 위한 bean id는 orderServiceDAO가 될것이다.
3. 단축형태(shortcut forms)를 사용하라.
단축형태는 자식요소에서 속성으로 프라퍼티값과 참조를 이동시켜 다소 덜 장황하게 만든다. 이 단축형태는 1.2버전 이후 지원된다.
단축형태는 다음과 같은 기능이다.
1.2이전버전에서는 다음과 같이 셋팅한다.
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>
<property name="url"><value>jdbc:mysql://localhost:3306/mydb</value></property>
<property name="username"><value>root</value></property>
</bean>
1.2이후 단축형태를 사용하면 다음과 같이 셋팅이 가능하다.
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mydb" />
<property name="username" value="someone" />
</bean>
4. 인자를 맞추기 위한 인덱스보다 타입을 선호하라.
인덱스를 사용하는 것은 때때로 다소 덜 장황하게 만든다. 하지만 이것은 에러를 좀더 생성하고 읽기 어렵다.
다음의 소스는 인덱스를 사용하는 예제이다.
<bean id="billingService" class="com.lizjason.spring.BillingService">
<constructor-arg index="0" value="lizjason"/>
<constructor-arg index="1" value="100"/>
</bean>
하지만 다음처럼 타입을 사용하는 것이 추천한다.
<bean id="billingService" class="com.lizjason.spring.BillingService">
<constructor-arg type="java.lang.String" value="lizjason"/>
<constructor-arg type="int" value="100"/>
</bean>
5. 가능하다면 bean정의를 재사용하라.
당신은 중복을 제거하기 위한 기법처럼 상속을 사용할수 있다. 당신이 할필요가 있는 모든것은 상위 bean에 abstract=true를 명시하고 자식 bean에 parent참조를 두는 것이다. 당신이클래스나 factory메소드를 명시하지 않는다면, bean은 함축적으로 abstract상태가 된다.
예를 들면 다음과 같이 셋팅한다.
<bean id="txProxyTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref bean="transactionManager" />
</property>
<property name="transactionAttributes">
<props>
<prop key="save*">PROPAGATION_REQUIRED</prop>
<prop key="remove*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
<bean id="userManager" parent="txProxyTemplate">
<property name="target">
<bean class="org.appfuse.service.impl.UserManagerImpl">
<property name="userDAO" ref="userDAO" />
</bean>
</property>
</bean>
6. import 보다는 ApplicationContext를 통해 bean정의를 조립(assembling)하는 것을 선호하라.
Ant스크립트내 import처럼, 그것들은 모듈화된 bean정의를 조립하는데 유용하다. 어쨌든, 이것은ApplicationContext를 통해 그것들을 조립하기 위해 좀더 유연하다. 당신은 ApplicationContext의생성자를 위해 bean정의의 배열을 전달할수 있다.
다음은 import를 조합하여 xml을 설정하는 소스이다.
<beans>
<import resource="billingServices.xml"/>
<import resource="shippingServices.xml"/>
<bean id="orderService" class="com.lizjason.spring.OrderService"/>
<beans>
import를 사용하여 xml을 조합하는 방식보다는 아래와 같이 ApplicationContext를 사용하는 것이 좀더 유연한 개발을 도와준다.
String[] serviceResources = {"orderServices.xml", "billingServices.xml", "shippingServices.xml"};
ApplicationContext orderServiceContext = new ClassPathXmlApplicationContext(serviceResources);
7. 가능하다면, bean 확인자로 id를 사용하라.
id를 사용하는 것은 가독성을 증가시키지 않는다. 하지만 이것은 bean참조를 확인하기 위해 XML파서에 영향을 끼칠수 있다. 만약 id가 XML IDREF제한을 위해 사용될수 없다면, 당신은 이름(name)을 사용할수 있다.
8. 개발시에는 의존성체크(dependency-check)를 사용하라.
당신은 bean정의의 dependency-check속성을 디폴트인 none이 아닌 다른값으로 셋팅할수 있다. 그래서 컨테이너는 당신을 위해 의존성체크를 할수 있다.
<bean id="orderService" class="com.lizjason.spring.OrderService" dependency-check="objects">
<property name="companyName" value="lizjason"/>
<constructor-arg ref="orderDAO"/>
</bean>
9. 각각의 XML파일을 위해 헤더(header) 주석을 추가하라.
XML파일내 내부 주석대신에 서술적인 id와 name을 사용하는것이 선호된다. 어쨌든, 각각의 파일이 정의된 bena을 요약하는 헤더를 가진다면 이해하기가 쉽다.
<beans>
<description>
이 파일은 거래(billing) 서비스 관련 bean을 정의하고
서비스 bean템플릿을 제공하는 baseService.xml파일을 의존한다.
</description>
...
</beans>
10. 변경을 위해 팀멤버간 의사소통을 하라.
당신이 자바소스코드를 리팩토리할때, 당신은 설정파일을 그 상황에 따라 변경하고 팀멤버에게 알릴 필요가 있다.
11. 생성자 삽입(constructor injection)보다 setter 삽입(setter injection)을 선호하라.
생성자 삽입은 bean들이 비정상적인 상태에서 생성될수 없다는 것을 확인할수 없다. 하지만 setter 삽입은 좀더 유연하고 관리가 가능하다. 특히 클래스가 다중 프라퍼티를 가진다면 더욱 그러하다.
다음은 생성자 삽입을 사용하는 예이다.
<bean id="orderService" class="com.lizjason.spring.OrderService">
<constructor-arg ref="orderDAO"/>
</bean>
다음은 setter삽입을 사용하는 예이다.
<bean id="billingService" class="com.lizjason.spring.BillingService">
<property name="billingDAO" ref="billingDAO">
</bean>
12. 의존성 삽입을 남용하지 말라.
마지막에, Spring ApplicationContext는 당신을 위해자바객체를 생성할수 있다. 하지만 모든 자바객체가 의존성삽입을 통해서 생성될수는 없다. 기억하라. 강력한 IDE인Eclipse와 IntelliJ를 사용하여, 자바코드는 좀더 읽고, XML파일보다 유지및 관리가 쉽다. 즉 의존성삽입을 설정하는XML파일보다는 자바코드가 개발자의 입장에서는 가독성이 좋다.
[출처] Spring XML설정파일을 위한 12가지 가장 좋은 선택|작성자 쭌
스프링 프레임워크의 프로세스는 대충 이해 했습니다.
스프링MVC에서 삽질 3일++ , 스프링JDBC에 도전해서 이틀동안 삽질한 결과 데이터베이스 연동을 할 수 있었네요..ㅋ
스프링.. 좋거나 혹은 나쁘거나 입니다. 제가 본 것은 빙산의 일각에 불과하지만 처음부터 너무 진을 빼놓네요.. 스트럿츠2가 그립습니다.
클래스를 많이 제공하는 것은 기호에 맞게 선택할 수 있는 폭이 넓어 좋지만, 반면 어떤 것을 적용해야 할지 어떤 것이 최선인가 하는데에 시간 투자를 많이 해야 합니다.
서블릿 설정파일에서도 시간을 많이 소비하였고 아직도 헷갈립니다. 톰캣과 궁합이 안좋은가...
JDBC연동 부분은 확실히 편하긴 하지만 커맨드,서비스,컨트롤러,DAO + servlet.xml 설정파일과 어떻게 연동이 되는가하는 부분에서도 하루를 날렸네요.
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dataSource.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>
org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>EUC-KR</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>dispatcherJdbc</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/boardService-servlet.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>dispatcherBank</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/sampleBankingServlet-servlet.xml , /WEB-INF/sampleBanking-services.xml
</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherJdbc</servlet-name>
<url-pattern>/test/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>dispatcherBank</servlet-name>
<url-pattern>/bank/*</url-pattern>
</servlet-mapping>
최범균님 책에 나와있는데로 다시 web.xml을 설정을 해보았다.. 잘 될 줄 알았는데... 역시나 에러... 어플 하나는 꼭 안되더군..
public class TestDAO {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public Object insert(TestCommand test) throws Exception{
Connection conn = null;
PreparedStatement pstmt = null;
try{
conn = DataSourceUtils.getConnection(dataSource);
pstmt = conn.prepareStatement("insert into member(id,name,password) values(?,?,?)");
pstmt.setString(1, test.getId());
pstmt.setString(2, test.getName());
pstmt.setString(3, test.getPassword());
pstmt.executeUpdate();
}catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}finally{
DataSourceUtils.releaseConnection(conn, dataSource);
}
return dataSource.getConnection();
}
}
DAO파일은 dataSource객체만 선언해 준 것 빼고 거의 비슷하게 했다. 스프링의 jdbc템플릿을 쓰지 않았다.
public class JoinController extends SimpleFormController{
public JoinController(){}
//서비스 객체 생성
private TestService service;
public TestService getService() {
return service;
}
public void setService(TestService service) {
this.service = service;
}
public ModelAndView onSubmit(Object command) throws Exception{
TestCommand test = (TestCommand) command;
//서비스를 호출
service.insertMemeber(test);
//직접 DAO접근
//to.insert(test);
return new ModelAndView(getSuccessView(), "success" , service.insertMemeber(test));
}
}
컨트롤러는 간단하다. 서비스는 DAO와 연결해주는 빈이고.. 이것을 컨트롤러에서 실행한다.. 이상하게도 DAO에 직접 접근하면 에러가 나더라..
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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-2.5.xsd">
<bean id = "testDao"
class = "sboard.DAO.TestDAO">
<property name="dataSource">
<ref bean="dataSource" />
</property>
</bean>
<bean id="simpleUrlMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/testjoin.htm">joinController</prop>
</props>
</property>
</bean>
<bean id="testService"
class="sboard.services.TestService"
p:tdao-ref="testDao" />
<bean id="joinController"
class="sboard.controller.JoinController">
<property name="sessionForm">
<value>true</value>
</property>
<property name="commandName">
<value>joinCommand</value>
</property>
<property name="commandClass">
<value>sboard.commands.TestCommand</value>
</property>
<property name="formView">
<value>join</value>
</property>
<property name="successView">
<value>memberlist</value>
</property>
<property name="service">
<ref bean="testService" />
</property>
</bean>
<!--뷰리졸버 세팅 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass">
<value>org.springframework.web.servlet.view.JstlView</value>
</property>
<property name="prefix">
<value>/WEB-INF/test/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
dataSource는 dataSource.xml에 정의 되어 있다. 그것을 가져와서 testDao에 담고,,, 서비스 클래스에 선언된 tdao객체를 초기화하기 위해 testService선언.. 파라미터로 testDao객체를 넘겨준다. 컨트롤러에서 사용할 서비스객체를 초기화하기 위해서 컨트롤러 빈 내에
service프로퍼티에 testService빈을 파라미터로 넘겨준다.(복잡하다...)
삽질한 만큼 끝내 결과를 보게 되서 다행입니다.;;
오늘도 또 봤지요... 스프링 인 액션2 책을 구해서 봤는데 약간 이해가 되다가도 예제가 그지같군요..(예제가 단적이지 않고 통합되어 있어서 소스를 분석해야함)
그래도 오기로.. 신상철박사님의 사이트에서 예제를 다운받아 실행이라도 되게끔 세팅해 보았습니다. 중간에 오류도 많았구여.
SimpleFormController로 컨트롤러를 구현한 예제였습니다.
차분히 컨트롤러 파일부터 한줄한줄 분석해 보았습니다.
아주 조금씩 이해가 되갈라 카네여..
작년에 스트럿츠2로 만들던 웹게임소스가 있어서 봤는데... 헐.. 로직이 쉽고 설정파일의 가독성이 이렇게나 아름다울수가!!!
스트럿츠2에 새삼 감사했지만, 강력함은 스프링에 못 미칩니다. 스트럿츠2는 단조롭죠.. 물론 더 알아봐야 겠지만요./
package springexample.controller;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;
import springexample.commands.AccountDetail;
import springexample.commands.LoginCommand;
import springexample.services.AccountServices;
import springexample.services.AuthenticationService;
/**
* SimpleFormController is a concrete FormController implementation that
* provides configurable form and success views, and an onSubmit chain for
* convenient overriding. Automatically resubmits to the form view in case
* of validation errors, and renders the success view in case of a valid
* submission.
*/
public class LoginBankController extends SimpleFormController {
public LoginBankController() {
}
// The submit behavior can be customized by overriding one of the onSubmit methods. 오버라이딩 필수
protected ModelAndView onSubmit(Object command) throws Exception {
LoginCommand loginCommand = (LoginCommand) command; //오브젝트의 커맨드객체를 형변환시킨다.커맨드에는 폼에서 전달한 아뒤와 패스워드 정보가 담겨있다.
authenticationService.authenticate(loginCommand); //로그인인증.. 아작스처리가 낫지 않을까?
AccountDetail accountdetail = accountServices.getAccountSummary(loginCommand.getUserId());//커맨드를통해정보를담자..머이리 복잡한것인가!!
return new ModelAndView(getSuccessView(), "accountdetail", accountdetail);
}
//어디서 많이 본듯한 장면아닌가..스트럿츠2의 액션인가?? 겟셋..
private AuthenticationService authenticationService;
private AccountServices accountServices;
public AccountServices getAccountServices() {
return accountServices;
}
//sampleBankingServlet-servlet.xml에서 <property name="accountServices">로 설정해 객체를 넘겼다.
//해당객체를 주입하여 인자로 넘겨줌으로써 위에서 선언한 객체변수들을 쓸 수 있는 것이다.
public void setAccountServices(AccountServices accountServices) {
this.accountServices = accountServices;
}
public AuthenticationService getAuthenticationService() {
return authenticationService;
}
public void setAuthenticationService(
AuthenticationService authenticationService) {
this.authenticationService = authenticationService;
}
}
패키지는 controller , services , commands로 나뉜다..
controller : 말그대로 컨트롤러 클래스 위치
commands : User , Account , Login 등 빈즈 위치
services : 비즈니스 로직이 구현된 클래스 위치.
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="simpleUrlMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/login.html">loginBankController</prop>
</props>
</property>
</bean>
<bean id="logonValidator" class="springexample.commands.LogonValidator"/>
<!-- LoginBankController is a SimpleFormController -->
<bean id="loginBankController"
class="springexample.controller.LoginBankController">
<!--AbstractFormController에 sessionForm()가 정의되어있어 사용가능하다 -->
<property name="sessionForm">
<value>true</value>
</property>
<!-- This is Command object. The command object can be
accessed in a view through <spring:bind path="loginCommand"> -->
<!--커맨드명설정 BaseCommandController클래스에 commandName()메소드가 정의되어있다 -->
<property name="commandName">
<value>loginCommand</value>
</property>
<!--커맨드클래스로 LoginCommand클래스 사용. 파라미터타입은 class타입 -->
<property name="commandClass">
<value>springexample.commands.LoginCommand</value>
</property>
<!-- BaseCommandController에 setValidator()메소드가 정의되어있다. -->
<property name="validator">
<ref bean="logonValidator"/>
</property>
<!-- Indicates what view to use when the user asks for a new form
or when validation errors have occurred on form submission. -->
<!--SimpleFormController클래스에 setFormView()메소드 정의되어있다. 인자로넘긴 login은 login.jsp로 연결
결국 onSubmit()에서 에러가 나면 폼입력화면..또는 처음요청은 GET방식이니까 폼입력으로.. -->
<property name="formView">
<value>login</value>
</property>
<!-- Indicates what view to use when successful form submissions
have occurred. Such a success view could e.g. display a submission
summary. More sophisticated actions can be implemented by
overriding one of the onSubmit() methods.-->
<!-- onSubmit()이 성공하면 getSuccessView()를 리턴하는데 여기서 setSuccessView를 세팅해준다.
accountdetail.jsp로 가는거다. -->
<property name="successView">
<value>accountdetail</value>
</property>
<property name="authenticationService">
<ref bean="authenticationService" />
</property>
<property name="accountServices">
<ref bean="accountServices" />
</property>
</bean>
<!--뷰리졸버 세팅 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass">
<value>org.springframework.web.servlet.view.JstlView</value>
</property>
<property name="prefix">
<value>/WEB-INF/jsp/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
오늘도 또 봤지요... 스프링 인 액션2 책을 구해서 봤는데 약간 이해가 되다가도 예제가 그지같군요..(예제가 단적이지 않고 통합되어 있어서 소스를 분석해야함)
그래도 오기로.. 신상철박사님의 사이트에서 예제를 다운받아 실행이라도 되게끔 세팅해 보았습니다. 중간에 오류도 많았구여.
SimpleFormController로 컨트롤러를 구현한 예제였습니다.
차분히 컨트롤러 파일부터 한줄한줄 분석해 보았습니다.
아주 조금씩 이해가 되갈라 카네여..
작년에 스트럿츠2로 만들던 웹게임소스가 있어서 봤는데... 헐.. 로직이 쉽고 설정파일의 가독성이 이렇게나 아름다울수가!!!
스트럿츠2에 새삼 감사했지만, 강력함은 스프링에 못 미칩니다. 스트럿츠2는 단조롭죠.. 물론 더 알아봐야 겠지만요./
package springexample.controller;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;
import springexample.commands.AccountDetail;
import springexample.commands.LoginCommand;
import springexample.services.AccountServices;
import springexample.services.AuthenticationService;
/**
* SimpleFormController is a concrete FormController implementation that
* provides configurable form and success views, and an onSubmit chain for
* convenient overriding. Automatically resubmits to the form view in case
* of validation errors, and renders the success view in case of a valid
* submission.
*/
public class LoginBankController extends SimpleFormController {
public LoginBankController() {
}
// The submit behavior can be customized by overriding one of the onSubmit methods. 오버라이딩 필수
protected ModelAndView onSubmit(Object command) throws Exception {
LoginCommand loginCommand = (LoginCommand) command; //오브젝트의 커맨드객체를 형변환시킨다.커맨드에는 폼에서 전달한 아뒤와 패스워드 정보가 담겨있다.
authenticationService.authenticate(loginCommand); //로그인인증.. 아작스처리가 낫지 않을까?
AccountDetail accountdetail = accountServices.getAccountSummary(loginCommand.getUserId());//커맨드를통해정보를담자..머이리 복잡한것인가!!
return new ModelAndView(getSuccessView(), "accountdetail", accountdetail);
}
//어디서 많이 본듯한 장면아닌가..스트럿츠2의 액션인가?? 겟셋..
private AuthenticationService authenticationService;
private AccountServices accountServices;
public AccountServices getAccountServices() {
return accountServices;
}
//sampleBankingServlet-servlet.xml에서 <property name="accountServices">로 설정해 객체를 넘겼다.
//해당객체를 주입하여 인자로 넘겨줌으로써 위에서 선언한 객체변수들을 쓸 수 있는 것이다.
public void setAccountServices(AccountServices accountServices) {
this.accountServices = accountServices;
}
public AuthenticationService getAuthenticationService() {
return authenticationService;
}
public void setAuthenticationService(
AuthenticationService authenticationService) {
this.authenticationService = authenticationService;
}
}
패키지는 controller , services , commands로 나뉜다..
controller : 말그대로 컨트롤러 클래스 위치
commands : User , Account , Login 등 빈즈 위치
services : 비즈니스 로직이 구현된 클래스 위치.
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="simpleUrlMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/login.html">loginBankController</prop>
</props>
</property>
</bean>
<bean id="logonValidator" class="springexample.commands.LogonValidator"/>
<!-- LoginBankController is a SimpleFormController -->
<bean id="loginBankController"
class="springexample.controller.LoginBankController">
<!--AbstractFormController에 sessionForm()가 정의되어있어 사용가능하다 -->
<property name="sessionForm">
<value>true</value>
</property>
<!-- This is Command object. The command object can be
accessed in a view through <spring:bind path="loginCommand"> -->
<!--커맨드명설정 BaseCommandController클래스에 commandName()메소드가 정의되어있다 -->
<property name="commandName">
<value>loginCommand</value>
</property>
<!--커맨드클래스로 LoginCommand클래스 사용. 파라미터타입은 class타입 -->
<property name="commandClass">
<value>springexample.commands.LoginCommand</value>
</property>
<!-- BaseCommandController에 setValidator()메소드가 정의되어있다. -->
<property name="validator">
<ref bean="logonValidator"/>
</property>
<!-- Indicates what view to use when the user asks for a new form
or when validation errors have occurred on form submission. -->
<!--SimpleFormController클래스에 setFormView()메소드 정의되어있다. 인자로넘긴 login은 login.jsp로 연결
결국 onSubmit()에서 에러가 나면 폼입력화면..또는 처음요청은 GET방식이니까 폼입력으로.. -->
<property name="formView">
<value>login</value>
</property>
<!-- Indicates what view to use when successful form submissions
have occurred. Such a success view could e.g. display a submission
summary. More sophisticated actions can be implemented by
overriding one of the onSubmit() methods.-->
<!-- onSubmit()이 성공하면 getSuccessView()를 리턴하는데 여기서 setSuccessView를 세팅해준다.
accountdetail.jsp로 가는거다. -->
<property name="successView">
<value>accountdetail</value>
</property>
<property name="authenticationService">
<ref bean="authenticationService" />
</property>
<property name="accountServices">
<ref bean="accountServices" />
</property>
</bean>
<!--뷰리졸버 세팅 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass">
<value>org.springframework.web.servlet.view.JstlView</value>
</property>
<property name="prefix">
<value>/WEB-INF/jsp/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
(key=”<mapping-pattern>”)
● /myapp/*.foo (pattern)
– /myapp/x.foo, /myapp/yz.foo (examples that match the pattern)
● /myapp/p*ttern
– /myapp/p1ttern, /yapp/pxttern
● /**/example
– /myapp/example, youapp/yourdir/example
● /myapp/**/mydir/foo.*
– /myapp/yourapp/yourdir/mydir/foo.x
● /**/*.jsp
– /yourapp/yourdir/x.jsp, /myapp/mydir/yz.jsp
두개의 servlet.xml 파일을 만들어서 각각 요청에 맞게 사용하고 싶은데.. 어떻게 해야할까...
책에 ApplicationContext설정을 보니까
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/front.xml</param-value>
</init-param>
이런식으로 쓰더라구..
처음 만들었던 dispatcher-servlet.xml과 새로 만든 hello-servlet.xml 을 web.xml에 위에 맞게 각각 설정해줬다..
hello.htm , hello2.htm 요청을 하니 hello2.htm만 정상적으로 뜬다,.
어찌된 걸까... 하나를 주석처리하고 실행해보면 각각 잘 된다.
그렇다면 설정에 문제가 있다고 생각했다...
삽질에 삽질...
책을 다시 봤다..
웹요청과 컨트롤러 매핑 : HandlerMapping 챕터에서 설명이 나와있었다..
문제는 <servlet-mapping> 두 서블릿 모두 .htm 으로 url-pattern을 설정한 것...
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>
SpringTest</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<!--한글인코딩 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>
org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>EUC-KR</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>
dispatcher
</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
</servlet>
<servlet>
<servlet-name>
hello
</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
</servlet>
<!--http://localhost/spring/say/hello.htm으로 호출 -->
<servlet-mapping>
<servlet-name>
dispatcher
</servlet-name>
<url-pattern>/say/*</url-pattern>
</servlet-mapping>
<!--http://localhost/spring/hello2.htm으로 호출 -->
<servlet-mapping>
<servlet-name>
hello
</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
</web-app>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="kame.spring" />
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/index.htm">indexController</prop>
<prop key="/hello2.htm">helloController</prop>
</props>
</property>
</bean>
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/test/"
p:suffix=".jsp" />
<bean name="indexController" class="org.springframework.web.servlet.mvc.ParameterizableViewController"
p:viewName="index" />
</beans>
============================================================================
dispatcher-servlet.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-2.5.xsd">
<!--핸들러-->
<bean id="handlerMapping"
class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
<bean name="/hello.htm" class="kame.spring.chap04.HelloController" />
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/view/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
스프링은 설정파일 부분이 너무 복잡하다. 방식도 다양하고 상속관계도 알아둘 필요가 있다.
아무튼 성공!!
<form:form>
<form:input path = "userId" />
...
<input type="submit" value="회원가입" />
</form:form>
<form:form commandName = "memberInfo">
....
</form:form>
커맨드객체의 이름이 기본 값인 command가 아니라면 위와 같이 commandName속성에 커맨드 객체의 이름을 명시해 주어야 한다.
<input>을 위한 커스텀태그 <form:input>,<form:password>,<form:hidden>
<form:form commandName = "memberInfo">
<p>
<form:label path="userId">회원아뒤</form:label>
<form:input path="userId"/>
<form:errors path="userId"/>
</p>
path속성에서 지정한 커맨드 객체의 프로퍼티 값이 출력된다. 그러니까 memberInfo.userId 가 되겠지...(안해봤지만ㅋ)
이외에도 <form:select>, <form:options>, <form:option> referenceData()메소드 이용...
체크박스, CSS및 HTML공통속성
에러관련 커스텀 태그 등이 있다.,.. 책을 참조하자..
컨트롤러 부분은 깊게 공부하지 않았다.
이거 일일이 진지하게 파헤쳐보려다간 시간을 너무 많이 날릴 수 있으므로... 충분히 실제 코딩하면서 레퍼런스할 정도로 공부해 두면 되겠다.
컨트롤러 클래스 분류
컨트롤러 부분은 어렵다.(무엇인들 어렵지 않을까..)
목적에 맞게 컨트롤러르 구현하자. 물론 책을 잘 참조해서,.
1.클라이언트의 요청이 DispatcherServlet에 전달
2.DispatcherServlet은 HandlerMapping을 사용하여 클라이언트의 요청을 처리할 컨트롤러 객체를 구한다
3.DispatcherServlet은 컨트롤러 객체의 handleRequest()메소드를 호출하여 클라이언트 요청 처리
4.컨트롤러의 handleRequest()메소드는 처리결과 정보를 담은 ModleAndView객체를 리턴
5.DispatcherServlet은 viewResolver로부터 응답 결과를 생성할 뷰 객체를 구한다.
6.뷰는 클라이언트에 전송할 응답을 생성
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>
SpringTest</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>
dispatcher
</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>
dispatcher
</servlet-name>
<url-pattern>*.htm
</url-pattern>
</servlet-mapping>
</web-app>
dispatcher-servlet.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-2.0.xsd">
<!--핸들러-->
<bean id = "handlerMapping"
class = "org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
<bean name = "/hello.htm"
class = "kame.spring.chap04.HelloController" />
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/view/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
web.xml에 DispatcherServlet설정과 어플리케이션 컨텍스트 설정
.hml으로 들어오는 클라이언트의 요청을 DispatcherServlet이 처리하도록 설정.
DispatcherServlet은 WEB-INF/에 위치한 [서블릿이름]-servlet.xml파일을 설정 파일로 사용
import java.util.Calendar;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
public class HelloController extends AbstractController{
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception{
ModelAndView mav = new ModelAndView();
mav.setViewName("hello");
mav.addObject("greeting",getGreeting());
return mav;
}
private String getGreeting(){
int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
if(hour >= 6 && hour <= 10){
return "좋은아침";
}else if(hour > 12 && hour <= 15){
return "점심해써?";
}else if(hour >= 18 && hour <= 22){
return "좋은밥";
}
return "안녕하세요";
}
}
처리결과를 ModelAndView에 담아 DispatcherServlet에 전달. ModelAndView는 컨트롤러의 처리결과에 대한 응답 화면을 생성할 뷰 정보저장.
<bean id = "handlerMapping"
class = "org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
<bean name = "/hello.htm"
class = "kame.spring.chap04.HelloController" />
BeanNameUrlHandlerMapping은 URL과 일치하는 이름을 갖는 컨트롤러를 사용. http://localhost/contextpath/hello.htm으로 요청이 들어올 경우, 이름이 "/hello.htm"인 컨트롤러를 이용하여 클라이언트의 요청을 처리한다.
처음해보면서 안되었는데... 톰캣의 server.xml 올바르게 잡고,, 소스 오타수정 모두 하고.. 모두 완벽했었는데 안되었다.
http://localhost/spring/view/hello.htm 주소창에 요렇게 치니까 404에러... 톰캣에서 docBase를 WebContent까지 패스에 잡아주었으므로 WebContent폴더 안에 있는 view폴더부터 쓰는것이 당연하다.. view에 있는 hello.jsp로 직접 접근하니 되었는데...(물론 매핑이 안되서 결과물은 안나왔다..)
안되서... 예제도 설치해서 해봤는데.. 예제는 이상한 에러 쫘악... 별짓다해봤다.. 자바버전도 수정해보고 폴더를 옮겨보기도 했는데...
어쨌든 해결했는데.... 주소창에 http://localhost/spring/hello.htm 로 view폴더를 빼고 치니까 되는거다!!. 프레임워크니까!! 괜히 프레임워크겠어!! spring이 톰캣에 설정한 패스이름이다.... 결론은 폴더고 나발이고간에 xml에 설정해놓으면 패스명/xxx.htm하면 되는거다!!
pageEncoding="EUC-KR"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=EUC-KR">
<title>인사</title>
</head>
<body>
인사말 " <strong>${greeting}</strong>
</body>
</html>
mav.setViewName("hello");
뷰이름을 가지고 viewResolver가 이 이름을 이용하여 알맞은 view객체를 선택한다.
스프링 컨테이너의 주요 인터페이스
1.1 BeanFactory인터페이스
org.springframework.beans.factory.BeanFactory는 빈 객체를 관리하고 각 빈 객체 간의 의존 관계를 설정해 주는 기능을 제공.
XmlBeanFactory클래스가 구현.
Resource인터페이스를 사용하여 다양한 종류의 자원을 동일한 방식으로 표현.
XmlBeanFactory factory = new XmlBeanFactory(res);
ParserFactory factory = (ParserFactory)factory.getBean("parserFactory");
특정 리소스로부터 설정 정보를 읽어와 XmlBeanFactory객체를 생성한 뒤에는 getBean()메소드를 이용하여 알맞은 빈을 가져와 사용하면 된다.
1.2 ApplicationContext , WebApplicationContext 인터페이스
빈팩토리인터페이스를 상속받은 인터페이스로서 빈팩토리가 제공하는 빈 관리 기능 이외에 파일과 같은 자원처리추상화, 메시지 지원 및 국제화지원, 이벤트 지원등 추가적인 기능을 제공하고 있다. BeanFactory보다 ApplicationContext 구현 클래스르 주로 사용한다.
빈(Bean)의 생성과 의존관계 설정
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
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-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<bean id = "writeArticleService" class = "kame.spring.chap02.WriteArticleServiceImpl" >
<constructor-arg>
<ref bean = "articleDao" />
</constructor-arg>
</bean>
<bean id = "articleDao" class = "kame.spring.chap02.OracleArticleDao" >
</bean>
bean의 id는 객체명이다. class는 이 객체를 참조하는 클래스가 있는 경로를 써주면 된다.
생성자를 통해 의존하는 객체르 전달받는 경우 <constructor-arg>태그를 이용하여 의존하는 객체를 전달할 수 있다.
위 코드는 WriteArticleServiceImpl클래스의 생성자에 식별 값이 "articleDao"인 빈 객체를 전달한다는 것을 의미한다.
코드로는....
WriteArticleServiceImpl writeArticleService = new WriteArticleServiceImpl(articleDao);
ref bean을 안쓰고 직접 constructor-arg ref="articleDao" 도 가능하다. 또한 <value>값도 설정할 수 있는데...
<constructor-arg>
<value>10</value>
</constructor-arg>
이런식으로 가능핟. value값은 String타입으로 처리된다. 따라서 숫자 3000의 value를 전달해도 파라미터가 String타입의 파라미터가 정의되어있으면 매핑되고,, 만약 스트링이 없고 int타입이 있으면 int타입으로 매핑된다.
프로퍼티
프로퍼티는 익숙하지... setXXXXX()형태의 메소드를 매핑한다. 앞의 set을 빼고 XXXXX가 이름이 된다.
이렇게 생각하자...
bean은 객체
property는 (set)메소드..
<property name = "periodTime" value = "10" />
<property name = "sender" value = "smsSender" />
</bean>
setPeriodTime메소드에 파라미터값으로 10을 전달, setSender메소드에 smsSender값을 전달한다. 쉽지 않은가..ㅋ
이외에도 컬렉션타입(set,map,list,제네릭)으로도 의존관계 설정이 가능하니 책을 참조하자.
의존관계 자동 설정
byName : 프로퍼티의 이름과 같은 이름을 갖는 빈 객체를 설정한다.
byType : 프로퍼티의 타입과 같은 타입을 갖는 빈 객체를 설정한다.
constructor : 생성자 파라미터 타입과 같은 타입을 갖는 빈 객체를 생성자에 전달한다.
autodetect : 생성자방식을 먼저 적용하고 byType방식을 적용하여 의존객체 설정한다.
<bean id = "monitor" class = " ....SystemmMonitor" autowire = " byName" />
1)프로퍼티 이름을 이용한 의존 관계 자동 설정
<bean name = "writeArticleService"
class = "kame.spring.chap02.WriteArticleServiceImpl" autowire = "byName" />
<bean name = "articleDao" class = "kame.spring.chap02.MysqlArticlDao" >
<bean name = "myArticleDao" class = "kame.spring.chap02.MysqlArticlDao" >
WriteArticleServiceImpl클래스가 이름이 "articlDao"인 프로퍼티를 갖고 있다면, 이름이 "articleDao"인 빈 객체가 프로퍼티의 값으로 전달된다.
2)타입을 이용한 의존 관계 자동 설정
<bean name = "writeArticleService"
class = "kame.spring.chap02.WriteArticleServiceImpl" autowire = "byType" >
<bean name = "myArticleDao" class = "kame.spring.chap02.MysqlArticlDao" >
<bean name = "oracleArticleDao" class = "kame.spring.chap02.OracleArticlDao" >
위코드에서 myArticleDao빈 객체와 oracleArticleDao 빈 객체는 둘 다 ArticleDao 타입이기 때문에 어떤걸 프로퍼티에 넣어야 할 지 결정할 수가 없다. 이럴때 예외를 발생시킨다.
최범균님의 책.. 스프링2.5를 보고 공부하고있다.
서평이나 사람들 말을 들어보면 완벽한 책이라고... 베스트셀러에다..
인정한다. 헌데 나랑 좀 맞지 않는 듯... 아..그것보다 내 실력이 쓰레기이기 때문이겠지...ㅋㅋ
구성은 좋은데 너무 딱딱하여 이해가 잘 가지 않는다. 논문보는듯..;;
아무튼 AOP.. 부분.. 다 훑어봤지만 20%정도 이해한듯 하다. 헐..어려워..
AOP
어플리케이션 전반에 걸쳐 필요한 기능. 핵심 비즈니스 기능과 구분하기 위해 공통기능을 공통관심사항(cross-cutting concern)
핵심 로직을 핵심관심사항(core concern)이라고 한다.
AOP용어
Advice : 언제 공통 관심 기능을 핵심 로직에 적용할지를 정의하고 있다. 예를 들어, 메소드를 호출하기 전에 트랜잭션을 시작한다(공통기능) 기능을 적용한다는 것을 정의하고 있다.
Joinpoint : Advice를 적용 가능한 지점을 의미한다. 메소드호출, 필드값 변경 등이 해당된다.
Pointcut : Joinpoint의 부분 집합으로써 실제로 Advice가 적용되는 Joinpoint를 나타낸다. 스프링에서는 정규표현식이나 AspectJ의 문법을 이용하여 Pointcut을 정의할 수 있다.
Weaving : Advice를 핵심 로직 코드에 적용하는 것을 위빙이라 한다. 공통코드를 핵심 로직 코드에 삽입하는 것이 위빙이다.!
Aspect : 여러 객체에 공통으로 적용되는 공통 관심 사항을 Aspect라고 한다. 트랜잭션이나 보안 등이 Aspect의 좋은 예이다.
스프링 API를 이용한 AOP
(1)MethodBeforeAdvice
대상 객체의 메소드를 실행하기 전에 공통 기능을 적용하고 싶을때에 사용되는 Advice인터페이스.
void before(Method method, Object[] args, Object target) throws Throwable;
Method method : 대상 객체에서 실제로 호출될 메소드를 나타내는 Method객체
Object[] args : 메소드 호출시 전달된 인자 목록
Object target : 실제 대상 객체
이외에..
(2)AfterReturningAdvice : 대상객체의 메소드를 실행한 후 공통기능 적용.
(3)ThrowsAdvice : 예외가 발생했을때 공통기능을 적용하고 싶을 때 사용.
(4)MethodInterceptor : 앞의 어드바이스를 모두 합쳐 놓은 인터페이스
Pointcut 및 Advisor 설정
Advice를 작성했다면,, Advice를 어떤 Joinpoint에 적용할지를 지정할 차례이다. 즉, Pointcut을 설정할 차례.
org.springframework.aop.support.JdkRegexMethodPointcut : 정규표현식을 이용하여 포인트컷 정의.
org.springframework.aop.aspectj.AspectJExpressionPointcut : aspectj의 표현식을 이용하여 포인트컷 정의
<bean id = "writePointcut" class = "org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name = "pattern" value = ".*write.*" />
</bean>
<bean id = "writeAdvisor" class = "org.....DefaultPointcutAdvisor">
<property name = "advice" ref = "beforeLogAdvice" />
<property name = "pointcut" ref = "writePointcut" />
</bean>
위 설정은 writeAdvisor는 beforeLogAdvice 어드바이스가 writePointcut 포인트컷에 적용된다는 것을 정의하고 있다. 즉, 메소드 이름에 write를 포함하고 있는 메소드를 호출할때 beforeLogAdvice를 적용하라고 설정하였다.
위 작업은 꾀 성가신 작업인데 RegexMethodPointcutAdvisor를 이용해 작업을 줄일수 있다.
<bean id = "writeAdvisor" class = "org.....RegexMethodPointcutAdvisor">
<property name = "advice" ref = "beforeLogAdvice" />
<property name = "pattern" ref = ".*write.*" />
</bean>
AspectJExpressionPointcut
expression프로퍼티를 이용하여 Pointcut을 정의하는 표현식을 설정한다.
<property name = "expression" value = "execution(public !void get*(..))" />
</bean>
<bean id = "getAdvisor" class = "org.....DefaultPointcutAdvisor" >
<property name = "advice" ref ="cacheadvice" />
<property name = "pointcut" ref = "getPoint" />
</bean>
XML네임스페이스 영문 페이지
http://www.w3.org/TR/REC-xml-names/
spring에서...
의존 관계를 설정하는 방법 중의 하나로...
<property>태그를 사용하지 않고 property 값을 설정하려 할 때 XML네임스페이스를 이용한다.
[XML네임스페이스]
xml네임스페이스는 xml문서에 URI레퍼런스로 구별되는 네임스페이스를 사용하여 xml문서에 쓰인 요소와 속성의 이름을 한정시킬 수 있게 한다.
.....
확장된 이름은 네임스페이스의 이름과 로컬 이름으로 구성된다. URI레퍼런스에서 빈 문자열은 네임스페이스의 이름으로 사용할 수 없다. 동일한 문서의 레퍼런스를 포함하고 있는 상대URI레퍼런스는 네임스페이스 선언에서 사용할 수 없다.
.....
네임스페이스는(정확히 말하자면 네임스페이스 바인딩) 예약된 속성어를 사용하여 선언하며 속성의 이름은 xmlns이거나 xmlns로 시작되어야 한다. XML의 다른 속성과 마찬가지로 여기에서 쓰이는 속성도 직접 입력하거나 디폴트로 해서 생성된다.
속성의 표준값은 URI레퍼런스 - 네임스페이스에 구별되는 네임스페이스의 이름 - 이거나 디폴트이어야 하며 지정된 용도에 맞게 쓰려면 네임스페이스의 이름은 유일성과 영속성을 가지고 있어야 한다. (스키마가 있더라도)이것은 스키마 검색을 위한 직접적인 용도는 아니다.
.....
속성이름이 PrefixedAttName과 일치하면 NCname은, 선언에 나온 요소의 영역에 있는 속성값에서 네임스페이스 이름과 함께 쓰이는 요소와 속성 이름에 결합하여 쓸 수 있는 네임스페이스 접두어를 사용한다. 이 때, 네임스페이스 속성은 공백이 될 수 없다.
속성이름이 DefaultAttName과 일치하면 속성값에서 네임스페이스 이름은 선언에 나온 요소의 영역에서 디폴트네임스페이스이다.
네임스페이스 이름 http://ecommerce.example.org/schema 에 네임스페이스 접두어 edi가 결합된 네임스페이스 선언의 예
<x xmlns:edi='http://ecommerce.example.org/schema'>
<!-- the "edi" prefix is bound to http://ecommerce.example.org/schema
for the "x" element and contents -->
</x>
.....
PrefixedAttName = xmlns : NCname
DefaultAttName = xmlns
prefix = NCname
.....
qualified name은 네임스페이스의 설명을 일컫는다.
접두어는 qualified name의 네임스페이스 접두어 부분을 제공하며 네임스페이스 선언에서 URI레퍼런스와 결합하여 쓰인다.
(...이상 발해석이었음)
즉, 네임스페이스에서 접두어로 사용하는 문자는 NCname이다...(나의 결론)
1. 클라이언트의 요청을 받을 DispatcherServlet을 web.xml 파일에 설정한다.
2. HandlerMapping을 이용하여 요청 URL과 컨트롤러의 매핑 방식을 설정한다.
3. 클라이언트의 요청을 처리(로직을 수행)할 컨트롤러를 작성한다.
4. 컨트롤러의 처리 결과 응답 화면을 생성할지를 결정하는 뷰 리졸버를 구성한다.
5. 사용자에게 보여주는 JSP를 작성한다.
최소한의 작업.
2. HandlerMapping을 사용하여 클라이언트의 요청을 처리할 컨트롤러 객체 매핑
3. handleRequest() 메서드를 호출하여 클라이언트의 요청 전달,
5. 클라이언트에 전송할 응답 생성
AOP 설정 엘리먼트 | 목적 |
<aop:advisor> | AOP 어드바이저를 정의 한다 |
<aop:after> | After어드바이스를 정의한다.(이 어드바이스는 메서드의 예외발생 여부와 관계 없다.) |
<aop:after-returning> | After-returning 어드바이스를 정의한다. |
<aop:after-throwing> | After-throwing 어드바이스를 정의한다. |
<aop:around> | Around 어드바이스를 정의한다. |
<aop:aspect> | 애스펙트를 정의한다. |
<aop:before> | Before 어드바이스를 정의한다. |
<aop:config> | 최상위 AOP엘리번트. 대부분의 <aop:*> 엘리먼트는 이 <aop:config>안에 포함되야 한다. |
<aop:pointcut> | 포인트컷을 정의한다. |
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<bean id="audience" class="chapter4.springidol.Audience" />
<aop:config>
<aop:aspect ref="audience">
<aop:before
method="takeSeats"
pointcut="execution(* *.perform(..))" />
<aop:before
method="turnOffCellPhones"
pointcut="execution(* *.perform(..))" />
<aop:after-returning
method="applaud"
pointcut="execution(* *.perform(..))" />
<aop:after-throwing
method="demandRefund"
pointcut="execution(* *.perform(..))" />
</aop:aspect>
</aop:config>
</beans>
...
<aop:config>
<aop:aspect ref="audience">
<aop:pointcut
id="performance"
expression="execution(* *.perform(..))" />
<aop:before
method="takeSeats"
pointcut-ref="performance" />
<aop:before
method="turnOffCellPhones"
pointcut-ref="performance" />
<aop:after-returning
method="applaud"
pointcut-ref="performance" />
<aop:after-throwing
method="demandRefund"
pointcut-ref="performance" />
</aop:aspect>
</aop:config>
...
@Aspect 어노테이션은 AspectJ 5버전에 새롭게 추가된 어노테이션으로, @Aspect 어노테이션을 사용하면 설정파일에 Advice 및 Pointcut등의 설정을 하지 않고도 자동으로 Advice를 적용 할 수 있습니다. 스프링2부터 이 어노테이션을 지원하고 있으며 설정파일에 <aop:aspectj-autoproxy> 태그를 설정파일에 추가해주면 @Aspect 어노테이션이 적용된 빈을 Aspect로 사용할 수 있게 됩니다. aop네임스페이스도 설정파일에 포함시켜야 합니다.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<aop:aspectj-autoproxy
/>
<bean class="chapter4.springidol.Audience" />
....
</beans>
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class Audience {
public Audience() {
}
@Pointcut("execution(* *.perform(..))")
public void performance() {
}
@Before("performance()")
public void takeSeats() {
System.out.println("The audience is taking their seats.");
}
@Before("performance()")
public void turnOffCellPhones() {
System.out.println("The audience is turning off their cellphones");
}
@AfterReturning("performance()")
public void applaud() {
System.out.println("CLAP CLAP CLAP CLAP CLAP");
}
@AfterThrowing("performance()")
public void demandRefund() {
System.out.println("Boo! We want our money back!");
}
}
어드바이스 유형 | 인터페이스 |
Before | org.springframework.aop.MethodBeforAdvice |
After-returning | org.springframework.aop.AfterReturningAdvice |
After-throwing | org.springframework.aop.ThrowAdvice |
Around | org.aopalliance.intercept.MethodInterceptor |
Introduction | org.springframework.aop.IntroductionInterceptor |
/**
* 세가지 어드바이스 유형을 구한다. MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice
*
*/
public class AudienceAdvice implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice {
public AudienceAdvice() {
}
// 메소드 실행전에 호출된다.
public void before(Method method, Object[] args, Object target) throws Throwable {
audience.takeSeats();
audience.turnOffCellPhones();
}
// 성공적으로 메소드가 반환되면 실행된다
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
audience.applaud();
}
// 예외가 발생되면 실행된다.
public void afterThrowing(Throwable throwable) {
audience.demandRefund();
}
private Audience audience;
public void setAudience(Audience audience) {
this.audience = audience;
}
}
<bean id="audience" class="chapter4.classicSpringAop.Audience" />
<bean id="audienceAdvice"
class="chapter4.classicSpringAop.AudienceAdvice">
<property name="audience" ref="audience" />
</bean>
<bean id="audienceAdvisor"
class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="audienceAdvice" />
<property name="pointcut" ref="performancePointcut" />
</bean>
<bean id="performancePointcut"
class="org.springframework.aop.aspectj.AspectJExpressionPointcut">
<property name="expression" value="execution(* perform(..))" />
</bean>
<bean id="performancePointcut"
class="org.springframework.aop.aspectj.JdkRegexpMethodPointcut">
<property name="pattern" value="*.perform" />
</bean>
기호 | 의미 |
. | 어떤 문자든 딱 한 글자 |
* | * 앞에 있는 문자가 여러개 있을 수 있음 |
+ | + 앞에 있는 문자가 최소한 한 개에서 여러개 있을 수 있음 |
\ | \ 뒤에 오는 문자가 있어야한다 |
<bean id="audienceAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="audienceAdvice" />
<property name="pattern" value=".*perform" />
</bean>
<bean id="dukeTarget"
class="chapter2.springidol.PoeticJuggler"
autowire="constructor">
<constructor-arg ref="sonnet29"/>
</bean>
<bean id="duke" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="dukeTarget" />
<property name="interceptorNames" value="audienceAdvisor" />
<property name="proxyInterfaces" value="chapter2.springidol.Performer" />
</bean>
<bean id="stevieTarget"
class="chapter2.springidol.PoeticJuggler"
autowire="constructor">
<constructor-arg ref="sonnet29"/>
</bean>
<!-- 똑같은 코드의 중복 -->
<bean id="stevie" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="stevieTarget" />
<property name="interceptorNames" value="audienceAdvisor" />
<property name="proxyInterfaces" value="chapter2.springidol.Performer" />
</bean>
<bean id="dukeTarget"
class="chapter2.springidol.PoeticJuggler"
autowire="constructor">
<constructor-arg ref="sonnet29"/>
</bean>
<bean id="stevieTarget"
class="chapter2.springidol.PoeticJuggler"
autowire="constructor">
<constructor-arg ref="sonnet29"/>
</bean>
<bean id="audienceProxyBase"
class="org.springframework.aop.framework.ProxyFactoryBean"
abstract="true">
<property name="interceptorNames" value="audienceAdvisor" />
<property name="proxyInterface" value="chapter2.springidol.Performer" />
</bean>
<bean id="stevie" parent="audienceProxyBase">
<property name="target" ref="stevieTarget" />
</bean>
<bean id="duke" parent="audienceProxyBase">
<property name="target" ref="dukeTarget" />
</bean>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
Aspect Oriented Programming의 약자로 여러 곳에 흩어져 존재할 수 밖에 없었던 코드를 모듈화 할 수있는 프로그래밍 기법입니다. Aspect라는 것을 사용하요 츠로그래밍을 할 때 특정한 관심사를 가진 코드 부분을 별도의 모듈로 분리함으로써, 기존 객체지향의 강력한 기능(상속, 위임)만으로는 처치가 곤란했던 중복을 제거 할 수 있게 되었습니다.
로깅, 트랜잭션 처리, 모니터링 관련 코드들을 AOP를 적용하여 하나의 Aspect로 모듈화각 가능하므로 서비스계층이나 도메인 계층에서 관련된 코드들의 제거 할수 있습니다. 또 특정 클래스에 종속성을 가지던 현상을 제거 할수 있게 되었습니다.
Spring AOP용어의 정리
◎ Advice : 언제 무엇을 할지 나타냅니다. 즉 애스펙트가 해야하는 작업과 언제 그 작업을 수행해야 하는지를 정의한 것이 어드바이스 입니다. (실제로 Target 객체에 적용할 일을 나타냄.)스프링은 의존하는 빈 객체의 타입이나 이름을 이용하여 의존 객체를 자동으로 설정할 수 있습니다. 여기(The Spring Framework - Reference Documentation)에 자세한 내용이 있네요 ^^;
일단 네가지 방식이 존재합니다. 방식으로는
1. byName : 프로퍼티의 이름과 같은 이름을 갖는 빈객체를 설정
2. byType : 프로퍼티의 타입과 같은 타입을 갖는 빈객체를 설정
3. constructor : 생성자 파라미터 타입과 같은 타입을 갖는 빈객체를 설정
4. autodetect : constructor방식을 먼저 적용, byType방식을 적용
네 가지 방식이 있습니다.
일단 설정파일에 포함된 모든 빈 객체에 대해서 특정방시의 자동 설정을 적용하고 싶다면
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:p="http://www.springframework.org/schema/p"
- 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-2.0.xsd
- default-autowire="byName">
- ...
- ...
- </beans>
네가지의 설정 중에서 예를 하나만 들겠습니다.
- <bean name="userService" class="yoyojyv.service.UserServiceImpl" autowire="byName"/>
DI설정을 프로퍼티 방식(setter Injection)을 썼을경우에 autowire속성을 constructor로 하면 어떻게 될까요? 직접 해본 결과 오류가 납니다. ㅋㅋ 각 설정에 맞게 써 주어야 하고, 또 byType, constructor, autodetect의 경우 동일한 타입의 빈 객체를 2개 이상 정의를 하게되면 오류가 납니다. 지가 어떤빈을 써먹어야 할지 결정을 못하네요~
의존 관계를 자동으로 알맞게 설정해주는 기능은 설정파일의 크기를 줄여주고 유용한 기능이긴 하지만, 설정파일만으로 빈 객체간의 의존관계를 파악하기 힘들다는 단점이 있습니다. 규모가 커질수록~ 더 파악하기 힘들어지기때문에 상황에 맞게~ 되도록이면 설정파일에 의존관계를 명시하는 것이 좋은 방법이라고 합니다.
스프링 프레임워크가 가지는 가장 핵심적인 기능은 IOC(Inversion Of Control)입니다. 그중에서 DI(Dependency Injection)는 스프링 프레임워크에서 지원하는 IOC의 한 형태입니다.
DI는 클래스 사이의 의존관계를 빈 설정(Bean Definition) 정보를 바탕으로 컨테이너가 자동적으로 연결해 주는 것을 말합니다. 컨테이너가 의존관계를 자동적으로 연결시켜 주기때문에 컨테이너 API에 종속되는 것을 줄일 수 있습니다.
의존관계를 설정하는 방식으로는 자주 쓰이는 것들에는 생성자 방식(Constructor Injection), 프로퍼티 설정 방식(Setter Injection) 등이 있습니다.
1. 생성자 방식
UserServiceImpl.java
public class UserServiceImpl implements UserService {
private UserDAO userDAO;
public UserServiceImpl(UserDAO newUserDAO) {
this.userDAO = newUserDAO;
}
...
}
<bean id="userDAO" class="yoyojyv.dao.MySQLUserDAO"/>
<bean id="userService" class="yoyojyv.service.UserServiceImpl">
<constructor-arg>
<ref bean="userDAO"/>
</constructor-arg>
</bean>
UserServiceImpl.java 의 생성자와 applicationContext.xml 의 3~5번째줄의 <constructor-arg>태그를 이용하여 의존하는 객체를 전달 할 수 있습니다.
<constructor-arg ref="userDAO"/ >
2. 프로퍼티 설정 방식
UserServiceImpl.java
public class UserServiceImpl implements UserService {
private UserDAO userDAO;
public void setUseDAO(UserDAO newUserDAO) {
this.userDAO = newUserDAO;
}
....
}
<bean id="userDAO" class="yoyojyv.dao.MySQLUserDAO"/>
<bean id="userService" class="yoyojyv.service.UserServiceImpl">
<property name="userDAO">
<ref bean="userDAO"/>
</property>
</bean>
UserServiceImpl.java 의 3~5번째줄의 setter메소드와 applicationContext.xml 의 3~5번째줄의 <property>태그를 이용하여 의존하는 객체를 전달 할 수 있습니다.
나머지 방식들과 IoC container에관한 내용은 여기(The Spring Framework - Reference Documentation)<-- 에서 확인 하세요~