[Spring 3.2] Interceptor에서 Controller의 어노테이션 체크하기

Published on: 2013. 12. 2. 19:45 by louis.dev

인증이 필요한 서비스를 개발할 경우, Interceptor에서 로그인 유무를 확인하는 로직을 추가하여 각 컨트롤러에 인증 로직을 추가로 작성하지 않도록 할수 있습니다.


만약 이때 어떤 컨트롤러의 메소드는 로그인을 체크하고 어떤 컨트롤러의 메소드에는 로그인을 체크하지 않도록 하려면 어떻게 해야 할까요?


스프링 3.2 부터는 mvc:interceptor 부분에 exclude-mapping 이라는 엘리먼트가 추가되어 기존의 스프링 버전보다 쉽게 인터셉터의 적용을 제외시킬수 있습니다.

<mvc:interceptor> <mvc:mapping path="/**"/> <exclude-mapping path="/info/**"/> <bean class="net.krespo.interceptor.LoginCheckInterceptor" /> </mvc:interceptor>

이렇게 exclude-mapping을 사용하면 LoginCheckInterceptor는 /info/ 로 시작하는 컨트롤러에 인터셉터 적용을 제외 시킬수 있습니다.


그런데 만약 /info/my는 로그인 인터셉터를 수행시키고 /info/test는 로그인 인터셉터를 수행시키려면 어떻게 해야할까요?


물론 exclude-mapping을 여러개 등록하면 되지만 로그인이 필요하지 않은 컨트롤러가 생길때마다 exclude-mapping을 선언하는것이 귀찮게 느껴질겁니다.


이럴때는 임의의 어노테이션을 생성하여 인증이 필요하지 않은 컨트롤러의 메소드에 어노테이션을 붙이고, 어노테이션 유무를 인터셉터에서 확인하여 로그인을 할지 안할지를 하면 좀더 편하게 개발을 진행할 수 있을것 같습니다.


이제부터 본격적으로 인터셉터에서 컨트롤러의 어노테이션을 체크하는 방법을 알아보도록 하겠습니다.


1. 어노테이션 생성

package net.krespo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoLoginCheck {
}

NoLoginCheck라는 어노테이션을 생성하였습니다. 이 어노테이션을 사용하여 어노테이션을 메소드에 추가를 했으면 로그인 체크를 하고, 어노테이션이 없으면 로그인 체크를 하지 않도록 할것입니다.


2. 컨트롤러에 어노테이션 추가


@Controller
public class MyController {
	
	@RequestMapping("/my/info")
	@NoLoginCheck 
	public ModelAndView myInfo(){
		...
	}
	
}

3. Interceptor에서 어노테이션 체크하기

public class LoginInterceptor extends WebContentInterceptor {
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException {
		//@NoCheckLogin 어노테이션이 컨트롤러에 사용되었는지 체크함
		NoCheckLogin usingAuth = ((HandlerMethod) handler).getMethodAnnotation(NoCheckLogin.class);

		//NoCheckLogin 어노테이션이 없음으로 무조건 로그인 체크
		if(usingAuth == null) {
			//TODO 로그인 체크
		}
		//NoCheckLogin 어노테이션이 없음으로 로그인 체크 하지 않음
		else {
			//TODO 추가작업
		}
	}	
}

가장 중요한 부분은 preHandler 메소드의 handler Object를 HandlerMethod로 타입케스팅 하는 부분입니다. 이렇게 HandlerMethod로 바꾼 handler오브젝트의 getMethodAnnotation을 통해서 앞으로 실행될 컨트롤러의 메소드에 해당 어노테이션이 추가가 되었는지 안되어 있는지를 확인할 수 있습니다.


위 내용은 스프링 3.2에서 확인해 보았습니다. 그러나 스프링 3.0에서는 해당 handler Object를 HandlerMethod의 오브젝트가 아닌 앞으로 실행시킬 컨트롤러오브젝트(위의 예제에서는 LoginController)가 반환이 됨으로 위의 코드는 3.0에서는 사용할 수가 없습니다~(3.1에서는 체크를 해보지 못했습니다. 3.1에서 사용해 보신 분께서는 댓글로 알려주세요~^^)

