<@RestController와 한글 출력>


문서를 만드는 것보다 데이터를 만들어서 제공해주는 것(api제공)이 주된 사용법인 컨트롤러 어노테이션이다. (한글 출력이 문제가 되기 때문에 설정이 필요하다)

Controller가 문서를 기반으로 하는 반환값을 가진다면
RestController는 Restful한 형태의 결과물을 반환하는 클레스

@RestController를 사용하면 맵핑을 한 서비스 메소드가 마치 @ResponseBody를 붙인 것과 같은 형태(문자열)를 return 하게 된다


<Springframework dispatcher bean conflict 문제>
컨트롤러 어노테이션을 쓸 때 spring IoC에서 묵시적으로 일어나는 일
@Controller    ...scan
NoticeController noticeController = new NoticeController
<bean id="noticeController " class="...NoticeController">

주로 동일한 이름의 class가 있을 때 일어난다
막아주려면 식별자를 부여해줘야 한다


@RestController("apiNoticeController")
@RequestMapping("/api/notice/")
public class NoticeController {

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

(결과) >> notice list

apiNoticeController 식별자 부여

하지만 return 문자열을 "공지사항 list" 등의 한글로 하면 

(결과) >> ???? list

이런 식으로 나오기 때문에 converter를 사용해 reponse를 가로채 인코딩을 변경해줘야 한다


@RestController 한글 깨짐 문제 해결 코드

 

[servlet.context.xml]

1
2
3
4
5
6
7
8
9
10
11
12
13
 
<mvc:annotation-driven>
    <mvc:message-converters> <!-- @ResponseBody로 String 처리할때 한글처리 -->
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="supportedMediaTypes">
                <list>
                    <value>text/html;charset=UTF-8</value>
                </list>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>
 
cs




<JSON 출력하기>

JSON (JavaScript Object Notation)은 경량의 DATA-교환 형식이다
본래 자바스크립트가 사용하는 객체 표기법이었는데 현재는 자바스크립트를 넘어서 모든 곳에서 사용 중

[
    {key1: val1, key2: val2, key3: val3}
    {key1: val1, key2: val2, key3: val3}
    {key1: val1, key2: val2, key3: val3}
    {key1: val1, key2: val2, key3: val3}
    {key1: val1, key2: val2, key3: val3}
                                                                  ]

이렇게 생김

서버 쪽 메모리에 올라가있는 객체를 클라이언트로 반환한다는 건 말도안되는 일이므로
XML, CSV, JSON 같은 표현 형식으로 데이터를 변경해서 @RestController를 통해 보내줌

클라이언트는 자바스크립트를 사용하므로 데이터를 보내줄 때는 JSON이 가장 좋은 방법이다
DB 같은 경우 자체적인 경량화를 위해 다른 객체 표기법을 사용하기도 한다 (ex. mongoDB BSON)

그렇다고 JSON 형식으로 만들어주기 위해 Collection을 일일히 꺼내서 for문 돌려가며 formatting 하는 건
매우 귀찮은 일이므로 라이브러리를 쓰자

 

jackson-databind 라이브러리 추가

 

[POM.xml]

1
2
3
4
5
6
7
 
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.11.2</version>
</dependency>
 
cs

 



List<Notice> 객체 그대로 클라이언트로 던지기

[NoticeController.java]
@RestController("apiNoticeController")
@RequestMapping("/api/notice/")
public class NoticeController {

  @Autowired
  private NoticeService service;

