[    5판 7장 객체(Object)와 배열(Array)    ]



[    스프링과 안드로이드 연동5 : Javascript에서 Android 함수를 호출하기    ]



요즘은 웹을 개발하고 웹뷰를 이용해서 안드로이드에 붙이는 식으로 해서 반응형으로 하이브리드 앱을 만드는 경우가 많은데,


이러한 경우 웹뷰에서 버튼을 클릭한다거나 했을 때 자바스크립트에서 안드로이드에 있는 함수를 호출해서 


안드로이드를 제어하고 싶은 경우가 있다.


----------------------------------------------------------------------------------------------------------------------------------


먼저, 웹뷰를 연결해 세팅부터 하자.


1. [    WebView(웹뷰) 세팅    ]


일단 스프링 웹 프로젝트에서 작성한 웹 페이지를 띄우는 웹뷰를 작성한다.

그 다음에 


2. 자바스크립트에서 호출시 수행할 안드로이드 메서드를 작성한다.


이때, 자바스크립트와 안드로이드를 중간에서 인터페이스 역할을 할 클래스를 작성해서 그 내부에

메서드를 정의하도록 한다.


당연히 인터넷 작업을 해야함으로

<!-- 인터넷 접속 권한 추가 -->
<uses-permission android:name="android.permission.INTERNET" />

를 manifest에 추가해주어야 하고

네트워크 작업은 백그라운드 쓰레드로 해야하며


백그라운드 쓰레드에서는 메인 뷰의 화면 제어를 할 수 없음으로

handler에게 대신해달라고 요청을 해야한다.


앞에선 계속 그렇게 해왔는데 이 2가지를 한번에 하는 것이


handler.post(new Runnable(){ run() }) 을 이용한 방식이다.


이런식으로해서 JavascriptInterface 클래스를 만들도록 한다.





3. 만들어준 JavascriptInterface 클래스를 웹뷰에 등록해 주어야 한다.



이때 중요한 점은 

webView1.addJavascriptInterface(new JavascriptInterface(),"myJSInterfaceName");

에서 두번째 매개변수란에 myJSInterfaceName 처럼 인터페이스 이름을 지정하게 되어 있는데

이 이름을 이용해서 자바스크립트에서 호출하게 된다.



4. 스프링 웹 프로젝트의 자바스크립트에서 특정 이벤트 발생시 안드로이드 함수를 호출하는 구문을 작성하자.


가장 중요한 부분은 window.myJSInterfaceName.callMethodName(str); 부분이다.

window.(지정한 javascript인터페이스명).수행할메서드명() 으로 호출하게 된다.

function callAndroid(){
    var str = document.getElementById("txtName").value;
    window.myJSInterfaceName.callMethodName(str);
}

<form id="formName" action="">
    <input id="txtName" type="text" />
    <button onclick="javascript:callAndroid()">호출하기</button>
 
</form>


여기까지 했다면, 


웹뷰 상에서 안드로이드 사용자가 "호출하기" 버튼을 클릭시에 callAndroid() 자바스크립트 메서드가 수행되고


해당 자바스크립트 메서드에서 window.myJSInterfaceName.callMethodName(str); 을 통해 안드로이드 메서드를 

호출하여, TextView에 있는 메시지를 웹뷰를 통해 입력한 값으로 세팅하게 된다.



안드로이드 쪽 전체 코드는 다음과 같다.



[    스프링과 안드로이드 연동4 : JSON으로 가져오기    ]



이번에는 안드로이드에서 스프링 프로젝트로 요청시 JSON으로 서버에서 제공하는 데이터를 가져오는 방식을 다루어 보겠습니다.


1. 먼저, 스프링 프로젝트에서 pom.xml에 라이브러리를 추가합니다.


1) jackson-databind 추가

: @ResponseBody로 반환시 필요

<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-databind</artifactId>

<version>2.8.7</version>

   </dependency>


2) jason-simple 추가

: JSONObject와 같은 json 객체 생성시 필요

<dependency>

<groupId>com.googlecode.json-simple</groupId>

<artifactId>json-simple</artifactId>

<version>1.1.1</version>

  </dependency>


2. 다음으로 스프링 컨트롤러 부분을 작성합니다. 스프링 컨트롤러에서 다음의 코드를 입력합니다.



http://localhost:8080/json.do 로 요청시 필요한 데이터를 JSONObject에 저장시켜 반환하는 컨트롤러 부분입니다.

반환시 한글 인코딩 문제 때문에 produces 부분에서 JSON 객체 생성시 utf-8로 인코딩하겠다는 설정을 하고,

JSONObject는 {변수명:값} 을 담을 수 있음으로

JSONArray로 배열 객체를 생성해 여기에 JSONObject 객체를 담아서

sendData라는 변수명으로 이 배열을 저장시켜 JSON으로 반환하게 됩니다.




2. 안드로이드에서 작업


안드로이드에서도 JSON 객체를 받아오기 위해서 2개의 라이브러리를 추가해주어야 합니다.

1. httpclient-4.2.2.jar

2. httpcore-4.2.2.jar

두개의 라이브러리를 mvnrepository 에서 검색해서 files 부분에 보면 다운로드를 할 수 있는데 다운로드 합니다.

그리고 안드로이드 프로젝트에서 프로젝트 보기가 Android로 되어 있는데 이 부분을 눌러 Project로 변경합니다.

그러면, App/libs 폴더가 보일겁니다.

여기 Android일 땐 보이지 않지만

Project로 변경시 app/libs 폴더가 보입니다. 그러면 이 폴더에 다운받은 2개의 라이브러리를 집어 넣습니다.


다음으로, android 형식으로 보기를 다시 누르고, Gradle Scripts 의 build.gradle 부분에 들어가서


dependency 부분에 


다음의 2줄을 추가해줍니다.

compile files('libs/httpclient-4.2.2.jar')
compile files('libs/httpcore-4.2.2.jar')


그리고 Sync Now 선택해주면 적용이 된 것입니다.



3. 이제 안드로이드에서 코드를 작성합니다.


역시 네트워크 작업임으로 이전에서 했던 3가지 사항을 준수합니다.


1) 인터넷 사용 권한 설정(manifest에)

<!-- 인터넷 접속 권한 추가 -->
<uses-permission android:name="android.permission.INTERNET" />


2) 네트워크 작업임으로 백그라운드 스레드에서 작업할 것


3) 백그라운드 스레드에선 메인 뷰에 접근할 수 없음으로 핸들러를 통해 뷰에 접근할 것!