[Spring 3.2] @ControllerAdvice를 이용한 익셉션 처리

Published on: 2013. 11. 29. 19:23 by louis.dev

스프링 프레임웍 3.2 이상부터 @ControllerAdvice라는 어노테이션이 추가 되었습니다. 이 어노테이션을 사용하면 간단하게 익셉션 발생시 작업을 처리할 수가 있습니다.


방법은 간단합니다. 설정파일에 아래와 같이 설정합니다. 아래의 설정 내용은 따로 설명하지 않겠습니다.

<annotation-driven />
<context:component-scan base-package="net.krespo" >

이후 적당한 위치에 클래스를 하나 생성하여 @ControllerAdvice를 붙여주면 됩니다.

package net.krespo.handler;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;


@ControllerAdvice
public class AnnotationExceptionHandler {
	@ExceptionHandler(Exception.class)
	public void handleException(Exception e) {
		System.out.println("exception");
	}
	
	@ExceptionHandler(RuntimeException.class) 
	public ModelAndView handleRuntimeException(RuntimeException e) {
		ModelAndView mnv = new ModelAndView("exceptionHandler");
		mnv.addObject("data", e.getMessage());
		
		return mnv;
	}
}

클래스에는 @ControllerAdvice를 선언해 주고 각 메소드 마다 @ExceptionHandler라는 어노테이션으로 어떤익셉션을 처리할 것인지를 선언해 주면 됩니다.


익셉션을 처리하는 메소드는 익셉션 파라미터를 받을 수 있습니다. 그래서 개발자가 익셉션에 메세지를 담아서 던지면 해당 메세지를 각 핸들러 메소드에서 확인할수 있습니다.


또한 리턴값으로는 void부터 ModelAndView까지 다양하게 리턴할수 있어 익셉션을 처리하는데 편리하게 사용할 수 있습니다.

Spring - Quartz를 사용하여 스케쥴러 구현하기

Published on: 2010. 9. 29. 15:43 by louis.dev

가끔 서버에서 주기적으로 어떠한 작업을 하고자 할때 리눅스에서는 크론탭을 사용하여 주기적으로 어떠한 작업을 처리합니다.
이런 주기적 작업을 처리하기위해 Spring에서 지원해 주는 Quartz스케쥴러를 통해 크론탭과 같은 역할을 하는 스케쥴러를 작성할 수 있습니다.
이번에는 Spring 과 Quartz를 연동하여 스케줄러를 작성해 보겠습니다.

작업순서는
스프링 기본 세팅 -> Quartz 세팅 순으로 작업하겠습니다.

1. 스프링 기본 설정
1) springframework.org 로 이동하셔서 스프링 라이브러리를 다운 받습니다.

위와 같은 페이지가 뜨면 해당사항을 입력하시고 Access Download 를 클릭하셔서 다운로드 페이지로 이동합니다. (귀찮으신 분들은 하단의 파란색으로 "download page"를 선택하시면 입력하시지 않고도 다운로드 페이지로 이동하실수 있습니다.

많은 버전의 라이브러리 중 spring-framework-2.5.6.SEC02.zip 를 다운 받습니다. 다른 버전을 다운 받으셔도 상관없습니다만 버전에 따라 세팅 내용이 조금 씩 달라지므로 같은 버전의 라이브러리로 진행하는 것이 나을 것같네요~^^.

2) 이렇게 라이브러리까지 다운로드 받고 나면 Eclipse와 같은 IDE에서 Dynamic Web Project를 선택하여 Project를 한개 생성합니다.
(저는 SpringQuartz 라는 이름으로 생성했습니다.)

3) 프로젝트가 생성되면 프로젝트 안에 /WEB-INF/lib 디렉토리에 스프링 라이브러리를 압축 푼 곳에 있는 dist/spring.jar 파일을 추가합니다.
* 팁 : 프로젝트를 진행하다 보면 위와같이 라이브러리 버전이 없는 jar파일을 그냥 추가하는 경우가 있는데 나중에 라이브러리를 업데이트 해야 할일이 생기게 되면 위와같이 spring.jar 라고 되어있으면 지금 적용되어 있는 버전이 몇 인지 알수가 없습니다. 그렇기 때문에 항상 라이브러리 추가하실때는 추가하시는 라이브러리의 버전 번호를 파일이름 뒤에 추가하는 습관을 들이 시는게 좋습니다.
     ex) spring-2.5.6.jar

