✅ 레이아웃 페이지 만들기

#️⃣ tiles 라이브러리 가져와서 설정하기

 

1)  Maven Repository - Global Repository에 다운받아둔게 있다면 pom.xml dependency에서 검색해서 가져올 수 있음.

(*홈페이지에서 dependency 코드가져와서 pom.xml에 붙여넣어도 됨)

 

라이브러리를 설정하고 저장(ctrl+s)하면 알아서 관련 라이브러리가 같이 들어온걸 볼 수 있음

 

 

2) layout.jsp파일에 설정하기

uri 설정해주기

 

#️⃣ URI와 URL이란?

👉 URI(Uniform Resource Identifier)와 URL(Uniform Resource Locator)은 웹에서 리소스를 식별하고 참조하기 위한 용어
👉 하지만 URI는 리소스를 식별하는 통합된 개념이고, URL은 리소스의 위치를 지정하는 구체적인 형태의 URI이다.
(+URN은 리소스의 이름을 나타내는 URI 중 하나)


⏩ URI (Uniform Resource Identifier):

URI는 인터넷에서 리소스(문서, 이미지, 동영상 등)를 식별하는 통합된 방식을 나타내는 개념.
URI는 URL과 URN(Uniform Resource Name)의 상위 개념.
URI는 해당 리소스를 유일하게 식별하며, 리소스의 위치나 이름을 나타낼 수 있다.

⏩ URL (Uniform Resource Locator):

URL은 URI의 하위 개념 중 하나로, 리소스의 위치를 나타낸다.
URL은 특정 리소스의 주소를 나타내며, 프로토콜(예: HTTP, FTP), 도메인 이름, 포트 번호, 경로 등의 정보로 리소스에 접근할 수 있는 경로를 지정한다.
예를 들어, "https://www.example.com/images/pic.jpg"는 URL로써 이미지 파일 "pic.jpg"가 위치한 주소를 나타낸다.


URN (Uniform Resource Name):

URN은 리소스의 이름을 나타내는 URI의 한 유형.
URN은 해당 리소스의 위치가 아니라 이름을 기반으로 식별된다. 예를 들어, "urn:isbn:0451450523"는 ISBN 번호를 가리키는 URN이다!

 

 

 

3) layout.jsp 각 속성에 위치 꽂아넣기

 

 


 

 

#️⃣ tiles.xml에 설정한 value를 getAsString으로 가져오기

1) tiles.xml 에서 value를 설정

 

2) layout.jsp에 가져오기 (title:getAsString)

👉 설정해둔 title을 기존의 title태그 자리에 넣어준다. 이때 기존에 있던 title태그들은 모두 지워준다!

 

 


 

Tiles ViewResolver 설정하기

👉 요청을 받고 찾아서 반환해주려면 Resolver가 tiles 지시서를 찾도록 설정해줘야 한다


#️⃣ Resolver란?

리졸버(Resolver)는 컴퓨터 네트워크에서 도메인 이름을 IP 주소로 변환하거나, 반대로 IP 주소를 도메인 이름으로 변환하는 작업을 수행하는 시스템 또는 소프트웨어. 인터넷에서 정보를 찾거나 네트워크 연결을 설정할 때 중요한 역할을 하며, 웹 브라우징, 이메일 송수신, 파일 전송 등의 다양한 네트워크 활동에서 사용된다.

두 가지 주요 유형의 리졸버가 있다:

✔ DNS 리졸버 (Domain Name System Resolver):

DNS 리졸버는 도메인 이름을 IP 주소로 변환하거나, 반대로 IP 주소를 도메인 이름으로 변환하는 역할을 수행.
컴퓨터나 장치가 도메인 이름을 사용하여 특정 웹 사이트에 접속하려고 할 때, DNS 리졸버가 해당 도메인 이름을 해당 웹 사이트의 IP 주소로 변환하여 네트워크 통신을 가능하게 한다.


리버스 리졸버 (Reverse Resolver):

리버스 리졸버는 IP 주소를 도메인 이름으로 변환하는 역할을 한다.
주로 보안 로그 등에서 IP 주소가 기록되는 경우, 이 IP 주소를 실제 도메인 이름으로 변환하여 어떤 도메인에서 해당 IP 주소로 접근하려고 했는지 파악하는 데 사용된다.




#️⃣ Dispatcher-Servlet과 Resolver의 관계?

Dispatcher Servlet과 Resolver는 웹 애플리케이션 개발에서 사용되는 요소로, 서로 다른 역할을 수행한다.

