Study Web Development

3월 10일

이전으로

Spring

10. springPage

Member

Controller
  1. MemberController 클래스에 다음의 메서드 추가
	// 비밀번호 변경 폼
	@GetMapping("/member/changePassword.do")
	public String formChangePassword() {
		// Tiles 설정 반환
		return "memberChangePassword";
	}

	// 비밀번호 변경 처리
	@PostMapping("/member/changePassword.do")
	public String submitChangePassword(@Valid MemberVO memberVO, BindingResult result, HttpSession session) {
		logger.info("<<비밀번호 변경 처리>> : " + memberVO);
		
		// 유효성 검증 결과 오류가 있으면 폼 호출
		if(result.hasFieldErrors("now_passwd") || result.hasFieldErrors("passwd")) {
			return formChangePassword();
		}
		
		Integer user_num = (Integer)session.getAttribute("user_num");
		memberVO.setMem_num(user_num);
		
		// 세션에 저장된 회원 번호를 이용해 회원 정보를 자바빈에 담아 반환
		MemberVO db_member = memberService.selectMember(user_num);
		// DB에서 읽어온 비밀번호와 사용자가 입력한 비밀번호가 불일치하면 폼 호출
		if(!db_member.getPasswd().equals(memberVO.getNow_passwd())) {
			result.rejectValue("now_passwd", "invalidPassword");
			return formChangePassword();
		}
		
		// 비밀번호 수정
		memberService.updatePassword(memberVO);
		
		return "redirect:/member/myPage.do";
	}
	
	// 회원 탈퇴 폼
	@GetMapping("/member/delete.do")
	public String formDelete() {
		// Tiles 설정 반환
		return "memberDelete";
	}
	
	// 회원 탈퇴 처리
	@PostMapping("/member/delete.do")
	public String submitDelete(@Valid MemberVO memberVO, BindingResult result, HttpSession session) {
		logger.info("<<회원 탈퇴>> : " + memberVO);
		
		// 유효성 검증 결과 오류가 있으면 폼 호출
		if(result.hasFieldErrors("id") || result.hasFieldErrors("passwd")) {
			return formDelete();
		}
		
		Integer user_num = (Integer)session.getAttribute("user_num");
		memberVO.setMem_num(user_num);
		
		// 아이디와 비밀번호 일치 여부 검증
		try {
			// 세션에 저장된 회원 번호를 이용해서 DB에 저장된 회원 정보를 자바빈에 담아 반환
			MemberVO db_member = memberService.selectMember(memberVO.getMem_num());
			boolean check = false;
			
			if(db_member!=null && memberVO.getId().equals(db_member.getId())) { // DB에서 읽어온 아이디와 사용자가 입력한 아이디가 일치하면
				// 비밀번호 일치 여부 검증
				check = db_member.isCheckedPassword(memberVO.getPasswd());
			}
			if(check) { // 인증에 성공하면
				// 회원 탈퇴
				memberService.deleteMember(memberVO.getMem_num());
				// 로그아웃
				session.invalidate();
				
				return "redirect:/main/main.do";
			}
			// 인증 실패시
			throw new AuthCheckException();
		}
		catch(AuthCheckException e) {
			result.reject("invalidIdOrPassword");
			
			return formDelete();
		}
	}
	
	// 이미지 출력
	@RequestMapping("/member/photoView.do")
	public ModelAndView viewImage(HttpSession session) {
		Integer user_num = (Integer)session.getAttribute("user_num");
		MemberVO memberVO = memberService.selectMember(user_num);
		
		ModelAndView mav = new ModelAndView();
		mav.setViewName("imageView");
		mav.addObject("imageFile", memberVO.getPhoto());
		mav.addObject("filename", memberVO.getPhoto_name());
		
		return mav;
	}
  1. MemberAjaxController 클래스에 다음의 메서드 추가
	@RequestMapping("/member/updateMyPhoto.do")
	@ResponseBody
	public Map<String, String> processProfile(MemberVO memberVO, HttpSession session) {
		Map<String, String> map = new HashMap<String, String>();
		
		Integer user_num = (Integer)session.getAttribute("user_num");
		if(user_num==null) { // 로그인되어 있지 않은 경우
			map.put("result", "logout");
		}
		else { // 로그인되어 있는 경우
			memberVO.setMem_num(user_num);
			memberService.updateProfile(memberVO);
			
			// 이미지를 업로드한 후 세션에 저장된 user_photo 값 변경
			session.setAttribute("user_photo", memberVO.getPhoto());
			
			map.put("result", "success");
		}
		
		return map;
	}
  1. 새 클래스 MemberAdminController 생성