4) 프로젝트 안에 생성된 web.xml에 Spring을 사용하기 위한 세팅을 추가해 줍니다.
* 저는 Quartz를 사용하기 위한 최소한의 Spring 세팅을 해놓았기 때문에 세팅 내용이 단순합니다. 만약 웹프로젝트와 함께 Quartz를 사용하신다면 웹에 맞게 설정하시고 사용하셔야 함을 알려드립니다.^^
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee" 	
	xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 	
	http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"	id="WebApp_ID" version="2.5">	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>	
	</listener>	
	<context-param>		
		<param-name>contextConfigLocation</param-name>		
		<param-value>/WEB-INF/config/applicationContext*.xml</param-value>
	</context-param>
</web-app>


5) 쿼츠 라이브러리를 다운로드 받고 라이브러리를 추가해 줍니다.
쿼츠 라이브러리 다운로드 하신다음 압축을 풀어 줍니다.
해당 라이브러리를 프로젝트의 lib 디렉토리에 복사하여 넣어줍니다.
- quartz-all-1.8.3.jar
- 압축푼 lib 디렉터리의 log4j-1.2.14.jar
- 압축푼 lib 디렉터리의 slf4j-api-1.5.10.jar
- 압축푼 lib 디렉터리의 slf4j-log4j12-1.5.10.jar
를 추가 해 줍니다.
마지막으로 apache의 commons-logging-1.1.1.jar 를 다운로드 하셔서 위와 같이 프로젝트의 lib에 추가해주시면 라이브러리 추가는 끝이 납니다.

6) Quartz의 핵심적인 기능을 할 /WEB-INF/config/applicationConext.xml 을 작성합니다.
스케쥴러의 핵심 세팅은 3가지 정도 입니다.
하나. 실제 주기적으로 실행될 클래스 등록
둘. 스케줄러가 동작하는 interval time 설정
셋. 실제 동작하게 끔 설정

이런 세가지가 있겠습니다.

스케줄러 동작방식에는 두가지가 존재 합니다.
-Simple : interval time이 간단하게 동작하는 방식으로 몇초, 혹은 몇분, 몇시간 단위로 작동하고 싶을때 사용합니다.
<Simple type setting>
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:context="http://www.springframework.org/schema/context"
	   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
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context-2.5.xsd">
	<!-- 하나.주기적으로 실행될 클래스 설정 -->
	<!-- property name은 jobClass로 fix, value는 사용자가 작성한 class 파일 위치 -->
	<bean id="simpleQuartzJob" class="org.springframework.scheduling.quartz.JobDetailBean">
		<property name="jobClass" value="net.test.quartz.SimpleQuartzJob"/>
	</bean>

	<!-- 둘.스케줄러의 interval time 설정 -->
	<!-- 쿼츠에는 아래와 같이 몇초에 한번씩 돌게 하는 Simple type 과 -->
	<!-- 무슨 요일 몇시에 한번씩 돌게 하는 날짜로 지정하는 Cron type 이 있다. -->
	<!-- 현재는 Simple type으로 세팅 -->
	<!-- jobDetail은 위에서 설정한 실제 동작할 클래스 id를 적어준다 -->
	<!-- startDelay는 서버 시작후 몇초 뒤에 시작할지 세팅(ms 단위)  -->
	<!-- repeatInterval은 몇 초에 한번씩 실행될 건지 세팅(ms 단위: 현재 1초) -->
	<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
		<property name="jobDetail" ref="simpleQuartzJob"/>
		<property name="startDelay" value="1000"/>
		<property name="repeatInterval" value="1000"/>
	</bean>
	<!--셋. 실제 동작하게끔 설정 -->
	<!--ref bean은 위에서 설정한 interval time 아이디를 넣어주면 됨  -->
	<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
		<property name="triggers">
			<list>
				<ref bean="simpleTrigger"/>
			</list>
		</property>
		<!-- Quartz 실행시 세팅 -->
		<property name="quartzProperties">
			<props>
				<prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop>
    			<prop key="org.quartz.threadPool.threadCount">5</prop>
    			<prop key="org.quartz.threadPool.threadPriority">4</prop>
    			<prop key="org.quartz.jobStore.class">org.quartz.simpl.RAMJobStore</prop>
    			<prop key="org.quartz.jobStore.misfireThreshold">60000</prop>
			</props>
		</property>
	</bean>
