자바 웹을 다루는 기술

[AOP][web.xml,action-servlet.xml 스프링] 책 20~21장

피아마수 2023. 8. 30. 20:49

의존성주입 !

객체는 하나이고 여러개의 클래스에서 선언한 참조변수에 대입한다.

a,b,c,d, 클래스의 각각의 boardService에는 컨테이너에 생성된 하나의 변수만이 대입된다.

 

 

 

 

함수사이의 공유: 멤버변수

 

DI구조에서는 멤버변수를 통해 공유 데이터를 공유하지 못함

 

class A {

    int a ;

    void insert(){
   		 a=0;
	}

    void update(){

   		 a=1;

    }

    void delete(){

    	sysout(a); //불가!

    }

}

 

DI구조에서는 매개변수를 통해서 공유데이터를 주고 받는다. 인자로 받지않으면 서로다른 스레드에 의해서 꼬여서 변경된다. 

DI는 객체가 한개!!!

 

 

 


 

 

20장

트랜잭션 - > 정상적이지 않게 종료된 작업은 다시 되돌리는 하나의 논리적인 최소단위

 

 

AOP - > 공통화기능으로 만든 내용을 각각 필요한 곳에 꽂아넣는다. (보조기능이라고 생각하면 돼)

로그기능을 구현하기 위해 하나의 기능을 수행하기 위해 호출된 모든 함수에서 로그 글자를 출력하고 싶으면 각각 함수 처음과 끝에 출력만 하면된다.

C++에서는 AOP안된다. -> 해킹위험 java는 리플렉션덕분에 됨

--------

<책내용>

AOP(관점지향프로그래밍)는 메서드 안의 주기능과 보조 기능을 분리한 후 선택적으로 메서드에 적용해서 사용한다. AOP를 이용하면 전체 코드에 흩어져 있는 보조기능을 하나의 장소에서 모아서 관리할 수있다. 또 보조기능을 자신이 원하는 주기능에 선택적으로 적용할 수 있어 코드가 단순해지고 가독성도 향상된다.

--------

 

이전에는 상속을 갖고 구현 -> AOP는 이러한 상속을 필요한때 꽂아넣을 수있다.

class A{

    void insert(){

    }

}

class B{

    void insert(){

        sysout();

        super.insert();

        sysout();

    }

}

service에서 트랜잭션을 걸어야한다. controller에서는 트랜잭션 걸지 말자 없어질수 있어! service클래스에 foo라는 함수가 있는데 이 애 전체를 대상으로 트랜잭션을 걸수 있다. 어노테이션으로도 걸수 있지만 밖에서 보조기능을 갖고 설정하겠다

pointcut -> service를 트랜잭션으로 자르는 것

 

 

AOP 관련용어

용어 설명
aspect 보조기능
advice aspect의 실제 구현체
joinpoint advice를 적용하는 시점
pointcut advice를 적용되는 대상 , joinpoint를 더 상세화 .호출전/후출후/예외로 자를 수있다. 3가지 
target advice가 적용되는 클래스
weaving advice를 주기능에 적용하는 것

 

AOP 인터페이스

-> 인자 : 메서드가 반환하는 값, 메소드 객체 , 인자목록 , 타겟객체, 예외 발생한 타입 --> 함수부터 밖으로 나가면서 인자를 받아오는 느낌

인터페이스 추상메서드 설명
MethodBeforeAdvice void befor(Method method, Object[] args, Object target) throws Throwable 해당 메서드가 실행되기 전 실행
AfterReturningAdvice void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable 해당 메서드가 실행된 후 실행
ThrowsAdvice void afterThrowing(Method method, Object[] args, Object target, Exception ex) 해당 메서드에서 예외 발생시 실행
MethodInterceptor Object invoke(MethodInvocation invocation) throws Throwable 해당 메서드의 실행 전 후와 예외 발생시 실행

 

실습

 

<MethodInterceptor invoke() >