package kr.spring.member.controller;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.validation.Valid;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import kr.spring.member.service.MemberService;
import kr.spring.member.vo.MemberVO;
import kr.spring.util.PagingUtil;

@Controller
public class MemberAdminController {
	private static final Logger logger = LoggerFactory.getLogger(MemberAdminController.class);
	
	@Autowired
	private MemberService memberService;
	
	// 회원 관리 목록
	@RequestMapping("/member/admin_list.do")
	public ModelAndView process(@RequestParam(value="pageNum", defaultValue="1") int currentPage, 
			@RequestParam(value="keyfield", defaultValue="") String keyfield,
			@RequestParam(value="keyword", defaultValue="") String keyword) {
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("keyfield", keyfield);
		map.put("keyword", keyword);
		
		// 총 회원 수 또는 검색된 회원 수
		int count = memberService.selectRowCount(map);
		
		logger.info("<<회원 관리>> : " + count);
		
		// 페이지 처리
		PagingUtil page = new PagingUtil(keyfield, keyword, currentPage, count, 20, 10, "admin_list.do");
		
		map.put("start", page.getStartCount());
		map.put("end", page.getEndCount());
		
		List<MemberVO> list = null;
		if(count>0) {
			list = memberService.selectList(map);
		}
		
		ModelAndView mav = new ModelAndView();
		mav.setViewName("admin_memberList");
		mav.addObject("count", count);
		mav.addObject("list", list);
		mav.addObject("pagingHtml", page.getPagingHtml());
		
		return mav;
	}
	
	// 회원 정보 수정 폼
	@GetMapping("/member/admin_update.do")
	public String form(@RequestParam int mem_num, Model model) {
		MemberVO memberVO = memberService.selectMember(mem_num);
		
		model.addAttribute("memberVO", memberVO);
		
		// Tiles 설정 반환
		return "admin_memberModify";
	}
	
	// 회원 정보 수정 폼에서 전송된 데이터 처리
	@PostMapping("/member/admin_update.do")
	public String submit(@Valid MemberVO memberVO, BindingResult result) {
		logger.info("<<관리자 회원 수정 처리>> : " + memberVO);
		
		// 유효성 검증 결과 오류가 있으면 폼 호출
		if(result.hasErrors()) {
			return "admin_memberModify";
		}
		
		// 회원 정보 수정
		memberService.updateByAdmin(memberVO);
		
		return "redirect:/member/admin_list.do";
	}
}
JSP
  1. member 폴더에 새 JSP 파일 memberChangePassword.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<!-- 중앙 컨텐츠 시작 -->
<script type="text/javascript" src="${pageContext.request.contextPath}/resources/js/jquery-3.6.0.min.js"></script>
<script type="text/javascript">
	$(function() {
		// 비밀번호 변경 검사
		$('#passwd').keyup(function() {
			if($('#confirm_passwd').val()!='' && $('#confirm_passwd').val()!=$(this).val()) {
				$('#message_id').text('비밀번호 불일치').addClass('error-color');
			}
			else if($('#confirm_passwd').val()!='' && $('#confirm_passwd').val()==$(this).val()) {
				$('#message_id').text('비밀번호 일치').removeClass('error-color');
			}
		}); // end of keyup
		$('#confirm_passwd').keyup(function() {
			if($('#passwd').val()!='' && $('#passwd').val()!=$(this).val()) {
				$('#message_id').text('비밀번호 불일치').addClass('error-color');
			}
			else if($('#passwd').val()!='' && $('#passwd').val()==$(this).val()) {
				$('#message_id').text('비밀번호 일치').removeClass('error-color');
			}
		}); // end of keyup
		
		$('#change_form').submit(function() {
			if($('#now_passwd').val().trim()=='') {
				alert('현재 비밀번호를 입력하세요');
				$('#now_passwd').val('').focus();
				return false;
			}
			if($('#passwd').val().trim()=='') {
				alert('변경할 비밀번호를 입력하세요');
				$('#passwd').val('').focus();
				return false;
			}
			if($('#confirm_passwd').val().trim()=='') {
				alert('변경할 비밀번호 확인을 입력하세요');
				$('#confirm_passwd').val('').focus();
				return false;
			}
			if($('#passwd').val()!=$('#confirm_passwd').val()) {
				$('#message_id').text('비밀번호 불일치').addClass('error-color');
				return false;
			}
		}); // end of submit
	});