**** 마지막으로.........

위의 경우에서는 안드로이드에서 스프링 컨트롤러로 post로 요청을 보낼 때 데이터를 같이 보내지 않았다. 만약 데이터를 같이 넘겨줘야 하는 경우에는

// NameValuePair : 변수명과 값을 함께 저장하는 객체로 제공되는 객체이다.
ArrayList<NameValuePair> postData = new ArrayList<>();
// post 방식으로 전달할 값들을 postData 객체에 집어 넣는다.
postData.add(new BasicNameValuePair("id","아이디"));
postData.add(new BasicNameValuePair("pw","패스워드"));
// url encoding이 필요한 값들(한글, 특수문자) : 한글은 인코딩안해주면 깨짐으로 인코딩을 한다.
UrlEncodedFormEntity request = new UrlEncodedFormEntity(postData,"utf-8");
HttpPost httpPost = new HttpPost(url);
// post 방식으로 전달할 데이터 설정
httpPost.setEntity(request);
// post 방식으로 전송, 응답결과는 response로 넘어옴
HttpResponse response = http.execute(httpPost);
// response text를 스트링으로 변환
String body = EntityUtils.toString(response.getEntity());
// 스트링을 json으로 변환한다.
JSONObject obj = new JSONObject(body);

// 스프링 컨트롤러에서 리턴해줄 때 저장했던 값을 꺼냄
String message = obj.getString("message");

과 같이 작성하면 된다.



[    스프링에서 구글맵 연동하기    ]



스프링에서 구글맵을 연동하는 방법을 알아봅시다.

1. 먼저 구글 API 키를 다음 url로 들어가 얻습니다.

https://console.developers.google.com/flows/enableapi?apiid=maps_backend,geocoding_backend,directions_backend,distance_matrix_backend,elevation_backend,places_backend&reusekey=true


생성된 키는 곧 사용되니 잘 보관하고 계시고요


2. 두번째로 뷰단에서 html 코드를 작성합니다.

<div id="map"></div> 이 부분이 구글맵이 들어갈 자리고, style안에 #map 부분은 구글맵 컨테이너에 대한 스타일을 지정하는 부분입니다.





3. script 쪽을 작성합시다.

getAddr() 함수 부분은 제 코드임으로 구글맵과는 연관성이 없음으로 빼주시면 되고요


function initMap() 부분에서 지도의 초기화화 그려주는 역할을 하게 됩니다.

<script async defer  src="https://maps.googleapis.com/maps/api/js?key=자신의API키를넣으세요&callback=initMap">

</script>

부분은 자신의 API키를 넣어서 map을 로딩하는 요청을 보낼 수 있도록하고, 해당 로딩이 완료되면 callback에 지정한 

initMap() 메서드로 콜백이 들어와 지도를 그려주게 됩니다.


나머지 마크에 대한 설정, 위도 경도 세팅에 대한 내용은 코드 주석을 참고하세요.



[    HandleBars 적용하기    ]

: HandleBars는 Javascript 라이브러리 중 하나로, 보통 AJAX로 가져온 데이터를 JQuery에서 문자열로 조합한다음에 append해주는

  불편함을 줄여주기 위해 주로 사용됩니다.

  특징)

1. 태그를 이용해서 구성을 잡아준다.(템플릿을 만든다.)
2. 템플릿 사이사이에 데이터가 들어갈 곳에 {{ 변수 }} 로 넣어놓는다.
3. 템플릿과 데이터를 연결시 {{}} 부분에 데이터가 들어가게 된다.

이때, {{#변수}} {{/변수}} 부분에는 배열과 같은 타입의 데이터의 길이가 들어와 그 길이만큼 반복작업을 수행하게 된다.

제일먼저 사용하기 위해, HandleBars js 라이브러리를 다운로드하고, 

<script src="" 를 이용해서 임포트 해주고 사용해야 한다. 이후 설명은 아래 소스코드에서 이어가겠습니다.



[ 스프링 환경 설정 정리 ]


1. [ index.jsp 메인 화면 띄우기 ]


1) WEB-INF/views 폴더 아래에 메인페이지로 쓸 index.jsp 페이지를 생성한다.

2) root 패키지에서 controller 패키지를 추가한 뒤, MyController.java 처럼 앞으로 컨트롤러로 이용할 자바 클래스를 만들고 아래처럼 소스코드를 작성한다.


@Controller // 스프링 웹 설정파일인 WEB-INF/spring/appServlet/servlet-context.xml 에서 <contextcomponent scan package로 지정된 패키지에서 탐색을 해 @Controller가 있는 클래스를 컨트롤러 클래스로 인식한다.

public class MyController {


// 메인 페이지 임으로 http://127.0.0.1:80/ 으로 요청시 index.jsp가 뜰 수 있도록 RequestMapping을 / 로 지정한다.

@RequestMapping(value="/")

public String homePage(){

return "index"; // servlet-context.xml 파일에서 ViewResolver에 매칭되는 부분으로 띄워줄 뷰 이름을 적는다.

}


3) 여기까지 작업시, http://localhost:80/root패키지/ 이렇게 해야 index.jsp가 뜨게 됨으로 http://localhost:80/ 만으로 뜨게하기 위해서 servers 탭의 서버를 더블클릭해 서버 설정창으로 들어가 modules 탭을 누르고 Edit를 눌러 Path 에 / 가 되도록 수정해 준다.


4) web.xml에 들어가 <welcome-file-list>index.jsp</welcome-file-list>를 추가해준다.

5) 앞으로 우리가만든 MyController로 쓸 것임으로 기존에 있던 HomeController 지워버리자.


: 결과) http://localhost:80/ 만으로 index.jsp 페이지가 뜨게 된다.

-----------------------------------------------------------


2. [ 한글 설정하기   ]


: JSP에서 filter를 이용해 controller로 들어가기 전에 request.setCharacterEncoding("UTF-8")을 해줬던 것처럼 스프링에선 이러한 역할을 하는 클래스를 제공한다. 이 클래스를 filter에 등록하고 url 매핑을 해주자.


1) web.xml에

<!-- [한글처리] -->

<filter>

<filter-name>encodingFilter</filter-name>

<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>

<init-param>

<param-name>encoding</param-name>

<param-value>UTF-8</param-value>

</init-param>

</filter>

<filter-mapping>

<filter-name>encodingFilter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>


코드를 추가해주자. 단! 주의할 점은, 반드시 servlet 태그 위에 위치해야한다.!! 서블릿보다 필터가 먼저 와야함!!

