<Spring XML 방식에서 Annotation 방식으로 변경하기 복습>


스프링은 2.0 때부터 Annotation을 쓸 수 있었는데, XML을 사용하던 것은 과거 방식이고 최근에는 대부분 Annotation을 통해 Dependency injection하는 방식을 사용했다
(하지만 아직 많은 회사나 기관들에 XML 방식 일때 짜여진 코드들이 많기 때문에 XML 방식도 알고있어야 한다)

또 Annotation java config로 바뀌면서 XML 설정부를 아예 제외하기도 하고, 가장 최근에는 java config도 아니고 다시 txt 파일로 가는데 XML이 아닌 더 경량화된 Properties나 YAML 등을 사용하기도 한다
(성능과 생산성, 직관성 측면에서 지속적으로 변화하고 있는 것 같다)



<하나씩 변경하기>


1) DI 부분 Annotation 방식으로 변경


Controller에서 Service객체 set 하는 부분 주석처리
[servlet-context.xml]
<bean name="/notice/list" class="com.newlecture.web.controller.notice.ListController">
     <!-- <property name="noticeService" ref="noticeService" /> -->
    </bean>


import하고 set되길 원하는 부분에 @Autowired Annotation 붙이기
[ListController.java]
import org.springframework.beans.factory.annotation.Autowired;

private NoticeService noticeService;

@Autowired
public void setNoticeService(NoticeService noticeService) {
this.noticeService = noticeService;
}


xml 설정에 context 스키마와 설정 추가
[servlet-context.xml]
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        https://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
        
    <context:annotation-config />


DI가 잘 되었는지 확인해보고 Annotation을 어디 붙일지 고민한다
[ListController.java]
import org.springframework.beans.factory.annotation.Autowired;

private NoticeService noticeService;


@Autowired
public void setNoticeService(NoticeService noticeService) {
this.noticeService = noticeService;
}

setter에 붙이면 함수 안에 injection시 실행되길 원하는 코드를 넣을 수 있다
딱히 그런 게 필요없다면 선언부에 붙이게 되면 setter가 필요없어진다


[ListController.java]
import org.springframework.beans.factory.annotation.Autowired;

@Autowired
private NoticeService noticeService;

setter 없어도 잘돌아감


[sesrvlet-context.xml]
<bean name="/notice/list" class="com.newlecture.web.controller.notice.ListController">
     <!-- <property name="noticeService" ref="noticeService" /> -->
    </bean>

애초에 xml에서 setter를 사용하는 property name이 이젠 필요없으니 굳이 작성할 필요도 없는 것


마찬가지로 DB 연결 정보도 Annotation으로 변경한다
[service-context.xml]
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        https://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
        
    <context:annotation-config />

<bean id="noticeService" class="com.newlecture.web.service.jdbc.JDBCNoticeService">
<!-- <property name="dataSource" ref="dataSource" /> -->
</bean>

스프링 컨텍스트 스키마 넣고 주석처리


[JDBCNoticeService.java]
public class JDBCNoticeService implements NoticeService {
// private String url = "jdbc:oracle:thin:@localhost:1521/xepdb1";
// private String uid = "";
// private String pwd = "";
// private String driver = "oracle.jdbc.driver.OracleDriver";

@Autowired
private DataSource dataSource;

// public void setDataSource(DataSource dataSource) {
// this.dataSource = dataSource;
// }

잘 되는지 확인




2) 객체 생성 부분 Annotation 방식으로 변경


먼저 xml에서 객체 생성을 못하게 막는다
[service-context.xml]
<!-- <bean id="noticeService" class="com.newlecture.web.service.jdbc.JDBCNoticeService">
<property name="dataSource" ref="dataSource" />
</bean> -->


객체를 생성할 수 있도록 Annotation을 붙여준다
[JDBCNoticeService.java]

import org.springframework.stereotype.Component;

@Component
public class JDBCNoticeService implements NoticeService {

@Autowired
private DataSource dataSource;

xml에서 Component Annotation을 scan할 위치를 지정해주지 않으면 class를 찾지 못한다


찾을 수 있도록 설정해준다
[service-context.xml]
    <!-- <context:annotation-config /> -->
    <context:component-scan base-package="com.newlecture.web.service" />

com.newlecture.web.service 안에 있는 하위 패키지 포함 모든 class를 scan 해라
그런 이유로 Autowired에 쓰인 <context:annotation-config />는 이제 필요없다


의미론적 Annotation 사용하기
[JDBCNoticeService.java]

import org.springframework.stereotype.Service;

@Service   // @Component, @Controller, @Service, @Repository
public class JDBCNoticeService implements NoticeService {

@Autowired
private DataSource dataSource;

@Component는 생성하는 객체에 범용적으로 사용하는 Annotation이다
클레스의 역활에 맞는 Annotation을 사용한다




3) Controller 설정을 Annotation으로 변경 (url 맵핑)


Controller는 직접 만든 인터페이스가 아닌 Spring MVC에서 지원하는 인터페이스를 사용했었다
(org.springframework.web.servlet.mvc.Controller)
이것 역시 Annotation 기능으로 변경해 xml에서 제외할 수 있다


Annotation scan 범위 설정, Controller 객체 생성 주석처리
(scan 범위는 프로젝트가 커질수록 성능 문제도 있으니 너무 넓게 잡지 않는다)
    <!-- <context:annotation-config /> -->
    <context:component-scan base-package="com.newlecture.web.controller" />

<!-- <bean name="/index" class="com.newlecture.web.controller.IndexController" /> -->
    <bean name="/notice/list" class="com.newlecture.web.controller.notice.ListController">
     <!-- <property name="noticeService" ref="noticeService" /> -->
    </bean>
    <bean name="/notice/detail" class="com.newlecture.web.controller.notice.DetailController" />


Controller는 객체 생성과 동시에 url 맵핑도 시켜줘야 한다


기존 XML 설정 상태의 Controller
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

public class IndexController implements Controller {

@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mv = new ModelAndView("root.index");
mv.addObject("data", "Hello Spring MVC~");
return mv;
}
}