</beans>

-Cron : linux 의 Cron tab 과 같은 역할을 하는 타입니다. 즉 몇월, 몇일 몇시에 동작하게 하고 싶으면 Cron type을 사용하시면 됩니다.
<Cron type setting>
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:context="http://www.springframework.org/schema/context"
	   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
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context-2.5.xsd">
	<!--하나. 주기적으로 실행될 클래스 설정 -->
	<bean id="cronQuartzJob" class="org.springframework.scheduling.quartz.JobDetailBean">
		<property name="jobClass" value="net.test.quartz.CronQuartzJob"/>
	</bean>
	
	<!--둘. 스케줄러의 interval time 설정-->
	<!--cronExpression을 통해서 스캐줄러 주기를 설정한다. -->
	<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
		<property name="jobDetail" ref="cronQuartzJob"/>
		<property name="cronExpression" value="0/1 * * * * ?"/>
	</bean>
	
	<!--셋. 실제 동작하게끔 설정 -->
	<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
		<property name="triggers">
			<list>
				<ref bean="cronTrigger"/>
			</list>
		</property>
		<property name="quartzProperties">
			<props>
				<prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop>
    			<prop key="org.quartz.threadPool.threadCount">5</prop>
    			<prop key="org.quartz.threadPool.threadPriority">4</prop>
    			<prop key="org.quartz.jobStore.class">org.quartz.simpl.RAMJobStore</prop>
    			<prop key="org.quartz.jobStore.misfireThreshold">60000</prop>
			</props>
		</property>
	</bean>
</beans>


Cron type을 사용하려면 CronExpression을 알아야 합니다.

*Cron Expression
cron expression의 각각의 필드는 다음을 나타낸다.(왼쪽 -> 오른쪽 순)
 필드 이름  허용 값  허용된 특수 문자
 Seconds  0 ~ 59  , - * /
 Minutes  0 ~ 59  , - * /
 Hours  0 ~ 23  , - * /
 Day-of-month  1 ~ 31  , - * ? / L W
 Month  1 ~12 or JAN ~ DEC   , - * /
 Day-Of-Week  1 ~ 7 or SUN-SAT  , - * ? / L #
 Year (optional)  empty, 1970 ~ 2099  , - * /