-> 다른 3가지 메소드의 기능을 동시에 수행가능

 

ProxyFactoryBean의 속성 target에 Calculator객체를 넣고 속성 interceptorNames에 LoggingAdvice객체를 넣어 두 객체가 상속관계처럼 target의 함수가 호출되었을때 interceptorNames의 함수를 호출되게 해준다.

 

이방식은 1.8버전으로 사용가능하다

 

web.xml

 

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

<!DOCTYPE beans PUBLIC "--//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">


<beans>
<bean id="calcTarget" class="pro20.Calcuator"/>
<bean id="logAdvice" class="pro20.LoggingAdvice"/>

<bean id="proxyCalc" class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="target" ref="calcTarget"/>
	<property name="interceptorNames">
		<list>
			<value>logAdvice</value>
		</list>
	</property>


</bean>
</beans>

 

Calculator.java

public class Calcuator {
	public void add(int a, int b) {
		System.out.println("결과 : " + (a + b));
	}
	public void subtract(int a, int b) {
		System.out.println("결과 : " + (a - b));
	}
	public void multi(int a, int b) {
		System.out.println("결과 : " + (a * b));
	}
	public void divide(int a, int b) {
		System.out.println("결과 : " + (a / b));
	}
}

LoggingAdvice.java

MethodInterceptor를 상속 받아 invoke함수를 재정의 해준다. 실행할때마다 처리해야할 것이 있다면 이 함수에서 정의해주는것이다. MethodInvocation매개변수에는 대상 객체의 모든 정보를 담고 있는 객체(호출된 메서드, 인자 등) 가 담겨있는데 proceed()를 통해서 그 함수를 부가기능을 수행하고 원하는 시점에 실행 할 수있다.

public class LoggingAdvice implements MethodInterceptor{
	@Override
	public Object invoke(MethodInvocation arg0)throws Throwable {
		System.out.println("[메서드 호출 전 : LoggingAdvice " + arg0);
		System.out.println("호출된 함수명 : " + arg0.getMethod().getName());
		System.out.println("전달된 인자 " + Arrays.toString(arg0.getArguments()));
		//실제 메소드 호출
		Object obj = arg0.proceed();
		
		System.out.println("[메서드 호출후 : logginAdvice");
		return obj;
	}
}

CalTest.java

BeanFactory 클래스로 AOPText.xml파일을 읽어와서 Calculator 참조변수에 ProxyFactoryBean를 가져와서 대입해준다 그러면 이제 Calculator의 함수를 호출하면 LoggingAdvice의 invoke를 호출해서 부가기능과 주기능을 함께 처리해준다.

public class CalcTest {
	public static void main(String[] args) {
		BeanFactory factory = new XmlBeanFactory(new FileSystemResource("AOPText.xml"));
		Calcuator calc = (Calcuator) factory.getBean("proxyCalc");
		
		calc.add(100, 20);
		System.out.println();
		calc.subtract(100, 20);
		System.out.println();
		calc.multi(100, 20);
		System.out.println();
		calc.divide(100, 20);
		System.out.println();
	}
}

 


 

 

21장

 

tiles - > headers, footer, side를 구분해서 모듈처럼  조립해서 출력함

 

<스프링 프레임 워크 mvc 구성 요소>

구성요소 설명
DispatcherServlet 클라이언트에게 요청을 전달 받으면 처리할 컨트롤러를 선택하여 요청을 전달하고 컨트롤러가 반환한 view값을 클라이언트에게 전달한다.
HandlerMapping 클라이언트의 요청 url를 통해 처리할 컨트롤러를 지정한다.
Controller 클라이언트의 요청 처리후 결과를 DispatcherServlet에 전달
ModelAndView 컨트롤러가 처리한 결과를 전달할 뷰를 지정한다 -> addObject라는 함수로 키값 쌍을 통해서 jsp페이지로 전달한다. (내부적으로 request로 전달하고 object값을 전달할수 없다.)
ViewResolver 컨트롤러의 처리 결과를 전달할 뷰를 지정한다. -> ModelAndView에서 설정해줄 출력 jsp페이지 링크의 시작하는 폴더명 /test/를 생략하고 .jsp라는 끝에 붙는 명칭을 생략하고 사용할 수있다.(prefix,suffix)
View 컨트롤러의 처리 결과화면을 생성한다.

