포스코DX X 비트교육센터 6기 - Spring


AOP

cross concern 횡단 관심을 바깥으로 들어내서 없애버리는 것.

  • aoptest -> jar 파일로 만듦.
  • 폴더 구조

image

  • pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>com.poscodx</groupId>
		<artifactId>spring-practices</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<artifactId>aoptest</artifactId>


	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.build.outputEncoding>UTF-8</project.build.outputEncoding>
		<maven.compiler.source>17</maven.compiler.source>
		<maven.compiler.target>17</maven.compiler.target>
		<org-springframework-version>5.3.25</org-springframework-version>
	</properties>

	<dependencies>
		<!-- spring mvc -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${org-springframework-version}</version>
		</dependency>

		<!-- spring aspect -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aspects</artifactId>
			<version>${org-springframework-version}</version>
		</dependency>
	</dependencies>

	<build>
		<finalName>aoptest</finalName>
	</build>
</project>
  • applicationContext.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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
        http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">
<!-- auto proxy -->
<aop:aspectj-autoproxy />

	<context:annotation-config />
	<context:component-scan base-package="service, aspect">
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository" />
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Service" />
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Component" />			
	</context:component-scan>
</beans>
  • mainApp.java
package main;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import service.ProductService;
import vo.ProductVo;

public class MainApp {
	public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("config/applicationContext.xml");
		
		ProductService ps = ac.getBean(ProductService.class);
		ProductVo vo = ps.find("TV");
		System.out.println(vo);
		
		((AbstractApplicationContext)ac).close();
		
		
	}
}

  • productService
package service;

import org.springframework.stereotype.Service;

import vo.ProductVo;

@Service
public class ProductService {

	public ProductVo find(String keyword) {
		ProductVo vo = new ProductVo(keyword);
		return vo;
	}
}

  • ProductVo.java
package vo;

public class ProductVo {
	private String name;
	
	public ProductVo(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "ProductVo [name=" + name + "]";
	}
	
	
}

  • 결과

image

  • MyAspect.java의 추가

image

package com.poscodx.aoptest.aspect;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class MyAspect {
   
   @Before("execution(public com.poscodx.aoptest.vo.ProductVo com.poscodx.aoptest.service.ProductService.find(String))") //해당 함수가 실행되기전 실행해라!!
   public void adviceBefore() {
      System.out.println("----before advice----");
   }
   
   //접근 지시자는 생략 가능!
   @After("execution(com.poscodx.aoptest.vo.ProductVo com.poscodx.aoptest.service.ProductService.find(String))") //해당 함수가 실행되기전 실행해라!!
   public void adviceAfter() {
      System.out.println("----before advice----");
   }
}
  • Spring Exception의 catch 문을 없애기 위해 사용하는 스타일

[ 첫 번째 ]

//ProductService.java
package com.poscodx.aoptest.service;

import org.springframework.stereotype.Service;

import com.poscodx.aoptest.vo.ProductVo;

@Service
public class ProductService {

	public ProductVo find(String keyword) {
		ProductVo vo = new ProductVo(keyword);
		if( 1 - 1 == 0) {
			throw new RuntimeException("ProductService.find() Exception");
		}
		return vo;
	}
}

  • main 실행

image

[ 두 번째 ]

  • 강력한 에러 처리!!

  • MyAspect.java

@Around("execution(* *..*.ProductService.*(..))")
   public Object adviceAround(ProceedingJoinPoint pjp) throws Throwable {
	   /*before*/
	   System.out.println("----- Around(before) Advice ----");
	   
	   Object result = pjp.proceed();
	   
	   /* after */
	   System.out.println("----- Around(after) Advice ----");
	   
	   return result;
   }
  • 강력함을 보여주는 예시
   
   @Around("execution(* *..*.ProductService.*(..))")
   public Object adviceAround(ProceedingJoinPoint pjp) throws Throwable {
	   /*before*/
	   System.out.println("----- Around(before) Advice ----");
	   
	   /* Point Cut Method 실행 */
	   Object[] params = {"Camera"};
	   Object result = pjp.proceed(params);
	   
	   
	   //Object result = pjp.proceed();
	   
	   /* after */
	   System.out.println("----- Around(after) Advice ----");
	   
	   return result;
   }