✔ Dispatcher Servlet:
Dispatcher Servlet은 Spring 프레임워크에서 웹 요청을 처리하고 적절한 핸들러(Controller)에게 전달하는 역할. 즉 
클라이언트로부터의 HTTP 요청을 받아서 이를 처리할 적절한 컨트롤러나 핸들러로 라우팅해주는 중심 역할이다.
웹 애플리케이션의 컨트롤러와 뷰 리졸버를 관리하며, MVC 패턴을 따르는 웹 애플리케이션에서 사용된다.


✔ Resolver:
Resolver는 Spring 프레임워크에서 뷰(View)를 결정하고 해당 뷰를 반환하는 역할. 
뷰 리졸버는 뷰 이름을 실제 뷰 객체로 변환하여 Dispatcher Servlet이 해당 뷰를 클라이언트에게 반환할 수 있게 한다.


✔ Dispatcher Servlet과 Resolver의 관계:
Dispatcher Servlet은 클라이언트의 요청을 받아서 적절한 컨트롤러로 전달하고, 컨트롤러가 처리한 결과를 뷰 리졸버를 통해 실제 뷰로 변환하여 클라이언트에게 반환한다.
이 때, 뷰 리졸버가 뷰의 이름을 실제 뷰 객체로 변환하는 역할을 수행한다. 이를 통해 Dispatcher Servlet은 클라이언트와 컨트롤러 간의 연결을 담당하고, Resolver는 뷰를 결정하고 처리한 결과를 클라이언트에게 보여주는 역할을 수행한다.
따라서 Dispatcher Servlet과 Resolver는 웹 애플리케이션의 요청-처리-응답 흐름에서 함께 동작하여 완전한 웹 페이지 생성과 제공을 담당한다.


 

👉 Dispatcher-servlet.xml 설정

jsp를 처리할 라이브러리가 없어서 찾질못하는 중
pom.xml에 가서 jstl라이브러리를 추가해준다


#️⃣ JSTL이란?
JSTL(JavaServer Pages Standard Tag Library)은 Java 웹 애플리케이션에서 웹 페이지를 구성하고 동적으로 데이터를 처리하기 위한 표준 태그 라이브러리.
JSTL은 JSP(JavaServer Pages)에서 사용되며, HTML 코드 안에 자바 코드를 혼합하지 않고 웹 페이지를 더 깔끔하고 유지보수 가능한 방식으로 작성하는 데 도움을 준다.

 

 

 

 

 

✅ 페이지 공통분모 집중화의 필요성

 

 

👉 기존 jsp에서 제공하는 기능을 활용하자면,  헤더를 잘라내서 include라는 폴더 안에서 공통분모를 저장하고 관리하고 있었다. (즉 헤더부분 자료는 공통부분에서 뽑아다 씀 마치 피그마의 컴포넌트처럼!)

👉 하지만 페이지를 만들때마다 집중화된 부분을 일일히 연결해줘야 하므로 이것조차 불편함이 있음. 따라서 전체적인 틀을 집중화하는 방법을 찾게 됨.  ▶ 라이브러리(tiles)를 활용한다

 

 

👉 tiles를 활용해서 공통분모를 모은 레이아웃을 만든다

 

 


 

✅ 페이지 모듈 분리하기

: 페이지에서 공통적인 부분과 아닌 부분을 분리하기

👉 즉 고객센터의 경우 layout 파일을 기준으로 header, footer, aside, visual을 붙이게 된다.

 

모듈분리가 완료되면 공통분모와 main이 완벽히 떨어져나간다

 

 


 

✅ tiles 지시서 작성하기

👉 스프링이 반환값을 판단할 때, 기본 jsp보다 tiles의 우선순위가 높다

 

 

 

#️⃣ tiles지시서에 넣는 두가지 지시사항

1. 어떤 이름일때 어떤 조합으로 붙일것인지

2. 레이아웃 어느위치에 붙일지

 

 

 

Apache Tiles - Framework - Creating Tiles Pages

Creating and using Tiles pages After installing and learning some of Tiles concepts, it is time to create some pages. Here you will find the steps to create reusable Tiles pieces and complete pages. Create a template Let's take the classic layout page stru

tiles.apache.org

👉  프론트에서도 화면 구현이 가능해지면서 현재는 tiles가 잘 이용되지 않고있다. 백에서 합쳐주는 라이브러리를 사용할 일이 적어졌으므로 apache에서도 17년도 이후로 해당 내용을 retired시켰음.

 

 

 

 

 

 

 

 

 

 

 

* 공지사항 & detail controller 추가 및 jsp 수정 파트는 모두 동일해서 공지사항으로만 정리함*

 

 

[1] html 파일 > jsp 파일로 변경

세팅만 되어있다면야 jsp화 시키는건 쉽다. 쉬운데... 컨트롤러랑 이어주는게 문제.

 

 

 

 

[2] 공지사항 컨트롤러 생성