controller , view(jsp), model(service,DAO)

 

요청에 대한 결과가 처리되는 과정

웹브라우저에서 요청이 들어오면 *.do로 끝나는건 다 DispatcherServlet에서 받아준다. 이를 HandlerMapping에게 전달하면 url을 통해 처리할 controller에 지정한다. 처리가 끝나명 ModelAndView에 addObject를 통해서 jsp에 전달해줄 값을 담아서 출력할 jsp명까지 DispatcherServlet에 리턴하면 ViewResolver에 전달해서 prefix와 suffix 부분을 ModelAndView에서 t넘겨준 jsp명에 붙여서 jsp를 찾아준다. 그리고 이를 View에 리턴. View는 ModelAndView에서 전달받는 키와 값들을 세팅해서 DispatcherServlet에 넘겨주면 이제 DispatcherServlet가 클라이언트에게 넘겨준다. 

 

실습

(주어진 스프링 jar파일 다 lib에 넣어놓고 실습해야함)

파일 설명
web.xml *.do 요청시 모두 DispatcherServlet클래스가 요청을 받을 수있도록 매핑한다.
action-servlet.xml 스프링프레임워크에서 필요한 빈들을 설정한다.(ViewResolver, HandlerMapping, MethodNameResolver) <bean>
SimpleUrlController.java 컨트롤러의 기능을 수행
index.jsp 요청에 대해 브라우저로 전송하는 jsp

 

DispatcherServlet은 내부적으로 init()에서 action-servlet.xml를 자동으로 로딩한다. action-servlet.xml이 BeanFactory가 getBean해서 컨테이너에서 객체를 찾아오는 걸 대신해준다.

 

 

1. jsp 하나에 servlet 하나 등록하는 방법

 

xml파일에 일단 *.do 링크로의 요청은 DispatcherServlet로 전달하도록 한다. 긴 클래스 경로를 action으로 별칭 설정한다. 그리고 *.do로 끝나는 요청은 action으로 전달한다.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">
  <display-name>pro21</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  
  <servlet>
  <!-- dispatcherServlet의 init에서 action- servlet.xml을 자동으로 로딩한다. -->
  	<servlet-name>action</servlet-name>
  	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  	<load-on-startup>1</load-on-startup>
  	
  </servlet>
  
  <servlet-mapping>
  <!-- *.do로 끝나는 url은 전부 dispatcherServlet로 전달한다. -->
  	<servlet-name>action</servlet-name>
  	<url-pattern>*.do</url-pattern>
  </servlet-mapping>
  
</web-app>

 

/test/index.do로 요청이 오는건 simpleUrlController로 가도록 urlMapping한다.

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

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:aop="http://www.springframework.org/schema/aop"
	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   
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<bean id="simpleUrlContoller" class="com.kosa.SimpleUrlController"/>

<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
	<property name="mappings">
		<props>
		<!-- url에 해당하는 controller 를설정해준다 -->
		<!-- 이렇게 하면 jsp하나에 servlet하나 -->
			<prop key="/test/index.do">simpleUrlContoller</prop>
		</props>
	</property>
</bean>

</beans>

 

controller를 상속 받아 DispatcherServlet가 url에 따른 요청을 simpleController에서 처리 할 수 있도록 한다. Controller 인터페이스를 상속받으면 요청을 처리할 <ModelAndView handleRequest()> 함수를 재정의 해야한다.

return new ModelAndView("index.jsp")를 통해 요청을 처리해줄 페이지로 전달한다 . 이제 ViewResolver가 jsp 위치를찾고 View에서 페이지를 완성하겠죠ㅋ