</script>
<div class="page-main">
	<h2>비밀번호 변경</h2>
	<form:form modelAttribute="memberVO" action="changePassword.do" id="change_form">
		<form:errors element="div" cssClass="error-color"/>
		<ul>
			<li>
				<form:label path="now_passwd">현재 비밀번호</form:label>
				<form:password path="now_passwd"/>
				<form:errors path="now_passwd" cssClass="error-color"/>
			</li>
			<li>
				<form:label path="passwd">변경할 비밀번호</form:label>
				<form:password path="passwd"/>
				<form:errors path="passwd" cssClass="error-color"/>
			</li>
			<li>
				<label for="confirm_passwd">변경할 비밀번호 확인</label>
				<input type="password" id="confirm_passwd">
				<span id="message_id"></span>
			</li>
		</ul>
		<div class="align-center">
			<form:button>전송</form:button>
			<input type="button" value="홈으로" onclick="location.href = '${pageContext.request.contextPath}/main/main.do';">
		</div>
	</form:form>
</div>
<!-- 중앙 컨텐츠 끝 -->
  1. member 폴더에 새 JSP 파일 memberDelete.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<!-- 중앙 컨텐츠 시작 -->
<div class="page-main">
	<h2>회원 탈퇴</h2>
	<form:form modelAttribute="memberVO" action="delete.do" id="delete_form">
		<form:errors element="div" cssClass="error-color"/>
		<ul>
			<li>
				<form:label path="id">아이디</form:label>
				<form:input path="id"/>
				<form:errors path="id" cssClass="error-color"/>
			</li>
			<li>
				<form:label path="passwd">비밀번호</form:label>
				<form:password path="passwd"/>
				<form:errors path="passwd" cssClass="error-color"/>
			</li>
		</ul>
		<div class="align-center">
			<form:button>전송</form:button>
			<input type="button" value="홈으로" onclick="location.href = '${pageContext.request.contextPath}/main/main.do';">
		</div>
	</form:form>
</div>
<!-- 중앙 컨텐츠 끝 -->
  1. member 폴더에 새 JSP 파일 admin_memberList.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!-- 중앙 컨텐츠 시작 -->
<script type="text/javascript" src="${pageContext.request.contextPath}/resources/js/jquery-3.6.0.min.js"></script>
<script type="text/javascript">
	$(function() {
		$('#search_form').submit(function() {
			if($('#keyword').val().trim=='') {
				alert('검색어를 입력하세요!');
				$('#keyword').val('').focus();
				return false;
			}
		}); // end of submit
	});
</script>
<div class="page-main">
	<h2>회원 목록 (관리자용)</h2>
	<form action="admin_list.do" id="search_form" method="get">
		<ul class="search">
			<li>
				<select name="keyfield">
					<option value="1" <c:if test="${param.keyfield==1}">selected</c:if>>아이디</option>
					<option value="2" <c:if test="${param.keyfield==2}">selected</c:if>>이름</option>
					<option value="3" <c:if test="${param.keyfield==3}">selected</c:if>>이메일</option>
				</select>
			</li>
			<li>
				<input type="search" name="keyword" id="keyword" value="${param.keyword}">
			</li>
			<li>
				<input type="submit" value="검색">
				<input type="button" value="목록" onclick="location.href = 'admin_list.do';">
			</li>
		</ul>
	</form>
	<c:if test="${count==0}">
	<div class="result-display">표시할 회원 정보가 없습니다.</div>
	</c:if>
	<c:if test="${count>0}">
	<table>
		<tr>
			<th>아이디</th>
			<th>이름</th>
			<th>전화번호</th>
			<th>이메일</th>
			<th>가입일</th>
			<th>권한</th>
		</tr>
		<c:forEach var="member" items="${list}">
		<tr>
			<td>
				<c:if test="${member.auth==0}">${member.id}</c:if>
				<c:if test="${member.auth>0}"><a href="admin_update.do?mem_num=${member.mem_num}">${member.id}</a></c:if>
			</td>
			<td>${member.name}</td>
			<td>${member.phone}</td>
			<td>${member.email}</td>
			<td>${member.reg_date}</td>
			<td>
				<c:if test="${member.auth==0}">탈퇴</c:if>
				<c:if test="${member.auth==1}">정지</c:if>
				<c:if test="${member.auth==2}">일반</c:if>
				<c:if test="${member.auth==3}">관리</c:if>
			</td>
		</tr>
		</c:forEach>
	</table>
	<div class="align-center">${pagingHtml}</div>
	</c:if>