: 현재 뷰는 WEB-INF - view - notice 아래에 각각 list.jsp, detail.jsp로 생성되어 있다. ▶ 해당 뷰 경로로 컨트롤러가 반환할 수 있게 잡아주기.

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 ListController implements Controller {

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		ModelAndView mv = new ModelAndView("notice/list"); // 뷰가 있는 정보 전달
		//mv.setViewName("/WEB-INF/view/notice/list.jsp"); 
		return mv;
	}
}

👉 컨트롤러를 통해 값을 반환받아 뷰로 내보낼 경로를 지정해주기.

 

 

 

[3] 컨트롤러 만든 후엔 dispatcher servlet에 url - mapping✨

❣❣❣❣❣ 이 부분 까먹어서 detailController 만들고 jsp 수정해놓고도 연결이 안됨! ❣❣❣❣❣

dispatcher-servlet.xml 에 빈 추가. id는 컨트롤러에 설정해준 매핑과 일치시키면 된다. 여긴 디테일까지 추가된 화면을 캡쳐해왔네.

 

 

 

[4] 서버 구동 - JSP 반환되는지 확인하기

👉 서버를 돌려서 공지사항으로 가기 위한 메뉴에 커서를 올렸을때, 왼쪽 하단에 경로가 list.html로 뜬다면, 이건 컨트롤러를 통해 jsp로 반환되는게 아니라 기존에 정적화면으로 반환되던 html과 연결되어 있다는 뜻.

list.html > list

👉 해당 페이지인 index.jsp 내부에 가서 해당 버튼과연결된 하이퍼링크를 확인하고, jsp로 연결해준다. 이동된 화면에서도 주소창에 html이 아닌지 더블체크 해주기.

 

 

 


✅ 즉, dispatcher-servlet.xml에서는 url을 보고 어떤 객체를 끌어올건지 세팅해놓는 것.

✅  각 jsp에서는 버튼을 눌러 이동시에 원하는 url로 들어가 있는지 확인하는 것.

 

<순서>

url ▶ dispatcher-servlet로 해당 클래스(컨트롤러)로 이동 ▶ Controller에서 정리된 값과 뷰 전달 ▶ 모델 받고 jsp 로 view 송출 (한바퀴 완) ▶ 클라에서 원하는 버튼 누르기 ▶ url 전송 ▶ dispatcher-servlet ▶ Controller ▶ 값 받아서 view보내기 (두바퀴 완) 

 


 

 

[5] 뷰 집중화 필요성

: index.jsp의 헤더에 있는 '고객센터' 메뉴의 list.html 을 jsp로 변경해놓았는데, list.jsp, detail.jsp 에 있는 '고객센터'메뉴(list.html)도 모두 일일히 jsp로 수정해줘야하는 상황이 발생한다.

👉 maven library 기능을 사용하여 뷰 페이지 집중화 진행할 예정.

 

 

[1] Spring MVC

: 톰캣은 받은 데이터를 모두 전송. 톰캣의 web.xml에 있던 url 값들은 Dispatcher서블릿에 설정된다.

: Dispatcher는 여러 컨트롤러중, 필요한 컨트롤러를 사용하여 model을 반환받고, dispatcher은 model, view값을 jsp에 넘겨서 결과페이지를 반환하게 한다.

: Dispatcher 역할의 집중화 = mvc모델의 발전

POJO: plain old object java 순수한 자바파일

여기서 Maven은 Java Resources ▶ Libraries Maven Dependencies 에 있는 라이브러리를 참조하고(+pom.xml),  Tomcat은 로컬에서  src main webapp WEB-INF lib 에 있는 라이브러리를 참조한다.(+web.xml)

 

 


* sts설치시 압축폴더오류: .jar > .zip 으로 변경했고, 윈도우 내장프로그램으론 .contents 압축을 풀 수 없어서 반디집 깔아서 사용함 

* spring에서 javaEE 버튼 없을때 추가하는 법

 

Eclipse에서 Java EE 추가방법

Eclipse 우측상단에 JavaEE 보기가 없는 경우 Open Perspective를 클릭하여 Java EE가 보이는지 ...

blog.naver.com


 

[2] sts초기설정

2-1) web.xml 저장하기

: pom.xml에 web,xml파일이 없다고 에러가 뜸. 설치해둔 톰캣 폴더에서 web.xml파일을 가져와서 WEB-INF폴더 만들고 안에 넣어주기.

 

2-2) 라이브러리 자바 버전 (jdk) 변경

:maven pom.xml - overview tap - properties - create

:maven이 사용하는 폼파일 변경

: pom.xml 수정 후에는 update잊지말기.

이렇게 해도 되고 소스 직접 써도 됨

	<maven.compiler.source>1.8</maven.compiler.source>
  	<maven.compiler.target>1.8</maven.compiler.target>

 

 

 

