[Jmlee] Spring Framework, Tomcat, myBatis, Hot Deploy, Hot Swapping ***
▦ 프로그램/└ Spring 2018. 4. 18. 16:35Tomcat configuration
server.xml
<?xml version='1.0' encoding='utf-8'?>
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<Connector ... />
<Engine name="Catalina" defaultHost="localhost">
<Realm ... />
<Host name="localhost"
appBase="webapps"
unpackWARs="true"
autoDeploy="false"
>
...
</Service>
</Server>
Eclipse configuration
C:/x/y/z/ 경로에 springloaded-1.2.5.RELEASE.jar 다운로드 (https://github.com/spring-projects/spring-loaded)
Window > Preferences > Tomcat > JVM Settings - Append to JVM Parameters 에 아래 옵션 추가
-javaagent:C:/x/y/z/springloaded-1.2.5.RELEASE.jar -noverify
Spring configuration
sql-map-config-xxx.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
...
</settings>
<typeAliases>
...
</typeAliases>
<!-- context-mapper.xml 설정파일로 이동
<mappers>
...
</mappers>
-->
</configuration>
context-mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<bean id="sqlSessionFactory" class="x.y.z.RefreshableSqlSessionFactoryBean">
<property name="configLocation" value="classpath:/egovframework/sqlmap/config/sql-map-config-xxx.xml" />
<property name="mapperLocations" value="classpath:/egovframework/sqlmap/xxx/**/*Mapper*.xml" />
<property name="interval" value="1000" />
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate" destroy-method="clearCache">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
</beans>
RefreshableSqlSessionFactoryBean.java (https://gist.github.com/sbcoba/a51a66a64d3441a88558)
package x.y.z;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.core.io.Resource;
/**
* mybatis mapper 자동 감지 후 자동으로 서버 재시작이 필요 없이 반영
*
* @author sbcoba
*/
public class RefreshableSqlSessionFactoryBean extends SqlSessionFactoryBean implements DisposableBean {
private static final Logger logger = LoggerFactory.getLogger(RefreshableSqlSessionFactoryBean.class);
private SqlSessionFactory proxy;
private int interval = 500;
private Timer timer;
private TimerTask task;
private Resource[] mapperLocations;
/**
* 파일 감시 쓰레드가 실행중인지 여부.
*/
private boolean running = false;
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();
@Override
public void setMapperLocations(Resource[] mapperLocations) {
super.setMapperLocations(mapperLocations);
this.mapperLocations = mapperLocations;
}
public void setInterval(int interval) {
this.interval = interval;
}
/**
* @throws Exception
*/
public void refresh() throws Exception {
if (logger.isInfoEnabled()) {
logger.info("refreshing sqlMapClient.");
}
w.lock();
try {
super.afterPropertiesSet();
} finally {
w.unlock();
}
}
/**
* 싱글톤 멤버로 SqlMapClient 원본 대신 프록시로 설정하도록 오버라이드.
*/
@Override
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
setRefreshable();
}
private void setRefreshable() {
proxy = (SqlSessionFactory) Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSessionFactory.class },
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// logger.debug("method.getName() : " + method.getName());
return method.invoke(getParentObject(), args);
}
});
task = new TimerTask() {
private Map<Resource, Long> map = new HashMap<Resource, Long>();
@Override
public void run() {
if (isModified()) {
try {
refresh();
} catch (Exception e) {
logger.error("caught exception", e);
}
}
}
private boolean isModified() {
boolean retVal = false;
if (mapperLocations != null) {
for (int i = 0; i < mapperLocations.length; i++) {
Resource mappingLocation = mapperLocations[i];
retVal |= findModifiedResource(mappingLocation);
}
}
return retVal;
}
private boolean findModifiedResource(Resource resource) {
boolean retVal = false;
List<String> modifiedResources = new ArrayList<String>();
try {
long modified = resource.lastModified();
if (map.containsKey(resource)) {
long lastModified = ((Long) map.get(resource)).longValue();
if (lastModified != modified) {
map.put(resource, new Long(modified));
modifiedResources.add(resource.getDescription());
retVal = true;
}
} else {
map.put(resource, new Long(modified));
}
} catch (IOException e) {
logger.error("caught exception", e);
}
if (retVal) {
if (logger.isInfoEnabled()) {
logger.info("modified files : " + modifiedResources);
}
}
return retVal;
}
};
timer = new Timer(true);
resetInterval();
}
private Object getParentObject() throws Exception {
r.lock();
try {
return super.getObject();
} finally {
r.unlock();
}
}
@Override
public SqlSessionFactory getObject() {
return this.proxy;
}
@Override
public Class<? extends SqlSessionFactory> getObjectType() {
return (this.proxy != null ? this.proxy.getClass() : SqlSessionFactory.class);
}
@Override
public boolean isSingleton() {
return true;
}
public void setCheckInterval(int ms) {
interval = ms;
if (timer != null) {
resetInterval();
}
}
private void resetInterval() {
if (running) {
timer.cancel();
running = false;
}
if (interval > 0) {
timer.schedule(task, 0, interval);
running = true;
}
}
public void destroy() throws Exception {
timer.cancel();
}
}
'▦ 프로그램 > └ Spring' 카테고리의 다른 글
[Jmlee] MyBatis - Custom TypeHandler로 Array Column 처리 (0) | 2018.03.08 |
---|---|
[Jmlee] Spring ApplicationContext 가 여러개 생성되는 문제 (스케줄러 2번씩 실행되는 이슈, Quartz Twice) (0) | 2016.03.16 |