티스토리 뷰

spring 프로젝트에서 springboot 프로젝트로 migration 하기 (1) - pom.xml

spring 프로젝트에서 springboot 프로젝트로 migration 하기 (2) - web.xml

spring 프로젝트에서 springboot 프로젝트로 migration 하기 (3) - 파일 추가 및 변경

spring 프로젝트에서 springboot 프로젝트로 migration 하기 (4) - logback

spring 프로젝트에서 springboot 프로젝트로 migration 하기 (5) - 빌드 및 실행

 

오랜만에 Springboot migration 글을 쓰는것 같다. 지난번에 시간이 없어서 이건 못하고 넘어갔었는데 짬이 나서 XML config to JAVA config도 진행을 할 수 있었다. 허접한 완벽주의자라 Springboot 프로젝트로 다 변환을 했음에도 이 부분을 안하고 그냥 import 하는 형식으로 남겨놓아서 찜찜했는데 속이 시원한것 같다. 

 

spring의 applicationContext, web-applicationContext를 모두 JAVA Config변환하는 작업이라 시간이 조금 걸렸다. 그리고 앞으로 써나갈 글의 양도 상당하다. (스압주의)


Springboot Application.java (main class)

Application.java (AS-IS)

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.SpringBootWebSecurityConfiguration;
import org.springframework.context.annotation.ImportResource;

@SpringBootApplication
@EnableAutoConfiguration(exclude = {org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration.class, SpringBootWebSecurityConfiguration.class})
@ImportResource(value= {"classpath:spring/application-context/common-context.xml", 
			"classpath:spring/application-context/dataaccess-context.xml",
			"classpath:spring/application-context/security-context.xml",
			"classpath:spring/web-application-context/dispatcher-context.xml"
})
public class Application {
    public static void main(String[] args) {
    	SpringApplication application = new SpringApplication(Application.class);
    	application.run(args);
    }
}

일단 지난번에 이처럼 @ImportResource로 기존의 xml 파일을 그대로 사용하는 방식으로 migration을 진행했었다. 이제 여기에서 하나씩 context 파일을 제거해가며 변환을 시도해야 한다. 한번에 context 파일을 다 제거하고 짜잔 성공하면 좋겠지만 현실적으로 불가능하다. 어디에서 문제가 나는지 빠른 파악을 하기 위해서는 하나씩 변환을 하는것이 좋다. 필자는 위에 import 된 순서대로 common > dataaccess > security > dispatcher 순으로 제거해가며 변환을 할 계획이다. 

 

Application.java (TO-BE)

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.SpringBootWebSecurityConfiguration;
import org.springframework.context.annotation.ImportResource;

@SpringBootApplication
@EnableAutoConfiguration(exclude = {org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration.class, SpringBootWebSecurityConfiguration.class})
public class Application {
    public static void main(String[] args) {
    	SpringApplication application = new SpringApplication(Application.class);
    	application.run(args);
    }
}

그래서 궁극적으로는 위와 같이 모든 context 파일을 java config로 전환하여 Application.java 파일에서는 무엇도 import 하지 않고 어플리케이션을 기동할 수 있어야 한다. 

 


applicationContext (common-context.xml)

common-context.xml

    <!-- application context annotation bean loading -->
    <context:component-scan base-package="oing">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    
    <!-- message -->
<!--     <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> -->
<!--         <property name="defaultEncoding" value="UTF-8"/> -->
<!--         <property name="basenames"> -->
<!--             <list> -->
<!--                 <value>classpath:/messages/message/common</value> -->
<!--                 <value>classpath:/messages/message/project</value> -->
<!--             </list> -->
<!--         </property> -->
<!--         <property name="cacheSeconds" value="60"/> -->
<!--     </bean>     -->
    
    <bean id="messageSource" class="oing.common.core.message.DBMessageSource">
        <property name="dataSource" ref="dataSource" />
        <property name="messageQuery" value="SELECT MESSAGE, LOCALE, CODE FROM TB_MESSAGES"/>
    </bean>

    <bean id="messageSourceAccessor" class="org.springframework.context.support.MessageSourceAccessor">
        <constructor-arg ref="messageSource" />
    </bean>    
    
    <!-- locale -->
    <bean id="localeResolver" class="org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver" />

    <bean id="exceptionManager" class="oing.common.core.exception.ExceptionManager"/>
    
    <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
        <property name="requestFactory"> 
            <bean class="org.springframework.http.client.SimpleClientHttpRequestFactory"> 
                <property name="connectTimeout" value="5000" /> 
                <property name="readTimeout" value="5000" /> 
            </bean> 
        </property> 
        <property name="messageConverters"> 
            <list> 
                <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" /> 
            </list> 
        </property>
    </bean>