2-3) UTF-8 로 설정 변경

STS 설정에서 CSS, HTML,JSP 설정 변경
프로젝트 설정 변경

 

2-4) jsp 파일 생성 ▶ jsp와 관련된 톰캣 라이브러리 추가

: The superclass "javax.servlet.http.HttpServlet" was not found on the Java Build Path ▶ maven repository에서 maven 버전에 맞게 dependency받아다 pom.xml에 저장하기

: mvc 활용을 위해 spring web mvc dependency도 추가하기

 

2-5) 그외

: window-web browser-default system browser 설정 (내장브라우저 쓰기싫으면!)

: jsp에서 톰캣 최초 실행시 해당 버전 & 홈디렉토리 연결 (bin폴더가 있는 화면까지 가야 홈디렉토리)

 

 

 



[3] Spring Dispatcher를 Front Controller로 설정하기

: 웹프로젝트에 spring을 얹는 과정

: spring-webmvc.jar 안에 DispatcherServlet.class가 있으므로 이를 다운받아야 한다 ▶ maven repository에서 spring webmvc dependency 추가

libraries - maven dependencies에서 확인 가능

 

3-1) Dispatcher-Servlet.xml 생성하기

: 컨트롤러는 순수 저바 코드로 남겨두고, 서블릿에 관한 url 은  web.xml이 모든 URL을 받도록 설정한다. (받아서 dispatcher Servlet.xml에게 넘김)

 

org.springframework.beans.factory.BeanDefinitionStoreException: IOException parsing XML document from ServletContext resource [/WEB-INF/dispatcher-servlet.xml];

👉 web.xml에서 넘겼는데 받아서 정리할 -servlet.xml 이 없으므로 500에러 반환. 이때 내가지정한 dispatcher이름(지정어)+servlet.xml(예약어)이 WEB-INF폴더 안에 있어주면된다.

 

//web.xml
 <!--  dispatcherServlet 설정 -->
  <servlet>
  <servlet-name>dispatcher</servlet-name> //이름을 dispatcher로 지어서 오류에도 dispatcher로 나옴
  	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  </servlet>

<!-- 모든 url mapping -->
<servlet-mapping>
	<servlet-name>dispatcher</servlet-name>
	<url-pattern>/</url-pattern>
</servlet-mapping>
//url 패턴을 /*로 설정하면, 다 돌고 mvc값을 가져온 리턴값이 다시 dispatcher로 돌아가서 일치하는 애를 찾게 되면서, 해당 jsp가 없으면 무한루프로 갇히게 된다.
//그냥 '/'로 설정하면 리턴값이 dispatcher에 없을때 그대로 출력한다

dispatcher library가 사용하는 설정파일 dispatcher-servlet.xml

 

* dispatcher-servlet 설정파일 - IoC container - Core Technologies

 

Index of /spring-framework/docs

 

docs.spring.io

해당 기본 틀을 복사해서 써도 되고 기존에 사용한 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">

<!-- mvc는 id자리에 url이 사용된다. 요청이 url로 올거니까.  -->
<!-- url요청이 오면 class에 담긴 컨트롤러가 객체화되어 컨테이너에 담긴다 -->
    <bean id="/index" class="com.newlecture.web.controller.IndexController">
    </bean>
</beans>

 

 

 

3-2) IndexController 작성

: servlet & jsp 때는 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {equest.getRequestDispatcher("/WEB-INF/view/index.jsp").forward(request, response);} 으로 값을 받고 다른페이지로 연결하는데만 중심을 두었다면, (url은 @WebServlet("/index") 로 컨트롤러에 붙어있었다)

 

: 디스패쳐를 통한 컨트롤러는 dispatcher Servlet이 url을 모두 가지고 있으므로 (디스패쳐가 forward하는 담당), public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {...}는 model and view 객체를 반환한다

//index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1> hey ${data }</h1> //model and view가 여기 값으로 들어간다
</body>
</html>

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

<!-- dispatcher는 id자리에 url이 사용된다. 요청이 url로 올거니까.  -->
<!-- url요청이 오면 class에 담긴 컨트롤러가 객체화되어 컨테이너에 담긴다 -->
    <bean id="/index" class="com.newlecture.web.controller.IndexController">
    </bean>
</beans>
---------------------------------
//IndexController.java

package com.newlecture.web.controller;

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

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

//컨테이너에서 꺼내서 가지고 있는 기능을 호출하기 - 컨트롤러마다 Handler Request를 구현해놓아야  dispatcher Servlet과 연결된다
public class IndexController implements Controller {

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		ModelAndView mv = new ModelAndView();
		mv.addObject("data","hello Spring MVC");  //(attribute name, value)
		mv.setViewName("/WEB-INF/view/index.jsp"); //절대경로로 지정해야 혼선이없다
		return mv;
	}
}

 