package com.kosa;

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

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

//스프링에서 제공하는 controller 인터페이스를 구현한다.
public class SimpleUrlController implements Controller {

	@Override
	public ModelAndView handleRequest(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception {
		System.out.println("???");
		return new ModelAndView("index.jsp");
	}

}

 

2. 1개의 controller로 메소드에 url을 매핑해서 사용한다.

 

 

1개의 controller로 메소드에 url을 매핑해서 사용하려면 action-servlet.xml에 controller안의 함수에 url 까지 매핑해줘야한다. 그래서 userUrlMapping에 controller 이름을 통해서 매핑을 해주고 MultiActionController를 상속받은 controller안에 선언되어있는 Method들을 PropertiesMethodNameResolver를 통해서 매핑해준다. 그 후에 controller의 methodNameResolver속성에 매핑한 결과값을 대입해준다.

action-servlet.xml

 

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

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:aop="http://www.springframework.org/schema/aop"
	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   
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">


<bean id="viewResolver"
		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="viewClass"
			value="org.springframework.web.servlet.view.JstlView" />
		<property name="prefix" value="/test/" />
		<property name="suffix" value=".jsp" />
	</bean>

<bean id="userUrlMapping"
		class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
		<property name="mappings">
			<props>
				<prop key="/test/*.do">userController</prop>
			</props>
		</property>
	</bean>

	<bean id="userController" class="com.kosa.UserController">
		<property name="methodNameResolver">
			<ref local="userMethodNameResolver" />
		</property>
	</bean>

	<bean id="userMethodNameResolver"
		  class="org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver">
		<property name="mappings">
			<props>
				<prop key="/test/login.do">login</prop>
				<prop key="/test/memberInfo.do">memberInfo</prop> 
			</props>
		</property>
	</bean>

</beans>

 

컨트롤러 하나가 각각의 함수들을 통해서 요청을 처리할 수있도록  MultiActionController를 상속받아 사용한다. 

<String getViewName(String url)>

url과 jsp명이 일치함을 이용해서 url에서 jsp명을 가져오는 함수

 

<ModelAndView login()>

login폼 jsp를 출력하기 위한 함수 세팅값이 없기 때문에 new 를통해 ModelAndView값을 리턴만해준다.

 

<ModelAndView memberInfo()>

login 폼으로부터 전달받은 값을 memberInfo.jsp에 전달해주기 위한 값을 ModelAndView에 넣어서 memberInfo 페이지로 전달한다.

public class UserController  extends MultiActionController{
	
	//url과 jsp명이 일치하기 때문에 url에서 jsp 명을 가져온다.
	public String getViewName(String url) {
		int startIndex = url.lastIndexOf("/")+1;
		int endIndex = url.indexOf(".do");
		String result = url.substring(startIndex,endIndex);
		
		return result;
	}
	
	
	public ModelAndView login(HttpServletRequest request,HttpServletResponse response) {
		System.out.println("login함수 호출");
		//action-servlet에서 .jsp 를 뺄수 있도록 설정했기때문에
		return new ModelAndView(getViewName(request.getRequestURI()));
	}
	public ModelAndView memberInfo(HttpServletRequest request,HttpServletResponse response) {
		System.out.println("memberInfo함수 호출");
		//action-servlet에서 .jsp 를 뺄수 있도록 설정했기때문에
		try {
			request.setCharacterEncoding("utf-8");
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        //로그인 form의 값을 받아온다.
		String userId = request.getParameter("userId");
		String passwd = request.getParameter("passwd");
		String name = request.getParameter("name");
		String email = request.getParameter("email");
		
		ModelAndView modelAndView = new ModelAndView(getViewName(request.getRequestURI()));
		//view에 값을 전달 request로
		modelAndView.addObject("userId",userId);
		modelAndView.addObject("passwd",passwd);
		modelAndView.addObject("name",name);
		modelAndView.addObject("email",email);

		return modelAndView;
	}
	
}