[Spring framework] 공부를 해보자 2탄
Spring framework
스프링은 특정 뷰 기술이 없어도 브라우저에서 렌더링이 가능하도록 하는 View Resolver를 제공한다. 스프링에서 JSP, Velocity(사용 안함), Freemarker, XSLT View, Thymeleaf, ReactJS, AngularJS등이 있으며, NodeJS 렌더링으로 유명한 Jade 또한 사용이 가능하다. (고로, 뷰 템플릿 엔진은 무엇을 쓰든지 가능하다. 자유도가 높다.)
스프링이 뷰를 처리하는데 중요한 기술 2가지 인터페이스가 있다.
- View Resolver interface
- View interface
view Resolver interface는 뷰 이름 = 실제 뷰를 매핑하는 작업을 하며, View interface는 요청을 준비하고, 뷰 템플릿 엔진을 사용해 요청에 대한 처리를 한다.
ViewResolver
- AbstractCachingViewResolver
- XmlViewResolver
- ResourceBundleViewResolver
- UrlBasedViewResolver
- InternalResourceviewResolver
- VelocityViewResolver/FreeMarkerViewResolver
- ContentNegotiatingviewResolver
이중에서 AbstractCachingViewResolver의 하위 클래스들을 이용하면, 처리하는 뷰 인스턴스를 캐싱하여, 뷰 기술의 성능을 향상 시킬 수 있다.
스프링은 다중 뷰 리졸버를 지원하며, 리졸버를 체이닝 할 수 있고, 우선순위를 정할 수 있다.
@RequestMapping은 URL을 컨트롤러의 메서드와 매핑할 때 사용한다.
@RequestMapping(value = {"/", "/hello"}, method = RequestMethod.GET)
public String printWelcome(ModelMap model) {
logger.debug("Home Page");
model.addAttribute("message", "Spring 3 MVC Hello World");
return "hello";
}
위 처럼 url이 다중으로 연결 할 수 있다. 또한, 정규식 패턴을 사용 할 수 있어 url 매핑을 특정 정규식으로만 매핑 시킬수도 있다. 그리고 HTTP 메소드 매핑에 따라서 매핑 가능하다.
동적 값 또한 받아 올 수 있다.
@RequestMapping(value = "/hello/{name:.+}", method = RequestMethod.GET)
public ModelAndView hello(@PathVariable("name") String name) {
ModelAndView model = new ModelAndView();
model.setViewName("hello");
model.addObject("msg", name);
return model;
}
위 코드에서는 {}
로 쌓인 name을 @PathVariable
로 타입까지 지정하여 가져 올 수 있다.
@RequestParam
@RequestParam
은 key = value 형태로 화면에서 넘어오는 쿼리 스트링 혹은 폼 데이터를 메소드의 파라미터로 지정한다. 당연히 파라미터의 수가 적을때만 사용하는게 좋다.
해당 어노테이션을 사용했는데도 불구하고 값이 없다면, 400 Error
가 발생한다. 고로, 에러 방지를 해줄 필요가 있다.
@RequestMapping(value="/...", method={RequestMethod.GET, RequestMethod.POST})
public String submit(HttpServletRequest req,
@RequestParam(value="num1", defaultValue = "0") int num1,
@RequestParam(value="num2", defaultValue = "0") int num2,
@RequestParam(value="oper", required=false) String oper)
throws Exception {
}
위 처럼 defaultValue
를 지정해주던지, 반드시 필요한 값은 아니라는 의미의 require=false
를 설정 하는 것이다.
파라미터 수가 많다면, Map 방식을 사용하는 것이 좋다.
@RequestMapping("/Detail")
public String ShopDetail(@RequestParam HashMap<String, String> map){
String SerchingPages = map.get("searchingPageValue");
return "/shopping2/detail.nhn";
}
@Controller
컨트롤러에 사용하는 어노테이션으로 클래스 선언을 단순화 시켜준다는 장점이 있다. 또한, bean을 자동으로 생성해준다. (xx-servlet.xml
에서 Component-Scan을 통해 bean 등록)
@Repository
이 어노테이션은 일반적으로 DAO에 사용 된다.
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any
*/
String value() default "";
}
실제 어노테이션 소스를 열어보면 이렇게 구성 되어 있는데, 특별한 일을 해준다는 것에 대해서는 잘 모르겠다. (더 찾아 보기)
나중에 @Autowired를 이용해 자동 빈 주입을 하려고 해도 @Repositoy 어노테이션도 적용을 하지 않고, 빈 등록도 하지 않으면, DAO에 대한 빈 주입을 할 수 없다. 고로, 어노테이션 기반이라면, DAO에 선언을 해주어야 한다.
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any
*/
String value() default "";
}
@Service 어노테이션 또한 비슷하다. 소스 코드 내의 서비스라는 것을 명시해준다는 것뿐인지. 또 다른 이면의 기능을 하는지 모르겠다.
@Repository처럼 서비스에 해당하는 빈에 해당 어노테이션을 추가해주어야 한다. 그렇지 않으면, 스프링 컨테이너인 IoC컨테이너가 빈을 찾을 수 없다.
(참조할 XML 설정 정보에도 없는데 무슨 수로 찾을까…)
@Resource
특정 Bean의 기능 수행을 위해 다른 Bean을 참조해야 할 때, 사용하는 어노테이션 중 하나 이며, 표준 어노테이션으로 Spring Framework 2.5부터 지원 가능하다.
필드, 입력 파라미터가 한 개인 Bean 프로퍼티 setter 메소드에 적용이 가능하다.
@Resource는 이름으로 연결한다.
사용하려면,
<bean class="org.springframework.beans.factory.annotation.CommonAnnotationBeanPostProcessor"/>
을 등록 시켜주어야 한다.
하지만, <context:annotation-config/>
를 사용해도 된다.
참고1 , 참고2
@Autowired
특정 Bean의 기능 수행을 위해 다른 Bean을 참조 할 때, 사용하는 어노테이션이며(의존 관계를 자동 설정할 때 사용한다.), Type-driven Injection 기반이다. 고로, 참조할 빈을 검색할 때, 같은 타입의 빈이 여러 개 혹은 없을 경우 오류가 난다. 그래서 @Qualifier 어노테이션을 같이 사용하여, 구분을 할 수 있게 해준다.
@Autowired(required=false)를 설정하여, 꼭 설정 하지 않아도 되면, 이렇게 설정함으로 예외를 발생시키지 않을 수 있다.
@Qualifier
를 정의하는 방법
- XML
<bean class="anyframe.sample.springmvc.annotation.web.SimpleProductCategory"> <qualifier value="electronics"/> <!-- inject any dependencies required by this bean --> </bean>
- Annotation
@Component @Qualifier("electronics") public class ElectronicsProductCategory implements ProductCategory { //... }
@Qualifier
@Autowired 사용시 같은 타입이 여러 개 중에서 특정 빈을 지정할 때, @Qualifier를 사용 할 수 있다. 고로, 이를 이용해 @Autowired의 예외 상황을 막을 수 있다.
Spring + Mybatis 연동하기
필요 라이브러리
- spring-jdbc : Spring 에서 지원하는 JDBC
- mysql-connector-java : MySQL 커넥션 드라이브를 제공
- mybatis : myBatis
- mybatis-spring : Spring 에서 연동을 지원하는 myBatis
- commons-dbcp : 커넥션 풀을 담당하는 Apache Commons DBCP
<!-- spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.3</version>
</dependency>
</dependencies>
이렇게 라이브러리를 추가하면 된다. 하지만, 여기서 부터 문제는 Spring + Mybatis 연동 방식이 각양각색이다. properties로 한다거나, xml로 한다거나 web.xml에 직접 jdbc 설정 정보를 넣거나 하는 방식이 여러가지이다. (핵심만 본다면, 결국 모든 방식이 Spring과 Mybatis를 연동하는 것 일뿐이다.)
프로젝트의 설정 하는 사람 마음대로 연동법이 각기 다르게 존재한다. (이것은 어떤 방식으로 학습을 해야 할지 갈피를 잡을 수 없다…)
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:config/spring/context/context-*.xml</param-value>
</context-param>
web.xml에 해당 내용을 추가하게 되면, 경로에 있는 context-로 시작하는 모든 xml 파일을 스프링 컨테이너가 첫 로딩시 web.xml을 읽으면서 같이 로딩 하도록 하는 것이다. 해당 경로에는 mybatis 연동 파일들이 존재한다.
context-datasource.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.0.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd">
<bean id="dataSource" 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/board"/>
<property name="username" value="board_user"/>
<property name="password" value="1234"/>
</bean>
</beans>
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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- name은 위에서 등록한 sqlSession bean에서 사용 할 이름-->
<!-- ref의 dataSource는 context-datasource.xml에서 정의한 bean을 참조하는 것을 의미한다.-->
<property name="dataSource" ref="dataSource" />
<!-- sql mapper 파일 위치의 *_sql.xml 모두 등록하기 위함이다. -->
<property name="mapperLocations" value="classpath*:/mapper/**/*_SQL.xml" />
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSession"/>
</bean>
</beans>
위 두 파일의 내용을 찾아봐야 할 듯 하다. 무슨 의미를 하는지, 무엇을 설정하는 지에 대해 알 필요가 있을 듯 하다.
이어서 찾아 보기
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
위 내용도 추가를 해주었는데, 이는 계층별로 xml 파일이 있으면, web.xml에서 모두 로딩 될 수 있도록 등록 할 때, 사용하며 서블릿 이전에 서블릿 초기화하는 용도로 쓰이고, contextConfigLocation이라는 파라미터를 사용시, Context Loader가 로딩할 수 있는 설정파일을 여러 개 쓸 수 있다. 참고1, 참고2