컨트롤러를 다녀오지않은, jsp화면 자체를 반환
dispatcher - controller - dispatcher를 통해 반환한 화면 (dispatcher에 /index로 매핑되어있음)

 

👉 Index.jsp - IndexController.java가 한묶음인데 사용자가 index.jsp페이지를 호출할 수 있는게 문제가 된다. (위와같이 출력값이 완전히 다르다) 

 

 

 

 

[4] view 페이지 & 서버의 상대경로와 절대경로

: 일단 Index.jsp는 WEB-INF아래에 넣어놓으면 사용자가 접근할 수 없다.

: 하지만 리턴하는 jsp의 경로는 절대경로, 상대경로에 따라 다르게 리턴되므로 주의해야 한다.

 

✔ 상대경로

mv.setViewName("WEB-INF/view/index.jsp"); ('/' 루트가 빠짐)

👉 dispatcher의 ID는 실제 폴더 여부에 상관없이 그 아이디로 요청시 객체를 담는다.

    <bean id="/aa/index" class="com.newlecture.web.controller.IndexController">
    > /aa/WEB-INF/view/index.jsp로 검색하게 된다 > 화면이 없다는 404 에러 발생

 

✔ 절대경로

mv.setViewName("/WEB-INF/view/index.jsp");

    <bean id="/aa/index" class="com.newlecture.web.controller.IndexController">
    > 정해져있는 /WEB-INF/view/index.jsp로만 반환한다

디스패쳐, 컨트롤러 모두 절대경로로 되어있어야 알맞게 나온다
경로에서 프로젝트명 숨기기
컨텍스트 명을 바꿨을 경우는 서버에 와서 기존 prj를 지울것.

 

 

[5] View Resolver

: 반복되는 view의 일부분을 알아서 붙여준다

: 컨트롤러의 ModelAndView에 jsp파일명만 쓸 수 있다! 👉 반복되는 '/WEB-INF/view/    .jsp'는 view resolver의 역할.

ModelAndView mv = new ModelAndView("index");

 

* view resolver가 없으면 무한루프로 찾다가 오류메시지가 나온다

메시지 Circular view path [index]: would dispatch back to the current handler URL [/index] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)

	<!-- view resolver -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/view/"></property>
		<property name="suffix" value=".jsp"></property>
	</bean>

 

[6] 정적파일 서비스하기

: 루트에 맞게 저장해놓은 로고 이미지가 웹에서 출력이 되지않는다 그이유는? 👉 스프링이 정적 파일을 제공하지 않도록 막아놓았기 때문.  

//web.xml
<!-- 모든 url mapping -->
<servlet-mapping>
	<servlet-name>dispatcher</servlet-name>
	<url-pattern>/</url-pattern> // /* 는 jsp까지도 정적파일 불허 , /는 jsp는 정적파일 허용
</servlet-mapping>

: 정적인 리소스(이미지, html, js, css 등) get 요청시 > mvc 지시어로 된 매핑으로 받는다. 이때 저장된 폴더 자체를 루트로 인식하게 해서 그곳에서만 정적 파일을 뒤지도록 한다.

http://localhost:8080/images/logo.png

// 사용자가 mapping="/images/"로 시작하는 url을 요청하면 location="/images/"에서 찾게 해주겠다
<mvc:resources location="/images/" mapping="/images/**" />
<mvc:resources location="/js/" mapping="/js/**" />
<mvc:resources location="/css/" mapping="/css/**" />

// 여러요소가 많으므로 일반적으로는 resource폴더를 따로 만들어서 몰아둔다
<mvc:resources location="/resource/" mapping="/resource/**" />

// 이렇게 몰아두면 html의 이미지 링크마다 /resource/를 모두 붙여야하므로, 실무에서는 정적파일용 폴더를 루트로 해서 몰아두는 편.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc" //mvc로 확장
    http://www.springframework.org/schema/mvc
    https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- (도메인 이름을 포함한) 파일 이름과 스키마파일 -->

<mvc:resources location="/static/" mapping="/**" />

 

[1] AOP란?

: Aspect Oriented Programming 관점 지향 프로그래밍

: 스프링이 아닌 방법론. 이를 구현할때 스프링이 도움을 주는 것

 

✔ OOP (Object Oriented Programming)

: 사용자가 원하는 업무 기반 로직

👉 이 프로그램을 구현/테스트 하기 위한 주업무 외의 로직이 필요. 이는 개발자, 운영자가 필요를 위해 만드는 것. (사용자는 모름) 

 

✔ Concern

핵심기능 : Primary (Core) Concern 는 객체로 만들어짐. 실제 업무는 메서드로 만들어짐