일단 common-context.xml 라고 하는 applicationContext 인데 프로젝트 전반적으로 공통으로 사용은 되지만 딱히 어떤 그룹에 포함이 되지 않는 설정들을 모아 놓은 것이라고 생각하면 된다. 

 

맨 위에 있는 component-scan 과 같은 경우는 springboot를 사용하면 Application.java 파일의 위치만 최상위에 있으면 별도로 설정을 해주지 않아도 된다. 그리고 예전처럼 applicationContext, web-applicationContext를 별도로 구분하지 않는 추세이므로 applicationContext에서는 @Servcie, @Repository, web-applicationContext에서는 @Controller를 include 시키는건 거의 하지 않는다. 

 

그리고 프로젝트마다 필수적으로 들어가있는 message 설정이 있다. 기본적으로 제공되는 file message 외에 필자의 프로젝트에는 customizing한 DB message를 사용하여 이에 대한 설정이 들어가 있다. 

 

또한 localeResolver, exceptionManager, restTemplate 등의 설정이 이 common-context.xml 에 들어있다. 

 

이것을 JAVA Config로 변환을 하면 다음과 같은 모습이 된다.

 

CommonConfig.java

import java.util.ArrayList;
import java.util.List;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;

import oing.common.core.exception.ExceptionManager;
import oing.common.core.message.DBMessageSource;

@Configuration
public class CommonConfig {
    
    private static final String MESSAGE_QUERY = "SELECT MESSAGE, LOCALE, CODE FROM TB_MESSAGES";
    
    @Autowired
    private DataSource dataSource;

    //DB message Source
    @Bean
    public DBMessageSource messageSource() throws Exception {
        DBMessageSource messageSource = new DBMessageSource();
        messageSource.setDataSource(dataSource);
        messageSource.setMessageQuery(MESSAGE_QUERY);
        return messageSource;
    }
    
    //file message Source
//    @Bean
//    public ReloadableResourceBundleMessageSource messageSource() throws Exception{
//        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
//        messageSource.setBasenames("classpath:/messages/message/common"
//                , "classpath:/messages/message/project");
//        messageSource.setDefaultEncoding("UTF-8");
//        messageSource.setCacheSeconds(60);
//        messageSource.setUseCodeAsDefaultMessage(true);
//        return messageSource;
//    }
    
    @Bean
    public MessageSourceAccessor messageSourceAccessor() throws Exception {
        return new MessageSourceAccessor(messageSource());
    }
    
    @Bean
    public AcceptHeaderLocaleResolver localeResolver() {
        return new AcceptHeaderLocaleResolver();
    }
    
    @Bean
    public ExceptionManager exceptionManager() {
        return new ExceptionManager();
    }
    
    @Bean
    public RestTemplate restTemplate(){
        RestTemplate restTemplate = new RestTemplate();
        
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setConnectTimeout(5000);
        requestFactory.setReadTimeout(5000);
        restTemplate.setRequestFactory(requestFactory);
        
        List<HttpMessageConverter<?>> converterList = new ArrayList<HttpMessageConverter<?>>();
        HttpMessageConverter<?> jacksonConverter = new MappingJackson2HttpMessageConverter();
        converterList.add(jacksonConverter);
        restTemplate.setMessageConverters(converterList);
        
        return restTemplate;
    }
}