</div>
<!-- 중앙 컨텐츠 끝 -->
  1. member 폴더에 새 JSP 파일 admin_memberModify.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!-- 중앙 컨텐츠 시작 -->
<div class="page-main">
	<h2>회원 정보 수정</h2>
	<form:form modelAttribute="memberVO" action="admin_update.do" id="modify_form">
		<form:hidden path="mem_num"/>
		<form:errors element="div" cssClass="error-color"/>
		<ul>
			<li>
				<label>회원 권한</label>
				<c:if test="${memberVO.auth<3}">
				<form:radiobutton path="auth" value="1"/>정지
				<form:radiobutton path="auth" value="2"/>일반
				</c:if>
				<c:if test="${memberVO.auth==3}">
				관리
				<form:hidden path="auth"/>
				</c:if>
			</li>
			<li>
				<form:label path="name">이름</form:label>
				<form:input path="name"/>
				<form:errors path="name" cssClass="error-color"/>
			</li>
			<li>
				<form:label path="phone">전화번호</form:label>
				<form:input path="phone"/>
				<form:errors path="phone" cssClass="error-color"/>
			</li>
			<li>
				<form:label path="email">이메일</form:label>
				<form:input path="email"/>
				<form:errors path="email" cssClass="error-color"/>
			</li>
			<li>
				<form:label path="zipcode">우편번호</form:label>
				<form:input path="zipcode"/>
				<input type="button" onclick="sample2_execDaumPostcode()" value="우편번호 찾기">
				<form:errors path="zipcode" cssClass="error-color"/>
			</li>
			<li>
				<form:label path="address1">주소</form:label>
				<form:input path="address1"/>
				<form:errors path="address1" cssClass="error-color"/>
			</li>
			<li>
				<form:label path="address2">상세 주소</form:label>
				<form:input path="address2"/>
				<form:errors path="address2" cssClass="error-color"/>
			</li>
		</ul>
		<div class="align-center">
			<form:button>전송</form:button>
			<input type="button" value="홈으로" onclick="location.href = '${pageContext.request.contextPath}/main/main.do';">
		</div>
	</form:form>
<!-- 우편번호 스크립트 시작 -->
<!-- iOS에서는 position:fixed 버그가 있음, 적용하는 사이트에 맞게 position:absolute 등을 이용하여 top,left값 조정 필요 -->
<div id="layer" style="display:none;position:fixed;overflow:hidden;z-index:1;-webkit-overflow-scrolling:touch;">
<img src="//t1.daumcdn.net/postcode/resource/images/close.png" id="btnCloseLayer" style="cursor:pointer;position:absolute;right:-3px;top:-3px;z-index:1" onclick="closeDaumPostcode()" alt="닫기 버튼">
</div>