부가기능 (관점이 다른 로직) : Cross-cutting Concern (예: 로그처리 (성능테스트 /요구권한 등), 보안처리, 트랜잭션 처리

 

✔ AOP

주 업무 틀만 만들어놓고 (소스코드 없음) Proxy를 만들어서 부가기능을 사용하게 한다

AOP의 구현 방식

👉 Proxy를 꽂고 빼는 부분은 Spring DI로 해결 가능 (코드 변경과 수정 문제)

 

 

 

[2] 순수 자바로 AOP구현해보기

: 프로그램은 proxy - 핵심 업무 - proxy의 순서를 거쳐 출력된다

: 필요에 따라 핵심기능만 출력할수도 있고 (exam.total()) 곁다리가 포함된 값을 출력할 수도 있다 (proxy.total())

: 해당 객체 내의 다른 메서드도 호출 가능하다 (proxy.avg())

//Program.java proxy가 있는 부가기능
public class Program {
	public static void main(String[] args) {
		Exam exam = new NewlecExam(1, 1, 1, 1);

		// proxy만들기
		// loader-실제업무호출, interface's' - 클래스를 배열형태로 전달, h-곁다리 업무를 꽂는부분(핸들러)
		Exam proxy = (Exam) Proxy.newProxyInstance(NewlecExam.class.getClassLoader(), new Class[] { Exam.class },
				new InvocationHandler() {

					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

						long start = System.currentTimeMillis();

						// 실제업무 호출 코드 작성
						Object result = method.invoke(exam, args); // args가 실제 업무 반환

						long end = System.currentTimeMillis();

						String message = (end - start) + "ms 시간이 걸렸습니다";
						System.out.println(message);

						return result;
					}
				});

		System.out.printf("total is %d%n", proxy.total()); // 곁다리 + 핵심기능. 걸린시간 + total is 4
		//System.out.printf("total is %d%n", exam.total()); // 핵심기능 출력 total is 4
		System.out.printf("agv is %3.2f%n", proxy.avg()); // 필요시 avg에도 곁다리를 끼워넣을수 있음
	}
}
//NewlecExam.java
public class NewlecExam implements Exam {
	private int kor;
	private int eng;
	private int math;
	private int com;

	//기본생성자 생략

	public NewlecExam(int kor, int eng, int math, int com) {
		this.kor = kor;
		this.eng = eng;
		this.math = math;
		this.com = com;
	}

	@Override
	public int total() { // 메인 메서드 1
		//long start = System.currentTimeMillis(); //곁다리
		
		int result= kor + eng + math + com;
		
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//long end = System.currentTimeMillis(); //곁다리
		
		//String message = (end-start)+"ms 시간이 걸렸습니다";
		//System.out.println(message);
		
		return result;
	}

	@Override
	public float avg() { // 메인 메서드 2
		//곁다리 업무 들어가는 공간
		float result = total() / 4.0f;
		//곁다리 업무 들어가는 공간
		return result;
	}

 

 


[3] 스프링 AOP로 AroundAdvice구현하기

: 스프링에서는 부가기능업무를 4가지로 구분하여 제공

 

✔ BeforeAdvice - 핵심기능 앞에만 부가기능 붙이기

 AfterReturnningAdvice- 핵심기능 결과값 반환 뒤에 부가기능 붙이기

 AfterThrowingAdvice - 예외처리

 AroundAdvice - 앞뒤 모두 부가기능 붙이기

 

 

3-1) xml을 통해 만들기

✔ xml에 생성해야 할 것

① 객체 생성 코드

② proxy객체 생성 코드

③ 부가 기능 코드

//setting.xml
	<!-- Exam exam = new NewlecExam(1, 1, 1, 1); Exam객체를 target으로 명명함 -->
	<bean id="target" class="spring.aop.entity.NewlecExam" p:kor="1" p:eng="1" p:math="1" p:com="1" />
	<bean id="logAroundAdvice" class="spring.aop.advice.LogAroundAdvice"/>
	
	<!--Exam proxy = (Exam) Proxy.newProxyInstance -->
	<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
		<property name="target" ref="target" /> //setExam과 마찬가지
		<property name="interceptorNames"> <!-- 타겟에 해당되는 것, 핸들러 설정 -->
			<list>
				<value>logAroundAdvice</value> //핵심 기능 값을 불러오는 invoke를 클래스 생성으로 구현예정
			</list>
		</property>
	</bean>
</beans>

 

 

 AroundAdvice만들기

//LogAroundAdvice.java
package spring.aop.advice; //xml에 넣은 패키지명과 동일

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class LogAroundAdvice implements MethodInterceptor {

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		long start = System.currentTimeMillis();

		Object result = invocation.proceed(); //타겟을 따로 넘겨받는일 없이 깔끔하게 처리. method.invoke(exam, args); 이부분임.

		long end = System.currentTimeMillis();

		String message = (end - start) + "ms 시간이 걸렸습니다";
		System.out.println(message);

		return result;
	}
}