-----------------------------------------------------------

3. [ DB 설정하기 ]


1) [ DB DataSource 객체 생성 및 JUnit 단위 테스트하기]

a) pom.xml에 아래 라이브러리를 추가시킨다.


<!-- mysql -->

<dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<version>5.1.35</version>

</dependency>


<!-- spring-jdbc : datasource 객체 가져올 때 쓰임 -->

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-jdbc</artifactId>

<version>4.3.7.RELEASE</version>

</dependency>


<!-- spring-test : Junit을 이용해 스프링 코드를 테스트할 때 필요하다. -->

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-test</artifactId>

<version>4.3.7.RELEASE</version>

</dependency>


+ JUnit 버전을 4.10 이상으로 올려준다.!!

b) 스프링 환경설정 파일에 datasource 빈 객체를 등록해주어야 하는데 스프링에는 환경설정 파일이 두개가 있다.

- WEB-INF/spring/appServlet/servlet-context.xml

: 웹과 관련된 환경설정을 담당한다.

- WEB-INF/spring/root-context.xml

: 웹이외의 환경설정을 담당

ex) DB설정, 트랜잭션 설정 등등...

따라서, root-context.xml에 아래의 코드를 추가시킨다.

<!-- [데이터베이스 DataSource설정] -->

<bean id="datasource"class="org.springframework.jdbc.datasource.DriverManagerDataSource">

<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>

<property name="url" value="jdbc:mysql://localhost:3306/book_ex?useSSL=false&amp;serverTimezone=Asia/Seoul"></property>

<property name="username" value="root"></property>

<property name="password" value="dudfhd13"></property>

</bean>


c) 데이터 베이스에 연결이 잘 되어 Connection 객체가 잘 얻어지는지 JUnit테스트를 진행하자!


: 스프링에서 test를 진행할 때는 src/test 폴더에서 작업을 해야한다. 

: src/test/패키지경로/ 에다가 DataSourceTest.java 클래스를 하나 만들고

: 다음의 코드를 작성한다.


// JUnit은 WAS 구동없이 테스트를 빠르게 할 수 있기 때문에 단위테스트에 적합하다. 또한, 스프링은 많은 작업을 하고 테스트하려면 오류를 잡기 어려움으로 단위테스트에 신경을 쓰자.

// 스프링 관련 테스트임으로 이 작업을하려면 pom.xml에 spring-test 라이브러리가 추가되어 있어야한다.

@RunWith(SpringJUnit4ClassRunner.class) // 스프링을 로딩한다.

@ContextConfiguration(locations ={"file:src/main/webapp/WEB-INF/spring/**/*.xml"}) // 스프링 설정파일을 로딩한다.

public class DataSourceTest {


@Inject // byType으로 스프링 환경설정파일에 생성해둔 타입이 같은

// 녀석을 자동으로 주입한다.

private DataSource ds;

@Test // 단위테스트할 메서드에 작성한다.

public void testConection()throws Exception{

try(Connection con = (Connection) ds.getConnection()){

// datasource로부터 Connection 객체를 얻어 로그를 찍어보자.

// 메서드를 더블클릭하고 오른쪽 클릭->Run as->Junit Test를 클릭해 Connection 객체가 정상적으로 생성되었다면 DB연결에 성공한 것이다.

System.out.println("테스트 : " + con);

}catch(Exception e){

e.printStackTrace();

}

}

}



2. datasource 객체를 등록했음으로 [ MyBatis(마바) 설정을 하자 ]


a) pom.xml에 아래 라이브러리를 추가한다.

<!-- mybatis -->

<dependency>

<groupId>org.mybatis</groupId>

<artifactId>mybatis</artifactId>

<version>3.4.1</version>

</dependency>


<!-- mybatis-spring -->

<dependency>

<groupId>org.mybatis</groupId>

<artifactId>mybatis-spring</artifactId>

<version>1.2.2</version>

</dependency>


b) classpath경로인 src/resources 에 mapper 폴더를 만들고 그 안에 MyBatisMapper.xml 파일을 만들고 아래 코드를 작성한다.


<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE mapper

PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

"http://mybatis.org/dtd/mybatis-3-mapper.dtd">


<!-- namespace는 mapper를 구분하는 역할을 함으로 고유한 값으로 지정하지만 보통 프로젝트의 패키지명으로 짓지만 여기선 간단히 작성했다. -->

<mapper namespace="MapperNameSpace">


</mapper>


c) src/resources 아래에 mybatis-config.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>

  <typeAliases>

    <package name="org.zerock.domain"/>   

    <!-- 

    typeAliases에 패키지 속성으로 지정해 놓으면 mapper에서 resultType시 일일이 패키지명을 다 기재하지 않고 클래스명만 

    기재해 사용할 수 있다. import와 유사함

     --> 

  </typeAliases>

</configuration>


d) 스프링 환경설정 파일 root-context.xml에 가서 다음의 코드를 작성한다.


<!-- MyBatis의 SqlSessionFactory 빈을 등록한다. -->

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

<property name="dataSource" ref="datasource"></property>

<property name="configLocation" value="classpath:/mybatis-config.xml"></property>

<property name="mapperLocations" value="classpath:mapper/**/*Mapper.xml"></property>

// 여기서 **는 어떠한 폴더가 와도 괜찮다는 의미 파일명 앞에 *는 아무거나 파일명와도 대고 끝에 Mapper.xml로 끝나는 파일 모두를 의미한다.

</bean>

<!-- MyBatis에서 실질적으로 우리가 사용하게 될 sqlSessionTemplate 빈을 등록하자. -->

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">

<constructor-arg index="0" ref="sqlSessionFactory"></constructor-arg>

</bean>


e) JUnit 테스트 코드를 작성하.....자....라 하고 싶은데 너무 귀찮아서 생략하겠다.(잘될거다..)

-----------------------------------------------------------

4. [ AOP와 Transaction 설정 ]


: AOP란?

: OOP(객체지향)에서 기능별로 클래스를 나누었지만, 그럼에도 불구하고 나누어진 클래스들 안에서도 공통으로 사용되는 코드가 중복되기 때문에 여전히 한계점을 지니고 있다. 따라서, 이러한 비지니스 로직이 아닌 공통관심사(횡단관심사)를 proxy(대리자)에게 실행시점에 너가 대신해서 저 비지니스 로직을 실행하는 메서드 앞이나 뒤 혹은 앞 뒤 에 넣어줘!(주입해줘) 처럼 작동하는 방식을 AOP(Aspect Object Programming : 관점지향 프로그래밍)이라고 한다.