  @RequestMapping("list")
  public List<Notice> list() throws ClassNotFoundException, SQLException {
      List<Notice> list = service.getList(1, "title", "");
      return list;
  }
}

List<Notice> 객체를 그대로 반환하고 있다
본래라면 말도 안되는 일이지만

 


이렇게 잘 보내짐
브라우저에 저렇게 보기좋게 정렬되는건 크롬 확장프로그램 JSONView 를 설치해서 그렇다
jackson-databind 라이브러리 설치안하면 converter java.utilArrayList 어쩌구는 보낼 수 없어욧 에러가 뜨니 설치 필수


Notice 한개만 보내기
@RequestMapping("list")
public Notice list() throws ClassNotFoundException, SQLException {
List<Notice> list = service.getList(1, "title", "");
return list.get(0);
}

 


잘보내짐

이제 객체 그대로 마구 던져도 알아서 JSON으로 잘 바뀌어서 클라이언트로 보내진다


 



1.서블릿 객체를 얻어서 문자열 출력

2.@ResponseBody 설정을 통한 문자열 출력

3.ResouceViewResolver를 이용한 문서 출력

4.TilesViewResolver를 이용한 문서 출력



문서 출력 2가지는 아래서 이미 해봄

서블릿 객체를 얻어서 문자열 출력을 해주는 방법

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

@RequestMapping("index")
public void index(HttpServletResponse response) {

PrintWriter out;
try {
out = response.getWriter();
out.println("hello index");
} catch (IOException e) {
e.printStackTrace();
}
}

servlet, jsp만 썼을 때처럼 response, request를 front controller에서 받아 쓰면 된다

 


단순히 문자열 출력만 원한다면

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

@RequestMapping("index")
@ResponseBody
public String index() {

return "Hello Index";

}

@ResponseBody를 붙여 String 값을 반환해주면 된다




<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를 뱉어낸다






스프링 설정 파일 나누기 
(dispatcher-servlet.xml 분리)


스키마 설정만 남기고 새로운 폴더에 원하는 카테고리로 설정 파일을 나눈다

 


ex)
servlet-context 껍데기
service-context 데이터 서비스
security-context 로그인, 보안 관련 서비스

담당 업무 분류로 사용할 수도 있다
(공유 repository에 올리면 파일이 겹치지 않으니까)
-비동기 개발 작업

설정 파일을 직관적으로 파악도 가능함


[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: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">

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

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@localhost:1521/xepdb1" />
<property name="username" value=""/>
<property name="password" value=""/>
</bean>
   
</beans>


이런 식으로 관련된 설정 코드만 넣는다
기존에 사용하던 dispatcher-servlet.xml 은 확장자를 .backup으로 바꿔주던 지우던 한다

dispatcher를 맵핑해주던 web.xml로 이동


[web.xml]
 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>
    /WEB-INF/spring/service-context.xml
    /WEB-INF/spring/security-context.xml
  </param-value>
 </context-param>
 
 <servlet>
  <servlet-name>dispatcher</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/spring/servlet-context.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
  <async-supported>true</async-supported>
 </servlet>
 <servlet-mapping>
  <servlet-name>dispatcher</servlet-name>
  <url-pattern>/</url-pattern>
 </servlet-mapping>

 

(굵게 칠해진 부분이 추가되는 부분이다)

<init-param> 안에는 한 개의 param 밖에 넣을 수 없다

<listener>
톰캣 or 세션이 시작할 때, 끝날 때 해결할 수 있는 이벤트를 처리할 때 사용됨
<listener>의 <context-param>값을 DispatcherServlet가 이용할 수 있으므로 추가하지 못한 설정 파일 경로를 추가한다

<load-on-startup>1</load-on-startup>
dispatcher(servlet객체)는 최초에 url을 호출 받았을 때 메모리에 올라가기 때문에 첫 요청시 속도가 느리다. 설정인 녀석이 느리면 안되므로 요청이 오기전에 미리 메모리에 첫번째로 올라갈 수 있도록 해준다.
load는 tomcat 서버의 load를 뜻한다. 서버가 올라갈 때 1번으로 올라가도록 설정.

<async-supported>true</async-supported>
비동기 설정이다. 동기적으로 다른 프로세스를 기다리지 않고 비동기화하여 바로 로드한다.

 

 


<서비스 클래스 분리하기>


DAO(data access object)를 예로들어 app을 서비스 중에 데이터베이스만을 교체해야 하는 경우가 생길 수 있다
JDBC를 사용 중에 JPA로 바꾸던가 mybatis를 사용한다던가
이러한 경우에 해당 클래스들을 모듈화(인터페이스화) 시켜 컴파일 되지 않은 문서형 설정 파일에서 쉽게 교체할 수 있도록 만들어줄 필요가 있다

본래는 DAO를 따로 만들어 DB에 관련된 코드를 작성하고
사용하는 클레스를 둬야 하지만 DI의 작업 방식이 xml에서 annotation으로 변화하는 과정을 확인하기 위한 연습중이므로 서비스 클레스에서 직접 jdbc를 사용한다고 가정한다
그러기 위해서 pom.xml에 ojdbc 설치


<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc10</artifactId>
<version>19.7.0.0</version>
</dependency>


Service 클레스를 인터페이스화 하기 전에 먼저 spring스럽게 클레스를 dispatcher-servlet.xml에서 객체화 시켜서 컨트롤러에 꼽아본다


<bean id="noticeService" class="com.newlecture.web.service.jdbc.NoticeService">

<bean name="/notice/list" class="com.newlecture.web.controller.notice.ListController">
     <property name="noticeService" ref="noticeService" />
</bean>


property name="noticeService"는 setNoticeService() 메소드를 불러오는 부분이므로 ListController에서 noticeService를 set 해주는 setter를 만들어준다


public class ListController implements Controller {

private NoticeService noticeService;


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

@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
// TODO Auto-generated method stub

ModelAndView mv = new ModelAndView("notice.list");
//mv.setViewName("/WEB-INF/view/notice/list.jsp");
List<Notice> list = noticeService.getList(1, "TITLE", "");
mv.addObject("list", list);

return mv;
}

}