JAVA Config 로 변환을 할때 JAVA class에는 @Configuration 을 달아주자. 그리고 이 클래스는 별도로 component-scan을 하지 않으므로 반드시 Application.java 하위 디렉토리에 위치하고 있어야 한다. 그래야지 이 클래스를 scan 할수 있기 때문이다. 

 

몇개 직접 변환을 해본다면 알수 있겠지만 xml config에서 java config로 변환을 하는것은 엄청나게 어려운 일이 아니다. 더군다나 xml 파일에 그냥 bean 으로 정의되어 있는 것들은 참 간단하게 변환을 할 수 있다. 

 

이런식으로 변환해서 돌려보고 잘 돌아간다면 common-context.xml 파일은 삭제하고 Application.java import 하는 부분에서도 빼주도록 하자. 


applicationContext (dataaccess-context.xml)

dataaccess-context.xml

    <!-- myBatis -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:/mybatis/mybatis-config.xml" />
        <property name="mapperLocations">
            <list>
                <value>classpath*:oing/**/*.sql</value>
            </list>
        </property>
    </bean>
    
    <!-- commonDao -->
    <bean id="commonDao" class="oing.common.dataaccess.CommonDao">
        <property name="sqlSessionFactory" ref="sqlSessionFactory" />
        <property name="defaultMaxRows" value="${defaultMaxRows}" />
    </bean>
    
    <!-- transaction -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="create*" rollback-for="Exception" />
            <tx:method name="insert*" rollback-for="Exception" />
            <tx:method name="update*" rollback-for="Exception" />
            <tx:method name="delete*" rollback-for="Exception" />
            <tx:method name="save*" rollback-for="Exception" />
            <tx:method name="*" read-only="true" propagation="SUPPORTS"/>
        </tx:attributes>
    </tx:advice>

    <!-- xml transaction -->
    <aop:config proxy-target-class="true">
        <aop:pointcut id="transactionPointcut" expression="execution(* *..service.*Service.*(..))" />
        <aop:advisor advice-ref="txAdvice" pointcut-ref="transactionPointcut"/>
    </aop:config>

    <!-- datasource -->
    <bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="jdbcUrl" value="jdbc:oracle:thin:@xx.xx.xx.xx:1521:OING"/>
        <property name="username" value="oing"/>
        <property name="password" value="daddy"/>
    </bean>
    <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
        <constructor-arg ref="hikariConfig" />
    </bean> 

dataaccess-context.xml 은 DB와 관련된 설정들을 모아 놓은 곳이다. 

myBatis 사용을 위한 sqlSessionFactory, 하나의 DAO를 사용하기 위한 commonDao, transaction 설정, 그리고 datasource 설정까지 이곳에 모두 있다. 모두 일반적으로 사용하고 있는 모습이고 이를 JAVA Config 로 어떻게 변환을 하는지 보자. 

 

DataaccessConfig.java

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionInterceptor;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

import oing.common.dataaccess.CommonDao;

@Configuration
@EnableTransactionManagement(order = 1, proxyTargetClass = true)
public class DataaccessConfig {
    
    @Value("classpath*:oing/**/*.sql")
    private Resource[] mapperLocations;
    
    @Value("classpath:mybatis/mybatis-config.xml")
    private Resource configLocation;
    
    @Value("${defaultMaxRows}")
    private String defaultMaxRows;
    
    @Value("${transaction.read.timeout}")
    private int transactionReadTimeout = 20;
    