ex) 로그, 트랜잭션, 수행성능 테스트 메서드 등..


: AOP 필수 용어

- Advice : 공통관심사, 즉 공통으로 수행할 코드 자체를 의미한다.

- PointCut : 구체적으로 비지니스 로직의 어디에 들어갈지를 지정한다.(앞,뒤 혹은 둘다)

- JoinPoint : PointCut의 묶음이라고 생각하면 쉽다. Advice가 적용될 메서드들

- Aspect : AOP의 하나의 단위라고 보면 된다. 즉, Advice + PointCut을 의미함

- Target : Aspect가 적용될 클래스를 가리킨다.

- Proxy : AOP를 대신 적용하는 대신자 객체

- Weaving : AOP를 적용하는 것을 위빙이라 한다.


여튼... 여기까진 상식적인 얘기고... 설정하는 방법은


Transaction을 어노테이션을 사용하면 저절로 AOP가 적용이 된다. 따라서 트랜잭션을 사용한다는 건, AOP를 사용하는 것과 마찬가지임으로 AOP에서 필요로하는 라이브러리도 필요하다.

즉, AspectJ와 spring-tx를 추가해야한다.


1) pom.xml에 

<!-- 트랜잭션을 위한 spring-tx -->

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-tx</artifactId>

<version>4.3.7.RELEASE</version>

</dependency>

<!-- AspectJ -->

<dependency>

<groupId>org.aspectj</groupId>

<artifactId>aspectjrt</artifactId>

<version>${org.aspectj-version}</version>

</dependency>

가 추가 되어 있는지 확인하자. 일반적으로 legacy project로 생성시 aspectj는 있으니까 spring-tx만 추가해주도록 하자.

-----------------------------------------------------------

5. [ REST 설정 ]


: REST 방식

특정 url이 구체적인 특정 데이터를 의미하는 방식이다.

ex) /replies/123 + DELETE 방식 => 123번 댓글을 지운다.

ex) /replies/ + JSON 데이터 + POST 방식 => 댓글을 추가한다.

이러한 REST 방식은 AJAX와 연동되어서 자주 사용된다.


AJAX?

: 비동기적 통신 방법인데 JSP 화면처리가 싱글쓰레드 기반으로 동작하기 때문에 처리를 할 때 화면이 깜빡이는 것을 우리는 경험했을 것이다.

하지만, 좋아요 버튼 등을 클릭할 때 화면이 깜빡 거린다면...? 극혐

따라서, 싱글 쓰레드처럼 순차적으로 처리하는게 아니라 멀티쓰레드처럼 동시적으로 처리되는 것처럼 느껴지게 하는 방식인데 사실 동시적으로 처리된다기보단, 여유가 될 때 처리를 하도록 해놓고 그 작업을 기다리지 않고 다른 작업을 하다가 그 작업이 처리되면 그 결과를 받는 방식이다.


여튼간.... 서두가 길었고...

설정을 위해서는... JSON 객체를 자동으로 일반 객체로 일반 객체를 반환시

JSON 객체로 자동으로 변환해주기 위해서 필요한 라이브러리가 있다.


1) pom.xml에 jackson-databind 라이브러리를 추가하자.

<!-- REST, AJAX를 위한 라이브러리 -->

<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-databind</artifactId>

<version>2.8.5</version>

</dependency>


-----------------------------------------------------------

6. [ jdbc용 로그 설정해주기 ]


: 우리가 MyBatis를 이용해 작업을 할 때 최대 단점은?

console에 찍히는 것만 바서는 어떤 변수에 어떤 값이 set되는지 정확히 파악하기가 너무 어렵다. 인정??

따라서, log4jdbc log4j2 라이브러리를 이용하면 이러한게 친절히 다 나온다...


1) pom.xml에

<!-- jdbc log를 위한 라이브러리 -->

<dependency>

<groupId>org.bgee.log4jdbc-log4j2</groupId>

<artifactId>log4jdbc-log4j2-jdbc4</artifactId>

<version>1.16</version>

</dependency>

를 추가하자.


2) 이때 반드시!!!!!!,

- log4jdbc.log4j2.properties

- logback.xml

이 두 파일을 src/resources 아래에 넣어주어야 한다.

이 두 파일은 인터넷에서 구글링해서 찾던지... 기존에 환경설정 되있던대에서 가져와라

-----------------------------------------------------------

6. [ 스프링 MVC 파일 업로드 설정 ]


: 이미지 파일의 경우 이미지를 깨끗하게 축소할 수 있는 imgScalr 라이브러리를 사용한다. 이 라이브러리는 큰 이미지 파일을 고정된 크기로 변환할 때 편리하다.


1) pom.xml에 아래의 라이브러리를 추가한다.

<!-- 파일 업로드 관련 라이브러리 -->

<dependency>

<groupId>org.imgscalr</groupId>

<artifactId>imgscalr-lib</artifactId>

<version>4.2</version>

</dependency>


<dependency>

<groupId>commons-fileupload</groupId>

<artifactId>commons-fileupload</artifactId>

<version>1.3.1</version>

</dependency>

2) 웹에서 파일 업로드는 multipart/form-data라는 방식으로 데이터를 여러 조각으로 나누어서 전송한다. 이때, 스프링 MVC에서 파일 업로드를 처리하기 위해서 파일 업로드로 들어오는 데이터를 처리하는 객체가 필요한데 이 객체를 "multipartResolver"라고 한다. 

이 객체는 웹 설정과 관련이 있기 때문에 root-context.xml이 아니라 servlet-context.xml에 아래의 빈을 등록해 주어야 한다.


<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

<beans:property name="maxUploadSize" value="10485760"></beans:property>

</beans:bean>


파일 사이즈는 최대 10M정도로 설정했다.

-----------------------------------------------------------7. [ Spring MVC "인터셉터" 설정 ]


: 인터셉터는 JSP의 필터와 거의 유사하다. Controller로 요청이 들어가기 전에 한번 걸려주는 역할로

게시물 작성할 때 로그인이 필요하듯 게시글 접근 전에 로그인이 되어 있는지 유무등을 검사할 때 자주 사용된다.

하지만, Filter는 웹 애플리케이션 내에서 동작하므로 스프링의 Context를 접근하기 어렵지만, 인터셉터는 Spring의 Context내에서 존재함으로 Context 내의 모든 객체를 활용할 수 있다는 장점이 있다.



설정 방법)