Annotation 형태의 Controller
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class IndexController{

@RequestMapping("/index")
public void aaaa() {
System.out.println("asdf");
}
}

(Controller interface implement 필요없음, interface 의존에서 벗어났으니 반드시 구현해줘야할 public ModelAndView handleRequest 메소드 필요없음, 결국 XML 설정 class import 필요없음, )


@RequestMapping을 읽어들여 url 맵핑을 해줄 수 있도록 servlet-context.xml 끝에 annotation-driven 설정을 추가한다
[servlet-context.xml]
<mvc:resources location="/static/" mapping="/**"></mvc:resources>
<mvc:annotation-driven />
</beans>


Annotation을 사용하면 더 이상 URL은 클레스에 맵핑되지 않는다
Controller Annotation이 붙은 클레스의 서비스 메소드에 @RequestMapping을 통해 맵핑되므로 하나의 Controller 클레스가 여러개의 url을 가질 수 있기 때문에 클레스가 반드시 url의 개수별로 필요하지는 않아졌다


이제는 root 또는 view 폴더 내의 view 파일들의 맵핑을 분류하는 폴더 개념으로 또는 관련된 서비스들의 url 맵핑을 모아주는 개념으로 Controller의 이름을 바꿔준다

URL 맵핑한 서비스 메소드가 Tiles에 맵핑한 definition name을 반환해서 조각모음(...)을 할 수 있도록 해준다

@Controller
public class HomeController{
@RequestMapping("/index")
public String index() {
return "root.index";
}
@RequestMapping("/help")
public void help() {
System.out.println("dsad");
}
}


index가 잘 나오는지 확인한다




4) Controller 정리하기


패키지 트리를 보기 편하게 하이라키로 변경

 

 


view 폴더의 기능적 구조에 따라 controller의 구조도 따라줄 필요가 있다

 

Tiles 사용
구조를 변경해야한다
통합시킬 controller 클레스 생성



기존 xml controller 클레스를 서비스메소드 하나로 바꾸기

 

xml 형태

 

annotation 서비스 메소드로 변경


(view 구조에 맞게 맵핑url도 맞춰준다)

 


필요없어진 controller 삭제

 




기존의 notice에 관계된 controller 객체 생성부 삭제
<!--  <!-- <bean name="/index" class="com.newlecture.web.controller.IndexController" /> -->
    <bean name="/notice/list" class="com.newlecture.web.controller.notice.ListController">
     <property name="noticeService" ref="noticeService" />
    </bean>
    <bean name="/notice/detail" class="com.newlecture.web.controller.notice.DetailController" /> -->
(이제 필요없다)


view의 링크 주소를 변경한 url맵핑에 맞게 수정하고 테스트해본다


url맵핑 공통부를 Controller에서 묶어줄 수 있다
@Controller
@RequestMapping("/customer/notice/")
public class NoticeController {

@Autowired
private NoticeService noticeService;

@RequestMapping("list")
public String list() throws ClassNotFoundException, SQLException {
List<Notice> list = noticeService.getList(1, "TITLE", "");
return "notice.list";
}

@RequestMapping("detail")
public String detail() {
return "notice.detail";
}
}


@Controller
@RequestMapping("/")
public class HomeController{

@RequestMapping("index")
public String index() {
return "root.index";
}




5) XML => XML + Annotation 변경 요약

xml 설정
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation=        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
스키마 설정
<context:component-scan base-package="com.newlecture.web.controller" />
컴포넌트 스캔 범위 설정
<mvc:annotation-driven />
url 맵핑 설정

class 생성
@Component @Controller @Service @Repository
클레스
@Autowired @RequestMapping
멤버, 서비스메소드

tiles 맵핑
return String tiles definition name

return 값이 String이 아니면 resolver에서 관련값 찾다가 404 에러 (controller는 정상 동작)
연결된 컨트롤러 없어도 404 에러
viewResolver 자체가 없다면 500 에러

Spring MVC에서는 Front Controller(servlet)가 내부적으로 동작해 controller를 찾아 url을 맵핑시켜준다
FC 내부적으로 method.invoke()를 실행해 컨트롤러를 찾아 맵핑된 메소드를 실행했으나 리턴값이 void라면 invoke 함수에 리턴되는 값이 없으므로 404에러(url없음)을 뱉어내는 것이고, 연결될 controller 자체가 없어도 404를 뱉어낸다




+ Recent posts