NoticeService에서 사용하는 서비스 메소드를 사용해서 클레스가 제대로 연결이 되는지 확인해본다
(view도 서비스 메소드를 사용할 수 있게 수정)



<잘 되면 서비스 클래스 분리를 시작한다>

하위 패키지를 생성해 NoticeService에 기능적으로 분류될 클레스들을 만든다
(DAO를 예로들었으니 JDBCNoticeService, JPANotceService 등)

인터페이스로 사용될 껍데기 NoticeService를 만든다


public interface NoticeService {

List<Notice> getList(int page, String field, String query) throws ClassNotFoundException, SQLException;
int getCount() throws ClassNotFoundException, SQLException;
int insert(Notice notice) throws SQLException, ClassNotFoundException;
int update(Notice notice) throws SQLException, ClassNotFoundException;
int delete(int id) throws ClassNotFoundException, SQLException;

}

이 NoticeService 멀티탭(인터페이스)에 꼽을라면 위와 같은 요구를 충족해야함

 

NoticeService를 인터페이스 삼아 요구 사항들을 야무지게 구현한 클레스를 만들어준다


public class JDBCNoticeService implements NoticeService {
public List<Notice> getList(int page, String field, String query) throws ClassNotFoundException, SQLException{ 이렇게 이렇게 돌아가게 만들거임 }



이제 dispatcher.servlet.xml 에서
<bean id="noticeService" class="com.newlecture.web.service.jdbc.NoticeService">

<bean id="noticeService" class="com.newlecture.web.service.jdbc.JDBCNoticeService">
이렇게만 바꿔주면 만들어놓은 NoticeService를 마음대로 바꿔줄 수 있게 된다



<연결 정보 분리하기>


서비스 배포시에 컴파일된 바이너리 코드를 배포하는데 그 안에 DB의 ip나 id,pw가 있으면 마음대로 변경할 수가 없다. 변경할 때마다 서버 내리고 컴파일하고 빌드해서 재배포 할 수는 없는 노릇이니 이러한 데이터들도 dispatcher-servlet.xml로 빼줄 수 있다

연결 정보가 들어있던 클레스에 DataSource를 import 해준다


import javax.sql.DataSource;


DataSource에 마우스를 가져다 대면 Interface 타입인 것을 알 수 있다
(DataSource 타입을 실제로 객체화 시킬 수는 없다)


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";

private DataSource dataSource;

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

//Class.forName(driver);
//Connection con = DriverManager.getConnection(url,uid, pwd);
Connection con = dataSource.getConnection();


멤버선언과 setter를 만들어주고 서비스 메소드들에서 기존의 DB와의 연결 정보를 전달하던 부분은 삭제하고, dataSource 인터페이스에 설정된 연결 정보를 가져오는 방식으로 변경한다
이제 dataSource에 정보를 설정해줘야 하므로 dispatcher로 간다


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


property name에 만든 setter를 넣고 ref에는 아래 만들 dataSource의 id를 넣는다


<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@localhost:1521/xepdb1" />
<property name="username" value=""/>
<property name="password" value=""/>
</bean>


driverClassName, url, username, password은 DataSource를 implement해서 객체화한 org.springframework.jdbc.datasource.DriverManagerDataSource 클레스에서 만들어지는 setter 들이다

(이름이 정해져 있단 소리. 못바꿈)
value에 연결 정보를 넣으면 분리 완료

dispatcher에서 사용하는 org.springframework.jdbc.datasource.DriverManagerDataSource 객체는 spring-jdbc 라이브러리에서 가지고 있는 녀석이므로 없다면 pom.xml에 추가해준다


<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>


이제 xml 설정만 바꾸면 소스코드 필요없이 DB의 연결정보를 변경할 수 있게 되었다

 

 

페이지 공통 분모 집중화

웹페이지에서 똑같이 사용되는 코드를 모듈화해 한쪽에서 href 주소를 변경하면
모든 페이지 소스 코드를 돌아가면서 변경해야 되는 수고로움을 없앰


Tiles
모듈화 시킨 페이지를 합쳐주는 라이브러리
(2017년 서비스 끝남. 프론트엔드에서 하는 작업이라 프론트 기술이 발전하면서 사망함)
(대규모 사이트에서는 아직 혼합해서 사용하고 있기도 하다고 한다)


dispatcher가 기존의 조각낸 페이지 경로를 뱉어내는 것보다 우선시 되야 하므로
1.tiles 라이브러리 명령을 우선적으로 수행하게 하는 코드와
2.요청된 페이지에 합쳐질 모듈 조각들의 종류에 대한 지시
3.어디에 어떻게 붙일지에 대한 위치 지정을 위한 지시가 필요하다.


기존의 jsp를 공통 분모에 따라 나눈다
header, footer, layout  (index에서도 공용으로 쓰일 경우)
aside, visual, layout  (게시판 같은 곳에서 공용으로 쓰일 경우. 따로 폴더 만들어서 넣음)
list, detail  (유일하게 바뀌는 main 영역, 얘도 구분지은 폴더에 따로만들어서 넣는게 좋음)

layout 외에는 head 부분조차 필요없음 그냥 조각냄
layout은 합치는 과정을 위한 head만 있는 껍데기


먼저 controller에서 viewResolver 보다 tiles 사용이 우선되어 반환되어야 하기 때문에
new ModelAndView("root/index");
new ModelAndView("root.index");
view 경로를 지정해주는 부분을 변경해준다


https://tiles.apache.org/framework/tutorial/basic/pages
Tiles 사용 튜토리얼 페이지


But for now, let's stick to the default and create the /WEB-INF/tiles.xml file, with a definition as described in concepts:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE tiles-definitions PUBLIC
       "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
       "http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>
  <definition name="myapp.homepage" template="/layouts/classic.jsp">
    <put-attribute name="title" value="Tiles tutorial homepage" />
    <put-attribute name="header" value="/tiles/banner.jsp" />
    <put-attribute name="menu" value="/tiles/common_menu.jsp" />
    <put-attribute name="body" value="/tiles/home_body.jsp" />
    <put-attribute name="footer" value="/tiles/credits.jsp" />
  </definition>
</tiles-definitions>