-----------------------
//Program.java는 기능을 모두 넘겨주고 출력 기능만 남아있다.
public class Program {
	public static void main(String[] args) {
		
		ApplicationContext context = 
			//	new AnnotationConfigApplicationContext(NewlecDIConfig.class);
				new ClassPathXmlApplicationContext("spring/aop/setting.xml");
		
		Exam proxy = (Exam) context.getBean("proxy");
		
		System.out.printf("total is %d%n", proxy.total()); // 곁다리 + 핵심기능. 걸린시간 + total is 4
		System.out.printf("agv is %3.2f%n", proxy.avg());

👉 여기서 Program & xml의 proxy를 exam으로 모두 교체하면 사용자 입장에서는 proxy값이 핵심기능인지 부가기능인지 알 수 없다.

👉 만약 xml에서 proxy를 모두 지우고 핵심기능인 target 을 exam으로 변경하면 핵심기능만 출력된다.

 

 

 

[4] BeforeAdvice 구현하기

: xml 내에 설정한 핸들러는 list로 값을 받으므로 BeforeAdvice, AroundAdvice 두개 다 설정해서 사용할 수 있다.

//setting.xml
	<!-- Exam exam = new NewlecExam(1, 1, 1, 1); -->
	<bean id="target" class="spring.aop.entity.NewlecExam" p:kor="1" p:eng="1" p:math="1" p:com="1" />
	<bean id="logAroundAdvice" class="spring.aop.advice.LogAroundAdvice"/>
	<bean id="logBeforeAdvice" class="spring.aop.advice.LogBeforeAdvice"/>
	
	<!--Exam proxy = (Exam) Proxy.newProxyInstance  -->
	<bean id="exam" class="org.springframework.aop.framework.ProxyFactoryBean">
		<property name="target" ref="target" /> 
		<property name="interceptorNames"> 
			<list>
				<value>logBeforeAdvice</value> //순서를 바꾸면 Around start값 먼저 출력된다
				<value>logAroundAdvice</value>
			</list>
		</property>
	</bean>
</beans>
//LogBeforeAdvice.java
package spring.aop.advice;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

public class LogBeforeAdvice implements MethodBeforeAdvice {

	@Override
	public void before(Method method, Object[] args, Object target) throws Throwable {
		System.out.println("--BeforeAdvice 앞에서 실행될 로직--");
	}
}

start랑 end인데 안고쳤네...

 

 

 

[5] AfterReturnningAdvice /Throwing Advice

<!-- Exam exam = new NewlecExam(1, 1, 1, 1); -->
//예외처리를 위해 kor값 변경
	<bean id="target" class="spring.aop.entity.NewlecExam" p:kor="200" p:eng="1" p:math="1" p:com="1" />
	<bean id="logAroundAdvice" class="spring.aop.advice.LogAroundAdvice"/>
	<bean id="logBeforeAdvice" class="spring.aop.advice.LogBeforeAdvice"/>
	<bean id="logAfterReturnningAdvice" class="spring.aop.advice.LogAfterReturnningAdvice"/>
	<bean id="logAfterThrowingAdvice" class="spring.aop.advice.LogAfterThrowingAdvice"/>
	
	<!--Exam proxy = (Exam) Proxy.newProxyInstance  -->
	<bean id="exam" class="org.springframework.aop.framework.ProxyFactoryBean">
		<property name="target" ref="target" /> 
		<property name="interceptorNames"> 
			<list>
				<value>logBeforeAdvice</value>
				<value>logAroundAdvice</value>
				<value>logAfterReturnningAdvice</value>
				<value>logAfterThrowingAdvice</value>
			</list>
		</property>
	</bean>

 

 

 

5-1) AfterReturnningAdvice

: 반환값이 온 후 작동

: 반환값을 가지고 구현할것이 있으면 returnValue로 가능. 매개변수에 추가된게 BeforeAdvice와의 차이점

//LogAfterReturnningAdvice.java
package spring.aop.advice;

import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;

public class LogAfterReturnningAdvice implements AfterReturningAdvice {

	@Override
	public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
		
		System.out.println("returnValue: "+returnValue+ ", method: " + method.getName());
	}
}

 

 

5-2) AfterThrowingAdvice

: 반환값에 예외처리사항이 있을시 작동

:  throwAdvice가 구현할 내용이 고정될 수 없으므로 디폴트 메서드만 구현되어있다.

//Program.java
public int total() {

		int result= kor + eng + math + com;
		
		if(kor>100) // 예외사항이 발생할경우 
			throw new IllegalArgumentException("유효하지 않은 국어점수");

	//sleep관련 try/catch문 생략
    
	return result;
}