1) 패키지에 SampleInterceptor 클래스를 하나 만들고 HandlerInterceptorAdapter를 extends(상속)한다.


package com.configure.javaStudy.interceptor;


import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;


public class SampleInterceptor extends HandlerInterceptorAdapter{


}


2) WEB-INF/spring/appServlet/servlet-context.xml에 다음 코드를 추가한다.


<!-- 인터셉터를 쓰기 위해 만들어논 SampleInterceptor 클래스를 객체 등록해놓자. -->

<!-- 인터셉터 등록하려면 Namespaces 탭에서 mvc 가 체크 되어 있어야만 함! -->

<!-- interceptor 태그 부분에는 mapping에 원하는 URI를 지정한다. 이 설정은 web.xml의 필터나 servlet의 설정과 

동일함으로 필요한 경로를 직접 지정하거나, **, * 와 같은 패턴을 적용할 수 있다.

-->

<!-- 아래처럼 작성하면 현재 프로젝트의 /doA 경로와 /doB 경로를 호출할 때 SampleInterceptor 클래스가 동작하도록 되어 있는 것! -->

<beans:bean id="sampleInterceptor"

class="com.configure.javaStudy.interceptor.SampleInterceptor">

</beans:bean>


<interceptors>

<interceptor>

<mapping path="/doA" />

<mapping path="/doB" />

<beans:ref bean="sampleInterceptor" />

</interceptor>

</interceptors>


-----------------------------------------------------------


여기까지가 스프링 기본 설정입니다.!! 수고하셨습니다.


- 작성자 : 황영롱 2017-04-28




쿠키와 세션을 이용한 자동 로그인 방식에 대해서 정리해 보겠습니다.


[    1. 쿠키와 세션이란?    ]


: 쿠키와 세션은 매우 유사하면서도 다른 특징을 지니고 있는데요.

- 공통점 : 사용자의 정보(데이터)를 저장할 때 이용된다.

- 차이점 : 

- 쿠키 : 1) 사용자의 로컬에 저장되었다가 브라우저가 요청시 왔다갔다하게 됨(보안에 취약)

     2) 세션과 달리 여러 서버로 전송이 가능함

     3) 세션이 브라우저 단위로 생성되어 브라우저 종료시 사라지는데 반해, 쿠키는 유효시간 설정을 할 수 있음. ex) 7일

- 세션 : 1) 서버에 데이터를 저장하여 쿠키에 비해 보안에 안전함

     2) 브라우저 단위로 생성됨 => 익스플로러를 켜고 크롬을 켜고 하면 각각 2개의 세션이 생성되는 것


[    2. why 쿠키와 세션을 이용한 로그인 처리를 하게 될까?    ]


: 세션은 위에서 설명한대로 기본 단위가 "웹 브라우저"입니다. 따라서, 웹 브라우저 종료시 소멸하게 되죠...

  그에 반해 쿠키는 사용자 PC에 저장되기 때문에 서버 요청시 전달되는 동안 네트워크 상에서 보안상 취약할 수는 있지만 유효시간을

  길게 설정할 수 있어 브라우저가 종료되는 것과 별개로 7일 30일 등 기간을 길게 설정할 수 있습니다.

  하지만, 

  그렇다고 쿠키에 로그인할 사용자의 정보를 담고 있는다면 정말 정말 너무 너무 보안상 취약할 것을 알 수 있겠죠?

  따라서, 자동 로그인을 구현할 때에는 "< 세션과 쿠키를 동시에 사용하는 것 >"이 바람직하다고 생각합니다.


[    3. 세션과 쿠키를 이용한 자동 로그인 구현에 대한 개요    ]


: 사용자가 로그인 폼에서 로그인을 할 당시, 자동로그인을 설정하겠다는 CheckBox를 클릭할 경우 사용자의 정보를 저장시키고 유효

기간을 설정한다는 것 까지는 알겠는데 그럼 도대체 어떤 사용자의 정보를 저장시켜 놓아야할까요?


먼저, 사용자가 로그인에 성공한 경우! -> 세션에 사용자 객체(UserVO)를 저장시켰었는데 앞에서 이 객체를 쿠키에 저장시킨다면, 굉장히 보안상 취약합니다. 비밀번호, 아이디 그 외 정보까지 UserVO에 들어 있었죠...

따라서, 로그인에 성공했을 때 사용자 DB 테이블에 sessionId와 유효시간 속성에 값을 지정하는 겁니다. 그리고 쿠키에는 세션Id를

넣어 놓는거죠... 그리고 "인터셉터"에서 해당 쿠키값이 존재하면 사용자 DB 테이블 내에서 유효시간 > now() 즉, 유효시간이 아직 

남아 있으면서 해당 세션 Id를 가지고 있는 사용자 정보를 검색해 해당 사용자 객체를 반환하는 겁니다.


당연히, 쿠키가 유효시간이 다되면 해당 자동완성 기능은 동작하지 않게 되고 다시 쿠키를 사용하겠다는 선택을 했을 때 동작하게 되겠죠

그럼, 다음으로 코드상에서 직접 한번 알아 봅시다.


[    4. 자동 로그인 실재로 구현해보기    ]


이번 장의 예제는 앞 게시글을 다 수행했다는 가정하에서 진행됩니다.


1) 먼저, UserController에서 로그인에 성공했으면서 사용자가 쿠키 사용 여부를 체크한 경우 -> 쿠키를 생성하고 세팅합시다.

코드를 살펴보면, service 객체의 login메서드를 통해 UserVO 객체를 반환하고 null이 아닌 경우 로그인에 성공했었죠?

이렇게 로그인에 성공되었으면서, 로그인 폼에서 checkBox를 선택한 경우(쿠키 사용하겠다는 체크박스) submit을 했을 때

UserVO 클래스 내의 useCookie 변수에 true/false로 값이 저장되어 들어 왔을 테니까

로그인에 성공했으면서 + 쿠키사용을 체크한 경우에 세션을 추가하도록 하는 부분이 앞에 코드에서 추가된 겁니다.

이때, 사용자 PC에서 쿠키를 보내는 경로가 "/" 로 설정함으로써 contextPath 이하의 모든 요청에 대해서 쿠키를 전송할 수 있

도록 설정한다는 것이고, 유효시간은 (초)단위 임으로 60 * 60 * 24 * 7로 세팅해주면, 로그인 후 해당 쿠키는 7일동안 유지될 수

있게 됩니다.(브라우저의 종료와 관계없이)