 /WEB-INF/tiles.xml 
file을 저기에 저런 이름으로 만들란다

[tiles.xml]
<definition name="root.index" template="/layouts/layout.jsp">
name에 controller에서 반환하는 이름, template에 layout 경로

<put-attribute name="title" value="Tiles tutorial homepage" />
name에 조립할때 쓸 이름, value에 조립 파일 경로

참고해 조립 설계도를 만든다

 


조립을 하려면 jsp에서 사용할 태그lib가 필요하다
먼저 tiles를 설치해야함

 

[pom.xml]
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-jsp</artifactId>
<version>3.0.8</version>
</dependency>


설치가 완료되면 jsp에서 조립한다


[layout.jsp]

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
32
 
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<!DOCTYPE html>
<html>
<head>    
    <meta charset="UTF-8">
    <title><tiles:getAsString name="title" /></title>
    <link href="/css/customer/layout.css" type="text/css" rel="stylesheet" />
</head>
    <body>
        <!-- header 부분 -->
        <tiles:insertAttribute name="header" />
        <!-- --------------------------- <visual> --------------------------------------- -->
        <!-- visual 부분 -->
        <tiles:insertAttribute name="visual" />
        <!-- --------------------------- <body> --------------------------------------- -->
        <div id="body">
            <div class="content-container clearfix">
                <!-- --------------------------- aside --------------------------------------- -->
                <!-- aside 부분 -->
                <tiles:insertAttribute name="aside" />
                <!-- --------------------------- main --------------------------------------- -->
                <tiles:insertAttribute name="body" />
            </div>
        </div>
        <!-- ------------------- <footer> --------------------------------------- -->
        <tiles:insertAttribute name="footer" />
    </body>
</html>
 
cs


<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
tiles tag lib 삽입

<tiles:insertAttribute name="aside" />
tiles.xml에 지정한 이름

<tiles:getAsString name="title" />
단순한 문자열 삽입도 가능하다



ViewResolver가 Tiles를 인식하고 맵핑 & 우선순위를 정할 수 있도록 설정

[dispatcher-servlet.xml]
<bean
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.tiles3.TilesView" />
<property name="order" value="1" />
</bean>
<bean
class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions" value="/WEB-INF/tiles.xml" />
</bean>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"></property>
<property name="suffix" value=".jsp"></property>
<property name="order" value="2" />
</bean>

 

굵게 칠해진 곳이 추가되는 부분

order로 우선 순위를 정해주고 있다

 


View 조립이 잘되는지 실행해본다
안 될 경우 왜 안되는지 보통 오류창에 나옴
필요한 lib이 없거나, 코드가 틀렸거나, maven update를 안했거나 등.
server 실행환경이 바뀐 설정을 적용하지 못했을 수도 있으니 서버를 지웠다가 다시 구성해보는 방법도 있다



Tiles Wildcard (pattern) 사용법

[tiles.xml]
<tiles-definitions>
 <definition name="root.*" template="/WEB-INF/view/inc/layout.jsp">
    <put-attribute name="title" value="공지사항" />
    <put-attribute name="header" value="/WEB-INF/view/inc/header.jsp" />
    <put-attribute name="body" value="/WEB-INF/view/{1}.jsp" />
 </definition>
</tiles-definitions>

definition name="root.*"
*을 붙일 경우 root.뒤로 어떤 컨트롤러 리턴값이 와도 해당 definition의 조립 명세를 따른다

value="/WEB-INF/view/{1}.jsp"
{1}을 붙일 경우 root.뒤로 온 값을 해당 영역에 삽입한다
root.index가 왔다면 view/index.jsp가 된다
{2}를 붙이면 root.*.* 의 2번째 값이 들어가게 된다


Wildcard 참고 : https://tiles.apache.org/framework/tutorial/advanced/wildcard.html


Spring MVC에서 정적 파일 서비스하기


Spring의 Dispatcher는 기본적으로 정적인 파일(html,css,image)을 서비스 할 수 없도록 막아놓고 있다

풀어주기 위해서는 dispatcher-servlet에 mvc스키마를 추가하고 리소스를 허용해줘야 한다 

[dispatcher-servlet.xml]
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
    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">


xmlns:mvc="http://www.springframework.org/schema/mvc"
-mvc 처리기를 사용할때 mvc: 를 사용하겠다 선언

xsi:schemaLocation="
        http://www.springframework.org/schema/mvc
        https://www.springframework.org/schema/mvc/spring-mvc.xsd"
-mvc 스키마 파일(.xsd) 경로 설정


스키마 설정 후 다음 코드를 추가
<mvc:resources location="/static/" mapping="/**"></mvc:resources>
-사용자가 요청하는(/** : /이후의 모든 폴더나 파일)것은 (절대경로 /static/)에서 찾아라
(/static/ 이 마치 root 폴더처럼 바뀐다. localhost:5000/images)

설정이 끝나면 static 내부에 있는 정적 파일들을 서비스할 수 있다
(html,css,js,img 등 정적 파일별로 개별적으로 설정하면 페이지를 요청할 때 귀찮으니 위처럼 폴더 하나 정해서 한곳에 몰아놓는다)

http, https 언제고 못찾아서 오류나겠네

 

'Development > Java' 카테고리의 다른 글

데이터 서비스 클레스 분리, DB 연결 정보 분리 #6  (0) 2022.08.29
Tiles #5  (0) 2022.08.28
Spring MVC, 웹 개발환경 설정 #3  (0) 2022.08.26
DI, AOP #2  (0) 2022.08.25
Maven 사용법  (0) 2022.08.24


Spring boot 없이 Maven 프로젝트에 톰캣, jsp, Spring 얹어 웹 개발하기


1. javaEE로 Maven 프로젝트 생성

(기본아키타입, package war로 변경)

2. 톰캣 web.xml 파일 복사해서 web-inf 경로에 위치

(pom.xml에 톰캣api DI)
(라이브러리 설치 편하게 하기 위해 미리 maven index 리빌드)

3. pom.xml에서 jdk 버전 설정
<properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>

pom.xml에 이러한 에러가 뜰 경우 
1 line : Could not initialize class org.apache.maven.plugin.war.util.WebappStructureSerializer

 

<build>
  <plugins>
    <plugin>
      <artifactId>maven-war-plugin</artifactId>
      <version>3.2.2</version>
    </plugin>
  </plugins>

</build>

빌드에 maven.plugin.war을 넣어준다. (maven update)

4. 프로젝트 설정에서 루트 웹 경로에서 프로젝트명 제거 (/프로젝트 => /)

(이미 서버 실행환경을 구성했다면 서버를 지우고 다시 구성해준다)

5. 프로젝트 설정 리소스 인코딩 방식 utf-8로 변경. Window preference 설정에서 html,css,jsp 파일 인코딩 생성 방식도 모두 변경

6. 실험삼아 webapp에 index.jsp 만들어서 실행(하는 김에 톰캣 서버 실행환경 구축도 해줌)
-브라우저 맘에 안들면 window 탭에서 브라우저 변경

7. Spring web을 DI 해야하는데 Spring web MVC 찾아서 DI 하면 web도 필요해서 알아서 필요한 라이브러리들 다 찾아서 설치해줌

8. 그외 jsp, jstl 등 없으면 필요한거 찾아서 설치해주고 Spring mvc 방식에 따라 주소 맵핑시켜주면 됨



Spring MVC

MVC model2 방식의 변화 : Dispatcher를 집중화 한 후의 모델

View(jsp), Dispatcher(dispatcher-servlet.xml), Controller로 나누기


(POJO : Plain Old Java Object)

-특정한 라이브러리나 프레임워크에 종속되지 않은 순수한 자바 객체 (getter,setter 등만 사용하는)

web.xml에서 모든 url을 dispatcher-servlet로 던져주고 dispatcher-servlet에서 url들을 맵핑시킨다.

(Controller와 View(jsp(url))을 dispatcher-servlet으로 객체화, 부품화 시켜 모듈화 할 수 있게 해주겠다는 것)


먼저 WEB-INF에 dispatcher-servlet.xml 이라는 파일을 만들어준다

https://docs.spring.io/spring-framework/docs/
dispatcher-servlet.xml 작성 레퍼런스 참조 사이트


사용하는 Spring framework 버전을 찾아 ref document 웹페이지를 찾아 들어간다
(버전마다 다르므로 자신이 pom에 더함충한 Spring version을 알아보자)


DI를 하는 부분이므로 Core > Ioc container 관련된 탭을 뒤져서 Configuration Metadata를 뒤적거리면 example을 찾아 볼 수 있다

 

ex)
https://docs.spring.io/spring-framework/docs/5.2.x/spring-framework-reference/core.html#beans-factory-metadata

The following example shows the basic structure of XML-based configuration metadata:
XML을 베이스로 하는 설정 메타데이터 기본 구조 예제를 보여주고 있다

 

기본 구조 예제)
[ dispatcher-servlet.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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="...">  
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->

</beans>

복 to the 붙 필요없는 <bean id="..." class="..."> 이딴건 지우고 XML로 DI 하는 방식으로 쓰면된다

[ example ]
    <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" />

다른 점은 bean의 name이 객체의 사용하려는 변수가 아닌 바로 url이 됨
class 속성이 class 경로인건 똑같다

아직 톰캣의 web.xml이 url을 받아 처리하고 있으니 spring에서 지원하는 dispatcher로
url을 맵핑 시켜줘야 한다

[ web.xml ]
 <servlet>
  <servlet-name>dispatcher</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
 </servlet>
 <servlet-mapping>
  <servlet-name>dispatcher</servlet-name>
  <url-pattern>/</url-pattern>
 </servlet-mapping>


org.springframework.web.servlet.DispatcherServlet.class
-spring MVC 지원 라이브러리

 

<servlet-name>은 원하는대로 작성해도 된다



원하는 컨트롤러 클레스에 Controller 인터페이스를 implements 한 뒤 ModelAndView 타입의 handleRequest 메소드로 view와 url을 맵핑 시켜줄 때, 일일히 생성자 안에다 /WEB-INF/view/페이지이름.jsp 해주는 건 귀찮으므로 ViewResolver 기능을 사용해주자

(handleRequest는 서블렛에 디스패처 포워드가 합쳐져있다)
(ModelAndView 인스턴스를 만들어 setViewName 메소드로 view 경로를 설정해줘도 되고, 생성자가 오버로드 되어 있으므로 인스턴스 생성시에 생성자 안에 view 경로를 적어줘도 맵핑시켜준다)

인스턴스.addObject(변수, 보낼거)를 하면 쉽게 view로 데이터를 전달할 수 있다 )


- ViewResolver -
[ dispatcher-servlet.xml ]
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/WEB-INF/view/"/>
  <property name="suffix" value=".jsp"/>
</bean>


설정이 끝나면 view 파일들을 WEB-INF/view 폴더에 넣어 은닉화 시켜주자
사용자가 view 파일을 직접 요청하여 볼 수 있게 하는 것은 절차상으로 일어나서는 안된다
view는 오로지 controller에 의해서만 랜더링 되고 보여져야 한다

 

번외)

/WEB-INF/view/index.jsp   절대주소

WEB-INF/view/index.jsp   상대주소

dispatcher로 맵핑 되어 있을 때 상대 주소를 쓰면 맵핑된 주소에서 인자로 받은 주소를 추가해서 스캔한다

예를들어 dispatcher에서 /aa/index로 맵핑시켜놨다면 /aa/WEB-INF/view/ 에서 index.jsp를 찾는다

절대 경로로 view 파일을 찾고 싶다면 앞에 / 를 붙여줘야한다

'Development > Java' 카테고리의 다른 글

Tiles #5  (0) 2022.08.28
Spring MVC에서 정적 파일 서비스하기 #4  (0) 2022.08.26
DI, AOP #2  (0) 2022.08.25
Maven 사용법  (0) 2022.08.24
Spring Framework, DI, IoC container #1  (0) 2022.08.23

+ Recent posts