<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<script>
    // 우편번호 찾기 화면을 넣을 element
    var element_layer = document.getElementById('layer');

    function closeDaumPostcode() {
        // iframe을 넣은 element를 안보이게 한다.
        element_layer.style.display = 'none';
    }

    function sample2_execDaumPostcode() {
        new daum.Postcode({
            oncomplete: function(data) {
                // 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.

                // 각 주소의 노출 규칙에 따라 주소를 조합한다.
                // 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
                var addr = ''; // 주소 변수
                var extraAddr = ''; // 참고항목 변수

                //사용자가 선택한 주소 타입에 따라 해당 주소 값을 가져온다.
                if (data.userSelectedType === 'R') { // 사용자가 도로명 주소를 선택했을 경우
                    addr = data.roadAddress;
                } else { // 사용자가 지번 주소를 선택했을 경우(J)
                    addr = data.jibunAddress;
                }

                // 사용자가 선택한 주소가 도로명 타입일때 참고항목을 조합한다.
                if(data.userSelectedType === 'R'){
                    // 법정동명이 있을 경우 추가한다. (법정리는 제외)
                    // 법정동의 경우 마지막 문자가 "동/로/가"로 끝난다.
                    if(data.bname !== '' && /[동|로|가]$/g.test(data.bname)){
                        extraAddr += data.bname;
                    }
                    // 건물명이 있고, 공동주택일 경우 추가한다.
                    if(data.buildingName !== '' && data.apartment === 'Y'){
                        extraAddr += (extraAddr !== '' ? ', ' + data.buildingName : data.buildingName);
                    }
                    // 표시할 참고항목이 있을 경우, 괄호까지 추가한 최종 문자열을 만든다.
                    if(extraAddr !== ''){
                        extraAddr = ' (' + extraAddr + ')';
                    }
                    //(주의)address1에 참고항목이 보여지도록 수정
                    // 조합된 참고항목을 해당 필드에 넣는다.
                    //(수정) document.getElementById("address2").value = extraAddr;
                
                } 
                //(수정) else {
                //(수정)    document.getElementById("address2").value = '';
                //(수정) }

                // 우편번호와 주소 정보를 해당 필드에 넣는다.
                document.getElementById('zipcode').value = data.zonecode;
                //(수정) + extraAddr를 추가해서 address1에 참고항목이 보여지도록 수정
                document.getElementById("address1").value = addr + extraAddr;
                // 커서를 상세주소 필드로 이동한다.
                document.getElementById("address2").focus();

                // iframe을 넣은 element를 안보이게 한다.
                // (autoClose:false 기능을 이용한다면, 아래 코드를 제거해야 화면에서 사라지지 않는다.)
                element_layer.style.display = 'none';
            },
            width : '100%',
            height : '100%',
            maxSuggestItems : 5
        }).embed(element_layer);

        // iframe을 넣은 element를 보이게 한다.
        element_layer.style.display = 'block';

        // iframe을 넣은 element의 위치를 화면의 가운데로 이동시킨다.
        initLayerPosition();
    }

    // 브라우저의 크기 변경에 따라 레이어를 가운데로 이동시키고자 하실때에는
    // resize이벤트나, orientationchange이벤트를 이용하여 값이 변경될때마다 아래 함수를 실행 시켜 주시거나,
    // 직접 element_layer의 top,left값을 수정해 주시면 됩니다.
    function initLayerPosition(){
        var width = 300; //우편번호서비스가 들어갈 element의 width
        var height = 400; //우편번호서비스가 들어갈 element의 height
        var borderWidth = 5; //샘플에서 사용하는 border의 두께

        // 위에서 선언한 값들을 실제 element에 넣는다.
        element_layer.style.width = width + 'px';
        element_layer.style.height = height + 'px';
        element_layer.style.border = borderWidth + 'px solid';
        // 실행되는 순간의 화면 너비와 높이 값을 가져와서 중앙에 뜰 수 있도록 위치를 계산한다.
        element_layer.style.left = (((window.innerWidth || document.documentElement.clientWidth) - width)/2 - borderWidth) + 'px';
        element_layer.style.top = (((window.innerHeight || document.documentElement.clientHeight) - height)/2 - borderWidth) + 'px';
    }
</script>
<!-- 우편번호 스크립트 끝 -->
</div>
<!-- 중앙 컨텐츠 끝 -->
DAO
  1. kr.spring.member.dao 패키지에 새 XML 파일 MemberMapper.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">  
<mapper namespace="kr.spring.member.dao.MemberMapper"> 
	<select id="selectList" parameterType="map" resultType="memberVO">
		SELECT 
			* 
		FROM (SELECT 
				a.*, ROWNUM AS rnum 
			FROM (SELECT 
					* 
				FROM spmember LEFT JOIN spmember_detail 
				USING (mem_num) 
				<where>
					<if test="keyword!='' and keyfield==1">
						id LIKE '%' || #{keyword} || '%'
					</if>
					<if test="keyword!='' and keyfield==2">
						name LIKE '%' || #{keyword} || '%'
					</if>
					<if test="keyword!='' and keyfield==3">
						email LIKE '%' || #{keyword} || '%'
					</if>
				</where>
				ORDER BY reg_date DESC) a)
		<![CDATA[
		WHERE rnum>=#{start} AND rnum<=#{end}
		]]>
	</select>
	<select id="selectRowCount" parameterType="map" resultType="integer">
		SELECT
			COUNT(*)
		FROM spmember LEFT JOIN spmember_detail
		USING (mem_num) 
		<where>
			<if test="keyword!='' and keyfield==1">
				id LIKE '%' || #{keyword} || '%'
			</if>
			<if test="keyword!='' and keyfield==2">
				name LIKE '%' || #{keyword} || '%'
			</if>
			<if test="keyword!='' and keyfield==3">
				email LIKE '%' || #{keyword} || '%'
			</if>
		</where>
	</select>
</mapper>

프로젝트 설정