이때, 가장 중요하게 볼 부분이 쿠키에 UserVO 객체를 저장하는 것이 아니고!!!!(사실 쿠키는 문자열만 저장되기 때문에 가능하지도 않습니다.)

현재 브라우저의 세션 id를 저장해 놓는 겁니다.


그럼... 쿠키에 의해 자동로그인 기간은 제어가 될 것이고... 사용자는 해당 세션 id에 대한 정보를 가지고 있어야 겠죠??

따라서, 다음으로는 DB의 userTable에 세션Id와 유효시간 정보를 담을 수 있는 컬럼을 추가하도록 합시다.


2) DB userTable에 세션Id와 유효시간을 설정할 수 있는 컬럼을 만들기


3) userMapper.xml에 작업을 합시다.

1. 로그인 성공시 sessionId와 유효시간을 저장하는 부분 작성

2. 사용자가 이전에 로그인에 성공했었는지 확인하는 부분


4) userDAO 인터페이스와 userDAOImpl 클래스를 수정합시다.


5) UserService 인터페이스와 UserServiceImpl 클래스 수정하기


6) UserController에서 로그인 성공하고 쿠키사용 체크한 경우에 사용자 테이블에 세션id와 유효시간 처리해주기

아까 쿠키를 생성해서 세션id 저장한 부분 바로 아래에다가 사용자 테이블에도 세션 id와 유효시간을 저장해 놓아야함!


이후, AuthenticationInterceptor의 preHandle() 부분에서 

세션에 UserVO 객체가 null이 아닌 경우는 로그인 되어 있는 부분이니까 그대로 처리되도록 놔두고, 세션의 UserVO 객체가

null이지만, 쿠키가 null이 아닌 경우 쿠키에서 sessionId를 꺼내와서 사용자 객체를 반환받도록 작업할 것이다.


7) AuthenticationInterceptor에서 자동 로그인의 핵심 부분을 처리하자.

AuthenticationInterceptor에서도 DB에 접근해서 처리를 해야함으로 UserService를 필드변수에 선언해주고 자동 주입을 위해

@Inject해주었다.

또한 preHandle() 메서드에서 로그인 세션이 없으면서 WebUtils를 이용해 쿠키를 가져온 뒤 

로그인 세션이 없지만, loginCookie가 존재하는 경우 웹브라우저를 새로 켜고 로그인을 하지는 않았지만 이전에 로그인 하면서

쿠키 체크를 해논 유효기간이 남아 있는 경우임으로 service.checkUserWithSessionKey() 메서드를 통해 DB에서 유효기간이 남아있고

해당 세션id를 가지고 있는 사용자 정보를 받아온다.

그리고 마지막으로 해당 사용자 정보로 세션의 login을 세팅해주면 자동로그인에 필요한 모든 작업이 완료되었다.


8) 로그아웃 처리(UserController에서...)

로그아웃 처리를 깜빡하고 마지막이랬다.. 하하하....


9) /views/board/listPge.jsp 에서 로그아웃 버튼 하나 넣기

<a href="/logout">[로그아웃]</a> 하나 추가하자 로그아웃 버튼!


10) 결과 확인하기


1) 로그인 하면서 로그인 상태를 기억하시겠습니까를 클릭하고 로그인 하는 모습

2) 로그인 한 직후 모습


3) 로그인 했음으로 글 작성 바로 잘 된다.


4) 로그아웃 버튼을 눌렀다. -> 눌렀지만, 내가 로그아웃 처리후 redirect를 listPage로 했기 때문에 다시 현재 화면이 뜸


5) 하지만 분명한건 로그아웃하고 다시 글등록 누르면 로그인 폼으로 이동된다는거~


여기까지해서 캐시 + 세션 + 인터셉터를 응용한 자동 로그인 구현을 마치겠습니다.


ps) 코드로 배우는 스프링 웹 프로젝트 책을 공부하고 정리한 내용임을 다시 한번 언급드립니다.

pf) 개인적으로 공부한 내용을 정리했습니다. 여러 블로그도 참고하였지만 주로 참고한 것은 "코드로 배우는 스프링 웹 프로젝트"(남가람북스) 책을 공부한 후 참고해 작성하였음을 미리 말씀드립니다.

[    1. 인터셉터란?    ]


특정 URI로 요청시 Controller로 가는 요청을 가로채는 역할을 한다.


[    2. Interceptor와 JSP Filter의 차이??    ]


- 공통점 : 둘 다 Controller로 들어가는 요청을 가로채 특정 작업을 하기 위한 용도로 사용된다.

- 차이점 : 케어할 수 있는 영역(범위)가 다르다. Filter는 같은 웹 어플리케이션 내에서만 접근이 가능하며,

  Interceptor의 경우 스프링에서 관리되기 때문에 스프링내의 모든 객체에 접근이 가능하다.

-> JSP Filter의 경우 주로 한글처리에 이용되고

-> Interceptor의 경우 "로그인 처리"에 이용이 된다.

- why 로그인 처리에 이용?? )

: 만약 인터셉터를 이용하지 않고, 로그인 처리를 한다면, 게시물을 작성("/board/register"), 게시물 수정("/board/modify"),

 게시물 삭제("/board/delete") 등 모든 요청마다 Controller에서 session을 통해 로그인 정보가 남아 있는지를 확인하는 코드

 를 중복해서 입력해야 할 것이다. 

 하지만!, 인터셉터를 이용하면, A, B, C 작업(A,B,C 경로로 요청)을 할 경우에는 ~~Interceptor를 먼저 수행해 session에서 

 로그인 정보가 있는지 확인해 주는 역할을 한다면, 중복 코드가 확 줄어들 수 있을 것이다. 이러한 장점 때문에 사용!


[    3. 인터셉터를 지원하는 인터페이스와 클래스, 메서드    ]


: Spring에서 인터셉터를 지원하기 위해서 

- HandlerInterceptor 인터페이스

- HandlerInterceptorAdapter 추상 클래스를 지원한다. => 요녀석은 위의 인터페이스를 사용하기 쉽게 구현해 놓은 추상클래스.


=> 이때, HandlerInterceptorAdaptor는 3가지 메서드를 제공한다. 이 3가지 메서드를 오버라이딩해서 우리가 이용할 수 있다.

1) public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)

: Controller로 요청이 들어가기 전!!에 수행된다.

 : request, response, handler 등의 매개변수를 이용가능한데 우리가 아는 HttpServletRequest, HttpServletResponse,

  이고, 나머지 하나는 이 preHandle() 메서드를 수행하고 수행될 컨트롤러 메서드에 대한 정보를 담고 있는 handle

  이다.

2) postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,

ModelAndView modelAndView)

: 컨트롤러의 메서드의 처리가 끝나 return 되고 화면을 띄워주는 처리가 되기 직전에 이 메서드가 수행된다.

: ModelAndView 객체에 컨트롤러에서 전달해 온 Model 객체가 전달됨으로 컨트롤러에서 작업 후 

: postHandle() 에서 작업할 것이 있다면 ModelAndView를 이용하면 된다.

3) afterCompletion()

: 컨트롤러가 수행되고 화면처리까지 끝난 뒤 호출된다.



[    4. 인터셉터를 이용한 로그인 구현해 보기    ]



1) 로그인 처리를 위한 DB table을 만들자

: 여기서는 mysql을 이용해서 아래와 같이 테이블을 생성하였다.

: 이와 같이 id, pw, name, point 를 갖는 테이블을 만들고, 4개 정도 회원 정보를 insert 해 놓았다.


2) 로그인에 사용될 UserVO 클래스를 정의하자.

: DAO(Data Access Object)와 Controller의 ModelAttribute 부분에 사용하기 위해 UserVO를 id와 pwd, name, point 정보 등을

 넣어 아래와 같이 만들어 보자.


3) 로그인처리를 할 MyBatis userMapper.xml 을 작성하자.

     : 살펴보면, namespace명은 고유한 값을 지정하고 select 문에 호출시 전달받은 userId와 userPw를 통해

 DB에서 select한 뒤 해당 튜플 정보를 resultType = UserVO 로 반환한다.

 이때, #{userId} 부분은 전달 받은 객체에서 getter메서드를 통해 호출됨으로 UserVO의 getUserId() 를 호출해 자동 대입이 될

 것임을 알 수 있다.


4) UserDAO 인터페이스와 이를 구현한 UserDAOImpl 클래스를 작성하자.

먼저) UserDAO

UserDAOImpl 

UserDAOImpl은 UserDAO인터페이스를 구현하였고 DAO(Data Access Object)로 DB에 직접 접근하는 처리를 담당하는 

클래스이다. 이 클래스에서는 스프링 환경설정에서 만들어 놓은 mybatis 접근 객체 : 즉, SqlSessionTemplate 객체를 

이용해야함으로 sqlSession.selectOne("MyBatis Mapper의 namespace명.지정한id",넘겨줄 데이터 객체); 를 이용해서 

MyBatis를 통해 작성한 쿼리를 실행해 UserVO 객체를 얻어오는 작업을 해준다. 이때, @Inject나 @Autowired를 통해서

자동 주입을 한다. @Inject와 @Autowired는 ByType(타입이 같은 것을 자동 대입)하는 성질이며, 환경설정 파일에서 우리가

sqlSessionTemplate 빈을 생성해 놓았음으로 자동 대입이 가능하다.(이후, 이런 설명은 생략한다.)

마찬가지로 서비스 객체에서 DAO를 자동주입할 것임으로 UserDAOImpl 클래스 위에 @Repository 어노테이션을 붙였다.

이는, 이 클래스는 DAO 클래스에요! 라는 의미 + 자동으로 new해서 이 객체를 생성해 주세요라는 의미를 가지고 있다.

이후, Service 객체에서도 @Inject를 이용해 자동 주입하기 위해 생성해 놓는다. 단, @Component, @Repoisitory, 

@Service(서비스 자동생성) 등의 어노테이션을 인식하기 위해서는 환경설정 파일에서 <context:component-scan 부분을

잘 지정해야한다. 여기서 지정한 패키지 내에서 해당 어노테이션이 탐색되기 때문이다.


여하튼...

하나의 UserVO 객체가 반환되야 함으로 selectOne() 메서드를 사용한 것이고, 여러 객체를 담고 있는 List로 반환을 받을

경우 selectList() 메서드를 호출한다. 이런 내용은 기본 적인 내용이니까... 이해가 안되면 MyBatis 연동 부분을 살펴보길 바란다.



5) UserService 인터페이스와 UserServiceImpl 클래스를 정의한다.

별거 없다... 단순한 서비스 클래스이다...

 dao와 마찬가지로 @Service 어노테이션을 사용해 이 클래스는 서비스 클래스로 사용될 녀석이에요 !! + 자동 객체 생성을 해준다.

 컨트롤러에서 사용되어야 하니까... 역시 이 서비스 클래스가 있는 패키지 경로도 <context:component-scan 이 잘 지정되어 있어야하

 는 것은 당연하다...


자... 여기까지가 기본 세팅이다... 수고했다... 이제 본격적으로 로그인과 관련된 기능을 만들어 보자.

---------------------------------------------------------------------------------------------------------------------------------

6) UserController에서 로그인 처리를 작성해 보자.

: 먼저 로그인 요청시 로그인 폼 화면을 띄워주는 부분을 작성해야 한다.

  loginForm() 부분이 바로 그 부분! RequestMapping된 메서드의 return한 부분은 환경설정 파일의 ViewResolver에 맵핑

  됨으로 loginForm.jsp 파일을 view아래에 적당한 위치에 만든다음에 return을 잘해서 http://localhost:8080/login으로 요청시

  해당 웹페이지가 뜨도록 설정한다. 주소에 url 요청시 GET 방식임으로 GET으로 설정한다.


 그 다음은, 로그인을 처리하는 부분이다. 로그인을 처리할 때 해야할 작업은 2가지이다.

1) 새로 로그인 요청이 온다면, 기존에 세션에 저장되어 있던 이전 로그인 사용자의 정보를 제거해 주어야 한다.

2) 새로 로그인 하는 사용자의 정보가 일치한다면, 해당 사용자 정보를 DB에서 가져와 세션에 저장해 놓아야 한다.

 

service.login()을 수행->dao.login()->UserMapper.xml 순으로 수행되며 id와 pw가 일치하면 사용자 정보를 담은 UserVO를 반환

할 것이고, 틀렸다면 null이 반환될 것이다. 

null 이 아닐 경우, 세션을 통해 사용자 객체를 저장해 놓는다.


이때, 마지막으로 작업해 주어야 하는 부분이 어디로 이동하느냐에 대한 정보이다.

로그인에 실패할 경우 return "redirect:/login"; 을 통해 다시 로그인 폼으로 이동시켜 주어야 할 것이고,

로그인에 성공해 session에 사용자 객체를 저장한 이후에는 return "redirect:/board/listPage" 와 같이 최초 페이지로 이동시켜주어