    @Value("${transaction.write.timeout}")
    private int transactionWriteTimeout = 20;
    
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        sessionFactory.setConfigLocation(configLocation);
        sessionFactory.setMapperLocations(mapperLocations);
        return sessionFactory.getObject();
    }
    
    @Bean
    public CommonDao commonDao() throws Exception {
        CommonDao commonDao = new CommonDao();
        commonDao.setSqlSessionFactory(sqlSessionFactory());
        commonDao.setDefaultMaxRows(Integer.parseInt(defaultMaxRows));
        return commonDao;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }
    
    @Bean
    public TransactionInterceptor txAdvice() {
        TransactionInterceptor txAdvice = new TransactionInterceptor();
        Properties txAttributes = new Properties();
        List<RollbackRuleAttribute> rollbackRules = new ArrayList<RollbackRuleAttribute>();
        rollbackRules.add(new RollbackRuleAttribute(Exception.class));
        
        DefaultTransactionAttribute readOnlyAttribute = new DefaultTransactionAttribute(
                TransactionDefinition.PROPAGATION_SUPPORTS);
        readOnlyAttribute.setReadOnly(true);
        readOnlyAttribute.setTimeout(transactionReadTimeout);

        RuleBasedTransactionAttribute writeAttribute = new RuleBasedTransactionAttribute(
                TransactionDefinition.PROPAGATION_REQUIRED, rollbackRules);
        writeAttribute.setTimeout(transactionWriteTimeout);
 
        String readOnlyTransactionAttributesDefinition = readOnlyAttribute.toString();
        String writeTransactionAttributesDefinition = writeAttribute.toString();

        txAttributes.setProperty("create*", writeTransactionAttributesDefinition);
        txAttributes.setProperty("insert*", writeTransactionAttributesDefinition);
        txAttributes.setProperty("update*", writeTransactionAttributesDefinition);
        txAttributes.setProperty("delete*", writeTransactionAttributesDefinition);
        txAttributes.setProperty("save*", writeTransactionAttributesDefinition);
        txAttributes.setProperty("*", readOnlyTransactionAttributesDefinition);

        txAdvice.setTransactionAttributes(txAttributes);
        txAdvice.setTransactionManager(transactionManager());
        return txAdvice;

    }
    
    @Bean
    public Advisor txAdviceAdvisor() {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* *..service.*Service.*(..))");
        return new DefaultPointcutAdvisor(pointcut, txAdvice());
    }
    
    @Bean(destroyMethod = "close")
    public DataSource dataSource(){
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setDriverClassName("oracle.jdbc.driver.OracleDriver");
        hikariConfig.setJdbcUrl("jdbc:oracle:thin:@xx.xx.xx.xx:1521:OING");
        hikariConfig.setUsername("oing");
        hikariConfig.setPassword("daddy");
        HikariDataSource dataSource = new HikariDataSource(hikariConfig);
        return dataSource;
    }
}

마찬가지로 @Configuration 을 클래스 위에 달아서 이것은 설정이요~ 라고 명시를 해줬다. 그리고 xml 설정에서는 aop config 에 있던 proxy-target-class를 @EnableTransactionManagement(order = 1, proxyTargetClass = true) 로 추가를 해 주었다. 그리고 하드코딩되어 있던 여러 값들을 모두 외부 환경변수로 빼서 그것으로부터 가져올 수 있도록 변경이 있었다. 이곳도 변환을 하는데 있어서 딱히 주의할것은 없다. 위의 JAVA 설정을 본인의 프로젝트에 맞게 가지고 가면 큰 문제 없이 변환이 가능하다. 


applicationContext (security-context.xml)

security-context.xml

    <security:http pattern="/css/**" security="none"/>
    <security:http pattern="/images/**" security="none"/>
    <security:http pattern="/js/**" security="none"/>
    <security:http pattern="favicon.ico" security="none"/>
    
    <security:http auto-config="true" use-expressions="true">
        <security:intercept-url pattern="/loginPage.do" access="permitAll" />
        <security:intercept-url pattern="/sso.do" access="permitAll" />
        <security:intercept-url pattern="/main/main.do" access="permitAll" />
        <security:intercept-url pattern="/**" access="isAuthenticated()" />
        <security:form-login login-page="/loginPage.do"
                    login-processing-url="/login"
                    username-parameter="username"
                    password-parameter="password"
                    authentication-success-handler-ref="loginSuccessHandler"
                    authentication-failure-handler-ref="loginFailureHandler"
                    always-use-default-target="true"
                    />
        <security:csrf disabled="true"/>
                
        <security:logout logout-url="/logout" success-handler-ref="customLogoutSuccessHandler" />        

        <security:headers>
            <security:frame-options policy="SAMEORIGIN"/>
            <security:hsts disabled="true"/>
            <security:xss-protection block="false"/>
        </security:headers>    
		
        <security:custom-filter before="CHANNEL_FILTER" ref="environmentSetupFilter"/>
    </security:http>    
	
    <security:authentication-manager>
        <security:authentication-provider ref="customAuthenticationProvider" />
    </security:authentication-manager>
       
    <bean id="customLogoutSuccessHandler" class="oing.login.CustomLogoutSuccessHandler"/>  
       
    <bean id="customAuthenticationProvider" class="oing.login.CustomAuthenticationProvider"/>    
    
    <beans:bean id="loginSuccessHandler" class="oing.login.LoginSuccessHandler">
        <beans:property name="defaultTargetUrl" value="/main/main.do" />
    </beans:bean>     
    
    <beans:bean id="loginFailureHandler" class="oing.login.LoginFailureHandler"> 
        <property name="defaultFailureUrl" value="/loginPage.do" /> 
    </beans:bean> 