실행 시간 알기

  • pom.xml 추가
<!-- spring aspect -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aspects</artifactId>
			<version>${org-springframework-version}</version>
		</dependency>
  • repository에 추가
	public List<GuestbookVo> findAll() {
		StopWatch sw = new StopWatch();
		sw.start();
		List<GuestbookVo> result = sqlSession.selectList("guestbook.findAll");
		
		sw.stop();
		long totalTime = sw.getTotalTimeMillis();
		System.out.println("--->" + totalTime);
		return result;
	}

image

처음에는 로딩이 오래걸리지만,

캐시를 기억하고있기 때문에 두번째 이상부터는 빠른 속도로 실행됨.

실행시간 aspect 패키지로 빼기

  • spring-servlet.xml 과 applicationContext.xml 에 다음 내용을 추가
<!--  auto proxy -->
<aop:aspectj-autoproxy />

  • 파일 구조

image

  • 내용 작성
package com.poscodx.mysite.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

@Component
@Aspect
public class MeasureExecutionTimeAspect {
	@Around("execution(* *..*.repository.*.*(..))") //모든 레파지토리의 모든 메소드
	public Object adviceAround(ProceedingJoinPoint pjp) throws Throwable{
		//before
		StopWatch sw = new StopWatch();
		sw.start();
		
		Object result = pjp.proceed();
		
		//after
		sw.stop();
		long totalTime = sw.getTotalTimeMillis();
		String className = pjp.getTarget().getClass().getName();
		String methodName = pjp.getSignature().getName();
		String taskName = className + "." + methodName;
		
		System.out.println("[Execution Time][" + taskName + "] " + totalTime + "mills");
		
		return result;
	}
}

image

  • sql 사용할 때, 측정

image

@Around("execution(* *..*.repository.*.*(..)) || execution(* *..*.service.*.*(..)) || execution(* *..*.controller.*.*(..))") 

이렇게 바꿔보기도 하고~

************* 이게 운영 일임!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!


에러 페이지

  • web.xml
<!-- 공통 에러 페이지 -->
	<error-page>
		<error-code>404</error-code>
		<location>/WEB-INF/views/error/404.jsp</location>
	</error-page>

image

  • sql에서 에러 페이지를 이용하는 방법. (try - catch 안에서)

image

+ 404페이지 : 없는 페이지
 	+ 500페이지 : 예기치 못한 오류 (ex. SQL문 에러)
  • spring에서는 runtime error를 사용한다. / controller에 옮길 때, runtime exception으로 전환하다. / 한곳에서 aop사용해서 처리한다.

image


//assets /WEB-INF/ views /error/exception.jsp
 
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>

<%@ 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>Exeption Thrown</h1>
	<p>
	${errors }
	</p>
</body>
</html>

image

spring-servlet.xml 에 추가

<context:component-scan base-package="com.poscodx.mysite.controller, com.poscodx.mysite.exception" />
package com.poscodx.mysite.exception;

import java.io.PrintWriter;
import java.io.StringWriter;

import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {
	@ExceptionHandler(Exception.class)
	public String handlerException(Model model, Exception e) {
		//1. 로깅(Logging)
		StringWriter errors = new StringWriter();
		e.printStackTrace(new PrintWriter(errors));
		System.out.println(errors.toString());
		
		//2. 사과 페이지
		model.addAttribute("errors", errors.toString());
		return "error/exception";
	}
}
package com.poscodx.mysite.exception;

public class UserRepositoryException extends RuntimeException {
	public UserRepositoryException() {
		super("UserRepositoryException Thrown");
	}

	public UserRepositoryException(String message) {
		super(message);
	}
	
}