------------------
//LogAfterThrowingAdvice.java

package spring.aop.advice;

import org.springframework.aop.ThrowsAdvice;

public class LogAfterThrowingAdvice implements ThrowsAdvice {
	// 어떤 예외를 처리할건지는 본인이 설정. 여기선 IllegalArgumentException을 설정

	public void afterThrowing(IllegalArgumentException e) throws Throwable{
		System.out.println("예외가 발생하였습니다.: " + e.getMessage());
	}
}

 

[6] Point Cut (Weaving, Join Point)

 

✔ Weaving : Cross-cutting Concern과 Core Concern 을 엮는 과정

✔ JoinPoint : Core Concern 에서 엮일 메인 함수 .proxy는 모든 메서드를 조인포인트로 생각한다. (total(), avg())

✔ PointCut : JoinPoint로 특정메서드만 사용하고 싶을 경우 포인트컷 설정

 

6-1) advisor하나당 advice하나를 지정해서 만드는 형태

: pointcut에 설정한 total에만 적용되는 advisor를 설정 가능

: 일일히 생성해야 하므로 간소화된 형태 필요.

//setting.xml

<!-- Exam exam = new NewlecExam(1, 1, 1, 1); -->
	<bean id="target" class="spring.aop.entity.NewlecExam" p:kor="1" p:eng="1" p:math="1" p:com="1" />
	<bean id="logAroundAdvice" class="spring.aop.advice.LogAroundAdvice"/>
	<bean id="logBeforeAdvice" class="spring.aop.advice.LogBeforeAdvice"/>
	<bean id="logAfterReturnningAdvice" class="spring.aop.advice.LogAfterReturnningAdvice"/>
	<bean id="logAfterThrowingAdvice" class="spring.aop.advice.LogAfterThrowingAdvice"/>
	
	<!-- pointcut설정 -->
	<bean id="classicPointCut" class="org.springframework.aop.support.NameMatchMethodPointcut">
		<property name="mappedName" value="total" />
	</bean>
	
	<!-- pointcut과 advice를 연결하는 advisor -->
	<bean id="classicBeforeAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
		<property name="advice"  ref="logBeforeAdvice"/>
		<property name="pointcut" ref="classicPointCut"/>
	</bean>
	
		<bean id="classicAroundAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
		<property name="advice"  ref="logAroundAdvice"/>
		<property name="pointcut" ref="classicPointCut"/>
	</bean>
	
	<!--Exam proxy = (Exam) Proxy.newProxyInstance  -->
	<bean id="exam" 	class="org.springframework.aop.framework.ProxyFactoryBean">
		<property name="target" ref="target" /> 
		<property name="interceptorNames"> 
			<list>
				<value>classicBeforeAdvisor</value>
				<value>classicAroundAdvisor</value>
				<value>logAfterReturnningAdvice</value>
				<value>logAfterThrowingAdvice</value>
			</list>
		</property>
	</bean>

 

 

6-2) PointCut 기능이 내장된 Advisor -NameMatchMethodPointcutAdvisor

<!-- 포인트컷이면서 어드바이져 --> NameMatchMethodPointcutAdvisor
	<bean id="classicBeforeAdvisor"
		class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
		<property name="advice" ref="logBeforeAdvice" />
		<property name="mappedNames">
			<list>
				<value> total</value> //해당되는 메서드값을 여러개 넣을때
				<value> avg </value>
			</list>
		</property>
	</bean>
    
	<bean id="classicAroundAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
		<property name="advice"  ref="logAroundAdvice"/>
		<property name="mappedName" value="total" />
	</bean>
	
	<!--Exam proxy = (Exam) Proxy.newProxyInstance  -->
	<bean id="exam" 	class="org.springframework.aop.framework.ProxyFactoryBean">
		<property name="target" ref="target" /> 
		<property name="interceptorNames"> 
			<list>
				<value>classicBeforeAdvisor</value>
				<value>classicAroundAdvisor</value>
				<value>logAfterReturnningAdvice</value>
				<value>logAfterThrowingAdvice</value>
			</list>
		</property>
	</bean>

 

6-3) 정규식 - RegexpMethodPointcutAdvisor

<bean id="classicBeforeAdvisor"
    class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice" ref="logBeforeAdvice" />
    <property name="patterns"> //names가 아닌 patterns로 변경
        <list>
            <value> .*to.*</value> // .anycharacter가 * 0개이상 올수있다는 뜻.+to. 즉 total만 적용
        </list>
    </property>
</bean>

 

: annotation으로 설정하는 법도 있음. 아직 강의가 안올라와서 구글링으로만 훑어보는중...

 

 

+ Recent posts