야 할 것이다.


로그아웃 부분은 그냥 로그아웃 url 요청이 있으면 해당 브라우저의 세션을 통째로 날려버리면 된다.

물론, session.removeAttribute('변수명") 해서 하나를 날려도 되지만, 만약 날려야하는 정보가 여러개라면 일일이 하기 힘들 수 있

기 때문에 invalidate() 를 많이 사용한다.


===> 여기까지 작업을 완료했다면 로그인, 로그아웃 기능은 다 만든 것이다. 하지만, 가장 중요하게 처리해 주어야 할 부분이

남아 있다.!!


** 게시물을 등록, 수정, 삭제 등을 할 때는 반드시 로그인 된 사용자만 할 수 있도록 "인터셉터"를 적용하는 것 말이다.!! ***


7) AuthenticationInterceptor 를 만들어 게시물 등록, 수정, 삭제 요청 전에 로그인 여부를 확인하는 인터셉터 클래스를 작성하자.

먼저 Interceptor를 구현하기 위해서는 가장 쉽게 사용할 수 있는 것이 HandlerInterceptor 인터페이스를 구현해 놓은 HandlerInterceptorAdapter이다.


이를 extends해서 preHandle()과 postHandle() 을 오버라이딩한다. shift + alt + s + v 를 하면... 알죠...?

보통, 세션에 로그인 사용자 정보를 컨트롤러에서 저장하지 않고 여기서 저장할 것이었으면 postHandle() 부분에서 수행해 주어야 겠

지만, 우리는 간편하게.....(그냥 귀찮았어...ㅠㅠ) 컨트롤러에서 다 해버렸음으로 인터셉터 클래스에서는 preHandle() 메서드만 필요하다.


그럼, preHandle() 에서는 request에서 세션 객체를 가지고 오고 세션에서 login변수에 사용자 정보 객체가 담겨 있나 확인을 한다.

없으면?? response.sendRedirect("uri") 를 통해 uri경로로 날려버리면 된다. 사용자 정보가 세션에 없다는건 지금 로그인이 되어 있지

않다는 것을 의미함으로 response.sendRedirect("/login") 을 통해서 다시 로그인하는 폼으로 이동시켜버리면 된다.

단, 이때 이 메서드를 수행 후 return false를 한 이유는, 이 preHandle() 메서드의 return 이 의미하는 바는 true일 경우 preHandle() 

메서드를 수행한 뒤에 본래 요청한 Controller 를 수행한다는 의미이고 false를 주면 수행하지 않는다는 의미를 담고 있다.

따라서, 로그인 안된 상태에서 요청시 해당 컨트롤러로 요청이 가지 않도록 false를 해주자.


반대로 사용자 정보가 세션에 담겨 있는 경우에는 단순히 return true를 해줘서 본래 사용자가 요청했던 Controller의 RequestMapping부분이 수행될 수 있도록 해주면 된다.


8) 가장 중요한... servlet-context.xml에 인터셉터 설정 정보를 등록해야한다..


: 일단, 인터셉터 자체는 웹 관련 설정임으로 root-context.xml이 아닌 servlet-context.xml에 작성하자. 물론, 요소마다 설정파일을

  다 나누어서 설정하셨다면... 본인에 맞는 환경설정 파일에서 작업을 해주세요...


이때, 인터셉터 빈 객체를 생성해 놓고, 인터셉터 정보를 등록할 때 해당 객체와 어떤 url이 요청시에 인터셉터가 작동할지를

설정해 놓아야 한다.


여기서는 필요한 uri를 하나하나 지정했는대 이렇게 하지 않고, 전체 경로 /** 로 잡아 놓고

<exclue-mapping path="예외url" /> 을 지정해서 모든 페이지에 인터셉터를 적용하지만 예외는 ~다 라고 지정할 수도 있다.



9) 죄송합니다... 기존거에서 빠트린 부분이 있어 이부분 추가합니다. -> "자기 글만 수정, 삭제가 가능하도록"


여러분... 제가 이 부분을 빠트렸었내요 ㅠㅠ... 지금은 로그인만하면, 모든 게시글들을 다 삭제하고 수정할 수 있죠...

그러면 큰일나자나요... ㅠㅠ

자기가 작성한 글만 수정, 삭제를 할 수 있도록 구성해야합니다. 어떻게하면 좋을까요... 여러 방법이 있겠지만 전 2가지

정도 생각이 나내요... 인터셉터의 postHandler() 메서드를 통해서 요청 uri를 분석한다음 수정일 경우 postHandler()에서 전달

받은 ModelAndView로 사용자 정보를 전달받아서 하는 방법이 있겠고... 근대 이건 좀 귀찮고...


사실... 훨씬 간단한 방법이 있어요... 뷰 페이지에서 처리하면 말이죠...


머냐....


일단, 수정버튼과 삭제 버튼은 해당 글을 클릭했을 때만 뜬답니다. 그렇죠? 그러면 글을 읽는 /views/board/read.jsp 페이지에서

<c:if test="${login.userId eq boardVO.writer}">


</c:if>

인 경우에만 수정하고 삭제 버튼이 보이도록하면 어떨까요? 세션에 login 변수로 userVO 객체를 담고 있으니까

현재 로그인한 사용자 userId와 현재 게시글의 boardVO 에서 writer를 비교해서 eq(같은경우)만 수정과 삭제 버튼이 보이고

목록으로가는 버튼은 항상 보이도록 하는거죠.

그러면 별다른 처리없이 안보이면 사용자는 수정과 삭제버튼은 클릭조차 할 수 없기에 모든 처리가 끝나게 되죠..

아래처럼 /views/board/read.jsp 페이지를 수정합시다.

아직 댓글에 대한 처리를 안했지만, 댓글도 마찬가지로 이와 같은 방식으로 해주면 되겠죠? 


10) 결과를 확인해 보자!!!

메인페이지)



로그인이 되어 있지 않은 상태에서 글등록을 누르면, 로그인 폼 페이지로 이동된다.


로그인에 성공하면, 게시글 첫 페이지로 이동된다.



이제 로그인이 된 상태임으로 "글등록"을 누르면 정상적으로 글 등록 폼으로 이동된다.


그리고 마지막으로, 로그인은 한 상태지만 다른 사람의 글을 클릭했을 때 수정과 삭제가 보이지 않고 목록으로 버튼만 보이는 화면입니다.



이상입니다.


열심히 끝까지 읽어주셔서 감사합니다.



+ Recent posts