이것도 일반적으로 사용하고 있던 spring security의 context이다. 필자의 환경과 다른 환경이 크게 다르지는 않을 것이다. 맨 위에서부터 차례대로 설명을 하자면 맨 위의 설정은 static resource에 대한 제외 설정이다. 

그 아래 5번째 블럭부터는 허용 규칙에 대한 설정이다. 또한 로그인 페이지는 어떤건지, 로그인을 성공하면 어디로 실패하면 어디로 가는지, csrf, xss 등의 설정은 어떻게 할건지에 대한 설정들이다. 필자는 form login 방식이 아닌 authentication-manager를 이용한 인증방식을 사용할 것이고 위와 같이 설정을 해주면 된다. 

자 그럼 JAVA Config로 변환된 모습을 보자. 

 

SecurityConfig.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import oing.web.filter.EnvironmentSetupFilter;
import oing.login.CustomAuthenticationProvider;
import oing.login.CustomLogoutSuccessHandler;
import oing.login.LoginFailureHandler;
import oing.login.LoginSuccessHandler;

@Configuration
@EnableWebSecurity
public class SecurityConfig  extends WebSecurityConfigurerAdapter {
    
    @Autowired
    private EnvironmentSetupFilter environmentSetupFilter;
    
    @Autowired
    private CustomAuthenticationProvider customAuthenticationProvider;
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception{
        auth.authenticationProvider(customAuthenticationProvider);
    }
    
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/css/**");
        web.ignoring().antMatchers("/js/**");
        web.ignoring().antMatchers("/images/**");
        web.ignoring().antMatchers("/favicon.ico");
    }
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/loginPage.do").permitAll()
                .antMatchers("/sso.do").permitAll()
                .antMatchers("/main/main.do").permitAll()
                .anyRequest().authenticated();

        http.formLogin()
                .loginPage("/loginPage.do")
                .loginProcessingUrl("/login")
                .usernameParameter("username")
                .passwordParameter("password")
                .successHandler(loginSuccessHandler())
                .failureHandler(loginFailureHandler());
        
        http.csrf().disable();

        http.logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .logoutSuccessHandler(customLogoutSuccessHandler());
        
        http.headers()
                .frameOptions().sameOrigin()
                .httpStrictTransportSecurity().disable()
                .xssProtection().block(false);
        
        http.cors();
        
        http.addFilterBefore(environmentSetupFilter, ChannelProcessingFilter.class);
        
        http.httpBasic();
    }
    
    @Bean 
    public LoginSuccessHandler loginSuccessHandler() {
        LoginSuccessHandler handler = new LoginSuccessHandler();
        handler.setDefaultTargetUrl("/main/main.do");
        return handler;
    }
    
    @Bean 
    public LoginFailureHandler loginFailureHandler() {
        LoginFailureHandler handler = new LoginFailureHandler();
        handler.setDefaultFailureUrl("/loginPage.do");
        return handler;
    }
    
    @Bean 
    public CustomLogoutSuccessHandler customLogoutSuccessHandler() {
        return new CustomLogoutSuccessHandler();
    }
}

이것은 JAVA Config로 전환을 하는데 조금 애를 먹었다. 다른 부분은 말썽이 없었는데 static resource 설정하는 부분이 문제가 있어서 수정을 하는데 오래 걸렸다. 그때 당시의 증상은 form login 방식이 아닌 AuthenticationProvider를 사용한 로그인이었는데 로그인을 하면 AuthenticationProvider를 지나지 않고 404가 발생을 하는 것이었다. 전혀 static resource와 문제가 있을거라고는 생각지 않고 문제를 찾으려다보니 어려움이 있었다. 

 

주의해야 할점은 클래스 위에 @EnableWebSecurity 를 달아주고 클래스는 WebSecurityConfigurerAdapter를 extends 해서 만들어야 한다. 그리고 AuthenticationProvider 방식을 사용하기 위해서는 WebSecurityConfigurerAdapter의 configure(AuthenticationManagerBuilder auth) 를 @Override 해서 사용해야 한다.

그 외에 static resource를 security none 하기 위해 위와 같이 web.ignoring() 에서 잘 처리를 해줘야한다. 

CustomFilter에 대한 처리는 이 글을 참조해서 Class로 변환된 값을 사용하도록 하자. 


web-applicationContext (dispatcher-context.xml)

dispatcher-context.xml

<!-- web application context annotation bean loading -->
    <context:component-scan base-package="oing">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
    
    <!-- logging -->
    <bean id="securityRulesInterceptor" class="oing.login.SecurityRulesInterceptor" />
    
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
        <property name="useSuffixPatternMatch" value="true" />
        <property name="alwaysUseFullPath" value="true" />
        <property name="order" value="0" />
        <property name="interceptors">
            <list>
                <ref bean="securityRulesInterceptor"/>
            </list>
        </property>
    </bean>
    
    <bean class="oing.web.mvc.CustomRequestMappingHandlerAdapter">
        <property name="customeArgumentResolver" ref="customeArgumentResolver"/>
        <property name="customeReturnValueHandler" ref="customeReturnValueHandler"/>
    </bean>

    <bean id="customeArgumentResolver" class="oing.web.mvc.CustomeArgumentResolver"/>
    <bean id="customeReturnValueHandler" class="oing.web.mvc.CustomeReturnValueHandler"/>
    
    <!-- View Resolver -->
     <bean id="beanNameViewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver" p:order="0" />
    
    <bean id="tilesViewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver" p:order="1">
        <property name="viewClass" value="org.springframework.web.servlet.view.tiles3.TilesView" />
    </bean>

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:order="2">
        <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView" />
        <property name="prefix" value="${spring.mvc.view.prefix}" />
        <property name="suffix" value=".jsp" />
    </bean>
    
    <!-- Exception Resolver -->
    <bean class="oing.web.exception.resolver.AjaxExceptionResolver" p:order="1">
        <property name="exceptionAttribute" value="exception"/>
        <property name="headerName" value="X-Requested-With" />
        <property name="headerValues" value="XMLHttpRequest" />
        <property name="errorView" value="ajaxErrorView"/>
    </bean>

    <bean class="oing.web.exception.resolver.PageExceptionResolver" p:order="2">
        <property name="exceptionAttribute" value="exception"/>
        <property name="exceptionMappings">
            <props>
                <prop key="oing.common.exception.UserException">system/exception/exceptionPage</prop>
                <prop key="oing.common.exception.SystemException">system/exception/serverExceptionPage</prop>
            </props>
        </property>
        <property name="defaultErrorView" value="system/exception/exceptionPage" />
    </bean>
    
    <!-- ajaxErrorView설정 -->
    <bean id="ajaxErrorView" class="oing.web.view.AjaxExceptionView" >
        <property name="userData" value="userData" />
    </bean>
     
    <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer" >
        <property name="definitions">
            <list>
                <value>${spring.mvc.view.prefix}/system/layout-definition/tiles-layout.xml</value>
            </list>
        </property>
    </bean>

    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
    
    <mvc:interceptors>
        <bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
            <property name="paramName" value="lang"/>
        </bean>
    </mvc:interceptors>

이 servlet context를 변환하는데 가장 오랜 시간이 걸렸다. 단순히 bean으로 변환을 하는것이 아닌 다른 규칙들이 있었기 때문이다. 이런 저런 기능들이 다 있어서 좀 정리할건 정리하고 빼도 되지만 이런 저런것도 한번 다 변환을 해보자는것이 목표였기에 이것 그대로 변환을 할 계획이다. 

 

component-scan은 아까도 언급을 했지만 더이상 wac와 ac 구분을 해서 사용하지 않는다. 따라서 없애도 되는 설정이다. RequestMappingHandlerMapping, RequestMappingHandlerAdapter 부터해서 viewResolver, exceptionResolver, view, interceptor 등등 MVC에 필요한 기능들은 거의 모두 들어있다고 보면 된다. 

 

WebConfig.java

import java.util.Properties;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextListener;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.view.BeanNameViewResolver;
import org.springframework.web.servlet.view.InternalResourceView;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.UrlBasedViewResolver;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesView;

import oing.web.exception.resolver.AjaxExceptionResolver;
import oing.web.exception.resolver.PageExceptionResolver;
import oing.web.mvc.CustomRequestMappingHandlerAdapter;
import oing.web.mvc.CustomArgumentResolver;
import oing.web.mvc.CustomReturnValueHandler;
import oing.web.view.AjaxExceptionView;
import oing.login.SecurityRulesInterceptor;

@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
   
   @Value("${spring.mvc.view.prefix}")
   private String viewPrefix;
   
   @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/public/");
        super.addResourceHandlers(registry);
    }
   
   //handlerMapping
   @Bean
   @Override
   public RequestMappingHandlerMapping requestMappingHandlerMapping() {
      RequestMappingHandlerMapping handlerMapping = super.requestMappingHandlerMapping();
      handlerMapping.setUseSuffixPatternMatch(true);
      handlerMapping.setAlwaysUseFullPath(true);
      handlerMapping.setOrder(0);
      handlerMapping.setInterceptors(securityRulesInterceptor());
      return handlerMapping;
   }
   
   //handlerAdapter
   @Bean
   public CustomRequestMappingHandlerAdapter customRequestMappingHandlerAdapter() {
      CustomRequestMappingHandlerAdapter adapter = new CustomRequestMappingHandlerAdapter();
      adapter.setCustomArgumentResolver(customArgumentResolver());
      adapter.setCustomReturnValueHandler(customReturnValueHandler());
      return adapter;
   }
   
   @Bean 
   public CustomArgumentResolver customArgumentResolver() {
      return new CustomArgumentResolver();
   }
   
   @Bean
   public CustomReturnValueHandler customReturnValueHandler() {
      return new CustomReturnValueHandler();
   }
   
   @Bean
   public CommonsMultipartResolver multipartResolver() {
      return new CommonsMultipartResolver();
   }
   
   //view Resolver
   @Bean
   public BeanNameViewResolver beanNameViewResolver() {
      BeanNameViewResolver beanNameViewResolver = new BeanNameViewResolver();
      beanNameViewResolver.setOrder(0);
      return beanNameViewResolver;
   }
   
   @Bean
   public UrlBasedViewResolver tilesViewResolver() {
      UrlBasedViewResolver urlBasedViewResolver = new UrlBasedViewResolver();
      urlBasedViewResolver.setViewClass(TilesView.class);
      urlBasedViewResolver.setOrder(1);
      return urlBasedViewResolver;
   }
   
   @Bean 
   public InternalResourceViewResolver viewResolver() {
      InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
      internalResourceViewResolver.setViewClass(InternalResourceView.class);
      internalResourceViewResolver.setPrefix(viewPrefix);
      internalResourceViewResolver.setSuffix(".jsp");
      internalResourceViewResolver.setOrder(2);
      return internalResourceViewResolver;
   }
   
   //exception Resolver
   @Bean
   public AjaxExceptionResolver ajaxExceptionResolver() {
      String[] headerValue = {"XMLHttpRequest"};
      AjaxExceptionResolver ajaxExceptionResolver = new AjaxExceptionResolver();
      ajaxExceptionResolver.setExceptionAttribute("exception");
      ajaxExceptionResolver.setHeaderName("X-Requested-With");
      ajaxExceptionResolver.setHeaderValues(headerValue);
      ajaxExceptionResolver.setErrorView("ajaxErrorView");
      ajaxExceptionResolver.setOrder(0);
      return ajaxExceptionResolver;
   }
   
   @Bean
   public PageExceptionResolver pageExceptionResolver() {
      Properties properties = new Properties();
      properties.setProperty("oing.exception.UserException", "system/exception/exceptionPage");
      properties.setProperty("oing.exception.SystemException", "system/exception/serverExceptionPage");
      
      PageExceptionResolver pageExceptionResolver = new PageExceptionResolver();
      pageExceptionResolver.setExceptionAttribute("exception");
      pageExceptionResolver.setExceptionMappings(properties);
      pageExceptionResolver.setDefaultErrorView("system/exception/exceptionPage");
      pageExceptionResolver.setOrder(1);
      return pageExceptionResolver;
   }
   
   @Bean
   public AjaxExceptionView ajaxErrorView() {
      AjaxExceptionView ajaxExceptionView = new AjaxExceptionView();
      ajaxExceptionView.setUserData("userData");
      return ajaxExceptionView;
   }
   
   @Bean
   public TilesConfigurer tilesConfigurer() {
      TilesConfigurer tilesConfigurer = new TilesConfigurer();
      tilesConfigurer.setDefinitions(viewPrefix + "/system/tiles-layout.xml");
      return tilesConfigurer;
   }
   
   @Override
   public void addInterceptors(InterceptorRegistry registry) {
      registry.addInterceptor(localeChangeInterceptor());
   }
   
   @Bean 
   public SecurityRulesInterceptor securityRulesInterceptor () {
      return new SecurityRulesInterceptor();
   }
   
   @Bean
   public LocaleChangeInterceptor localeChangeInterceptor() {
      LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
      localeChangeInterceptor.setParamName("lang");
      return localeChangeInterceptor;
   }
   
   @Bean 
   public RequestContextListener requestContextListener(){
       return new RequestContextListener();
   } 
}

Servlet context를 이렇게 JAVA Config로 변환을 하는건 여러 방법이 있는데 필자는 그중 WebMvcConfigurationSupport를 extends 받아서 사용하는 방법을 택했다. @EnableWebMvc, WebMvcConfigurerAdapter 등 다른 방법들이 있는데 이를 혼용해서 사용하지 않으면 된다. 

 

고생을 한 부분은 xml config에서는 static resource에 대한 정의를 따로 하지 않았었는데 java config에서는 위와 같이 addResourcesHandler를 통해서 static resource에 대한 정의를 해줘야 한다. 또한 Customizing한 RequestMappingHandlerMapping, RequestMappingHandlerAdapter 등에 대한 변환이 시간이 오래 걸린것 같다. 나머지는 다 단순한 @Bean으로의 변환이라 어렵지 않게 구성을 하였다. 


 

xml config만 10여년을 사용하다보니 그것에 너무 익숙해져서 java config로의 전환이 거부감이 들었는데 막상 이렇게 해놓고 보니 똑같다. 그냥 익숙함의 차이인것 같다. java config 가 디버깅이라던지 자동완성 등 분명 더 이점이 있기에 추세가 java config로 가는것일테고 그 흐름을 거부하지는 않아야겠다. 

 

springboot로의 migration.. 다음 단계는 디테일한 부분들에 대해서 다루도록 하겠다.

 

끝!

 

spring 프로젝트에서 springboot 프로젝트로 migration 하기 (1) - pom.xml

spring 프로젝트에서 springboot 프로젝트로 migration 하기 (2) - web.xml

spring 프로젝트에서 springboot 프로젝트로 migration 하기 (3) - 파일 추가 및 변경

spring 프로젝트에서 springboot 프로젝트로 migration 하기 (4) - logback

spring 프로젝트에서 springboot 프로젝트로 migration 하기 (5) - 빌드 및 실행

댓글
최근에 올라온 글
최근에 달린 댓글
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31