Tiles
Definition
  1. tiles-def 폴더의 member.xml에서 <tiles-definitions> 태그 사이에 다음의 내용을 추가
	<definition name="memberChangePassword" extends="main">
		<put-attribute name="title" value="비밀번호 변경"/>
		<put-attribute name="body" value="/WEB-INF/views/member/memberChangePassword.jsp"/>
	</definition>
	<definition name="memberDelete" extends="main">
		<put-attribute name="title" value="회원 탈퇴"/>
		<put-attribute name="body" value="/WEB-INF/views/member/memberDelete.jsp"/>
	</definition>
	<!-- 관리자 -->
	<definition name="admin_memberList" extends="main">
		<put-attribute name="title" value="회원 목록 (관리자용)"/>
		<put-attribute name="body" value="/WEB-INF/views/member/admin_memberList.jsp"/>
	</definition>
	<definition name="admin_memberModify" extends="main">
		<put-attribute name="title" value="회원 정보 수정 (관리자용)"/>
		<put-attribute name="body" value="/WEB-INF/views/member/admin_memberModify.jsp"/>
	</definition>
XML
  1. servlet-context.xml에서 <interceptor> 태그 사이에 다음의 내용을 추가
			<mapping path="/member/changePassword.do"/>
			<mapping path="/member/delete.do"/>
  1. servlet-context.xml에서 <beans:beans> 태그 사이에 다음의 내용을 추가
	<!-- BLOB 타입 이미지 표시 -->
	<beans:bean id="imageView" class="kr.spring.view.ImageView"/>
CSS
  1. src/main/webapp/resources 폴더에 하위 폴더로 images 폴더 생성하고 face.png를 해당 폴더로 이동
  2. css 폴더의 style.css 하단에 다음의 내용을 추가
/* 메인 */
a:link,a:visited {
	text-decoration: none;
	color: #000000;
}
a:hover {
	text-decoration: underline;
	color: #999999;
}

/* My Page */
.mypage-div {
	width: 48%;
	float: left;
	padding: 5px;
}
.my-photo {
	object-fit: cover;
	object-position: top;
	border-radius: 50%;
}

/* 목록 */
form#search_form {
	border: none;
}
ul.search {
	width: 500px;
	list-style: none;
	padding: 0;
	margin: 0 auto;
}
ul.search li {
	margin: 0 0 9px 0;
	padding: 0;
	display: inline;
}

/* 등록, 수정 폼 */
input[type="text"],input[type="password"],input[type="email"] {
	width: 200px;
}
#register_form input[id="id"] {
	width: 113px;
}
input[id="zipcode"] {
	width: 95px;
}
  1. css 폴더의 style.css에서 .result-display의 스타일 규칙을 다음처럼 수정
.result-display {
	width: 400px;
	height: 200px;
	margin: 50px auto;
    /* 테두리 */
	border: 1px solid #000;
	/* 하위 요소를 수직으로 쌓을 수 있는 공간을 만듦 */
	display: flex;
	/* 세로 정렬 */
  	align-items: center;
  	/* 가로 정렬 */
  	justify-content: center;
}

View

  1. 새 패키지 kr.spring.view 생성하고 새 클래스 ImageView 생성
package kr.spring.view;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.springframework.web.servlet.view.AbstractView;

public class ImageView extends AbstractView {

	@Override
	protected void renderMergedOutputModel(Map<String, Object> model,
			HttpServletRequest request, HttpServletResponse response)
					throws Exception {
		
		byte[] file = (byte[]) model.get("imageFile");
		String filename = (String) model.get("filename");
		
		String ext = filename.substring(
				           filename.lastIndexOf("."));
		if(ext.equalsIgnoreCase(".gif")) {
			ext = "image/gif";
		}else if(ext.equalsIgnoreCase(".png")) {
			ext = "image/png";
		}else {
			ext = "image/jpeg";
		}

		response.setContentType(ext);
		response.setContentLength(file.length);

		String userAgent = request.getHeader("User-Agent");
		boolean ie = userAgent.indexOf("MSIE") > -1;
		String fileName = null;
		if (ie) {
			fileName = URLEncoder.encode(filename, "utf-8");
		} else {
			fileName = new String(filename.getBytes("utf-8"),
					"iso-8859-1");
		}
		response.setHeader("Content-Disposition", "attachment; filename=\""
				+ fileName + "\";");
		response.setHeader("Content-Transfer-Encoding", "binary");
		
		OutputStream out = response.getOutputStream();

		InputStream input = new ByteArrayInputStream(file);
		IOUtils.copy(input, out);
		out.flush();
		out.close();
		input.close();
	}

}

다음으로