Cron Expression 의 특수문자
'*' : 모든 수를 나타냄. 분의 위치에 * 설정하면 "매 분 마다" 라는 뜻.
'?' : day-of-month 와 day-of-week 필드에서만 사용가능. 특별한 값이 없음을 나타낸다.
'-' : "10-12" 과 같이 기간을 설정한다. 시간 필드에 "10-12" 이라 입력하면 "10, 11, 12시에 동작하도록 설정" 이란 뜻.
',' : "MON,WED,FRI"와 같이 특정 시간을 설정할 때 사용한다. "MON,WED,FRI" 이면 " '월,수,금' 에만 동작" 이란 뜻.
'/' : 증가를 표현합니다. 예를 들어 초 단위에 "0/15"로 세팅 되어 있다면 "0초 부터 시작하여 15초 이후에 동작" 이란 뜻.
'L' : day-of-month 와 day-of-week 필드에만 사용하며 마지막날을 나타냅. 만약 day-of-month 에 "L" 로 되어 있다면 이번 달의 마지막에 실행하겠다는 것을 나타냄.
'W' : day-of-month 필드에만 사용되며, 주어진 기간에 가장 가까운 평일(월~금)을 나타낸다. 만약 "15W" 이고 이번 달의 15일이 토요일이라면 가장가까운 14일 금요일날 실행된다. 또 15일이 일요일이라면 가장 가까운 평일인 16일 월요일에 실행되게 된다. 만약 15일이 화요일이라면 화요일인 15일에 수행된다.
"LW" : L과 W를 결합하여 사용할 수 있으며 "LW"는 "이번달 마지막 평일"을 나타냄
"#" : day-of-week에 사용된다. "6#3" 이면 3(3)번째 주 금요일(6) 이란 뜻이된다.1은 일요일 ~ 7은 토요일 

 Expression  Meaning
 "0 0 12 * * ?"  매일 12시에 실행
 "0 15 10 ? * *"  매일 10시 15분에 실행
 "0 15 10 * * ?"  매일 10시 15분에 실행
 "0 15 10 * * ? *"  매일 10시 15분에 실행
 "0 15 10 * * ?  2010"   2010년 동안 매일 10시 15분에 실행
 "0 * 14 * * ?"  매일 14시에서 시작해서 14:59분 에 끝남
 "0 0/5 14 * * ?"  매일 14시에 시작하여 5분 간격으로 실행되며 14:55분에 끝남
 "0 0/5 14,18 * * ?"  매일 14시에 시작하여 5분 간격으로 실행되며 14:55분에 끝나고, 매일 18시에 시작하여 5분간격으로 실행되며 18:55분에 끝난다.
 "0 0-5 14 * * ?"  매일 14시에 시작하여 14:05 분에 끝난다.

* 시간에 맞춰 돌아가는 스케줄러에서 다른 클래스를 사용하고 싶을 때는 다음과 같이 설정합니다.
<!-- 스프링 DI : 사용할 Service 객체를 생성 -->
<bean id="quartzJobService" class="net.test.quartz.service.impl.QuartzJobServiceImpl"/>

<bean id="simpleQuartzJob" class="org.springframework.scheduling.quartz.JobDetailBean">
	<property name="jobClass" value="net.test.quartz.SimpleQuartzJob"/>
	<!-- 사용하고자 하는 class의 bean id를 등록 -->
	<property name="jobDataAsMap">
		<map>
			<entry key="quartzJobService">
				<ref local="quartzJobService"/>
			</entry>
		</map>
	</property>
</bean>

 *두가지 스케줄러를 동시에 실행 시킬때
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
	<property name="triggers">
		<!--트리거를 두개 생성후 아래와 같이 세팅 -->
		<list>
			<ref bean="simpleTrigger"/>
			<ref bean="cronTrigger"/>
		</list>
	</property>
	<property name="quartzProperties">
		<props>
			<prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop>
    			<prop key="org.quartz.threadPool.threadCount">5</prop>
    			<prop key="org.quartz.threadPool.threadPriority">4</prop>
    			<prop key="org.quartz.jobStore.class">org.quartz.simpl.RAMJobStore</prop>
    			<prop key="org.quartz.jobStore.misfireThreshold">60000</prop>
		</props>
	</property>
</bean>


7) 실제 작동할 Class파일 생성
package net.test.quartz;

import net.test.quartz.service.QuartzJobService;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

public class SimpleQuartzJob extends QuartzJobBean{
	//실행될 클래스는 꼭 QuartzJobBean을 상속받아야 되며 
	//executeInternal method를 override 하면 자동으로 이 메소드가 실행

	//Spring의 DI를 사용하여 Service객체를 setting
	//DI를 사용하지 않는다면 필요 없는 부분
	private QuartzJobService quartzJobService;
	public void setQuartzJobService(QuartzJobService quartzJobService) {
		this.quartzJobService = quartzJobService;
	}


	@Override
	protected void executeInternal(JobExecutionContext ex)throws JobExecutionException {
		quartzJobService.printLog();
	}
	
}


위와 같은 방식으로 Spring과 Quartz를 사용하여 스케줄러를 사용할 수 있습니다.

함께 업로드 하는 파일은 제가 직접 작업한 프로젝트이구요. 함께 확인 해 보시면 쉽게 쿼츠를 사용하실수 있으실 겁니다.~^^

SpringQuartz.war