본문 바로가기

회고록(TIL&WIL)

TIL 2023.03.02 Spring framework ver 2.X (IoC-DI, AOP, Junit)

IoC - DI(의존성 주입)

주입의 방법 3가지

  1. Field injection (파라미터)
  2. Setter injection (세터)
  3. Constructor injection (생성자)

Spring에서는 setter와 생성자를 이용한 DI 를 이용한다.

모듈 정의, 다형성을 이용한 인터페이스 화

public interface Module {
	public void func1();
}
-----------------
public class Module1 implements Module{
	public void func1() {
		System.out.println("Module1 run...");
	}
}
-----------------
public class Module2 implements Module{
	public void func1() {
		System.out.println("Module2 run...");
	}
}

Setter를 이용한 주입

public class ModuleService {
	Module module;
	public void setModule(Module module) {
		this.module = module;
	}
--------- App
public class App {
	public static void main(String[] args) {
		ModuleService moduleService = new ModuleServic
		moduleService.setModule(new Module1());
		moduleService.work(); // module run1...
	}
}

생성자를 통한 주입

public class ModuleService {
  Module module;
  ModuleService(Module module){
    this.module = module;
  }
}
--------- App
public class App {
  public static void main(String[] args) {
	ModuleService moduleService = new ModuleService(new Module1());
	moduleService.work(); // module run1...
  }
}

Spring 2.X 에선 XML에서 객체를 생성하고 이를 주입하는 형태로 구성을 하였다.

web.xml - DispatcherSevlet을 이용한 매핑, applicationContext.xml 세팅

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
	<welcome-file-list>
		<welcome-file>index.do</welcome-file>	
	</welcome-file-list>
	
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:/applicationContext.xml</param-value>
	</context-param>
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	
	<servlet>
		<servlet-name>sts03</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/servlet/servlet-context.xml</param-value>
		</init-param>
	</servlet>
	<servlet-mapping>
		<servlet-name>sts03</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
</web-app>
 
-------- applicationContext.xml
<bean id="module" class="com.bit.spring.non.module.Module2"></bean>  // Module2 객체 생성

<bean id="ms" class="com.bit.spring.non.service.ModuleService">
	<property name="module" ref="module"></property>
	<constructor-arg ref="module"></constructor-arg> // 생성자가 반드시 있어야함
</bean>
-------- App
public class App {
	public static void main(String[] args) {
		ApplicationContext ac = null;
		ac = new ClassPathXmlApplicationContext("/applicationContext.xml");
		ModuleService my = (ModuleService) ac.getBean("ms"); // 생성자를 통한 주입
		my.work(); // Module2 run...
	}
}

XML에서 각 타입에 맞게 주입 가능

기본 타입

---------- applicationContext.xml
	<bean id="nal" class="java.util.Date"></bean>
	<bean id="module" class="com.bit.spring.service.Module3">
		<property name="boo" value="true"></property>
		<property name="ch1" value="@"></property>
		<property name="su1" value="1234"></property>
		<property name="su2" value="123456789"></property>
		<property name="su3" value="3.14"></property>
		<property name="msg" value="abcdefg"></property>
		<property name="nalja" ref="nal"></property>
	</bean>
---------- Module
import java.util.Date;

public class Module3 {
	boolean boo;
	char ch1;
	int su1;
	long su2;
	double su3;
	String msg;
	Date nalja;
	public void setBoo(boolean boo) {
		this.boo = boo;
	}
	public void setCh1(char ch1) {
		this.ch1 = ch1;
	}
	public void setSu1(int su1) {
		this.su1 = su1;
	}
	public void setSu2(long su2) {
		this.su2 = su2;
	}
	public void setSu3(double su3) {
		this.su3 = su3;
	}
	public void setMsg(String msg) {
		this.msg = msg;
	}
	public void setNalja(Date nalja) {
		this.nalja = nalja;
	}
	public void func() {
		System.out.println(boo);
		System.out.println(ch1);
		System.out.println(su1);
		System.out.println(su2);
		System.out.println(su3);
		System.out.println(msg);
		System.out.println(nalja);
	}
}

배열

----------- applicationContext.xml
<bean id="module" class="com.bit.spring.service.Module4">
  <property name="msgs">
    <array>
      <value>item1</value>
      <value>item2</value>
      <value>item3</value>
      <value>item4</value>
      <value>item5</value>
    </array>
  </property>
</bean>
----------- Module
public class Module4 {
	String[] msgs;
	public void setMsgs(String[] msgs) {
		this.msgs = msgs;
	}
	
	public void func() {
		for(String msg : msgs) {
			System.out.println(msg);
		}
	}
}

List

----------- applicationContext.xml
<bean id="set" class="java.util.HashSet"></bean>
<bean id="module" class="com.bit.spring.service.Module5">
  <property name="list" ref="set"></property>
</bean>
----------- Module
public class Module5 implements Module {
	Set<String> list;
	public void setList(Set<String> list) {
		this.list = list;
	}
	
	public void func() {
		for(String msg : list) {
			System.out.println(msg);
		}
	}
}

Map

----------- applicationContext.xml
<bean id="module" class="com.bit.spring.service.Module6">
  <property name="map">
    <props>
      <prop key="key1">val1</prop>
      <prop key="key2">val2</prop>
      <prop key="key3">val3</prop>
      <prop key="key4">val4</prop>
    </props>
  </property>
  <property name="map">
    <map>
      <entry key="key1">
        <value>val1</value>
      </entry>
      <entry key="key2">
        <value>val2</value>
      </entry>
      <entry key="key3">
        <value>val3</value>
      </entry>
    </map>
  </property>
</bean>
----------- Module
public class Module6 implements Module {
	Map<String,String> map ;
	Properties props;
	public void setMap(Map<String, String> map) {
		this.map = map;
	}
	public void setPros(Properties props) {
		this.props = props;
	}
	
	public void func() {
		Set<Entry<String,String>>entrys = map.entrySet();
		for(Entry<String,String> entry : entrys) {
			System.out.println("key :" +entry.getKey()+", val : "+ entry.getValue());
		}
	}
}

AOP (Aspect Oriented Programming) 관점지향프로그래밍

이전, 이후와 같이 현재의 메서드의 진행 기준을 잡고 이 기준으로 관점을 나누고 관점에 따라 모듈을 만드는 프로그래밍
현재의 Spring에서는 직접 만들어 사용하는 경우는 거의 없으나 트랜잭션에서 사용되어지고 있음

applicationContext.xml

<bean id="myService" class="com.bit.spring.aop.CoreService">
  <property name="msg" value="abcde"></property>
  <property name="name" value="user1"></property>
  <property name="su" value="0"></property>
</bean>
<bean id="methodBeforeAdvice" class="com.bit.spring.aop.BeforePrint"></bean>
<bean id="afterReturningAdvice" class ="com.bit.spring.aop.AfterPrint"></bean>
<bean id="throwsAdvice" class="com.bit.spring.aop.AfterErr"></bean>
<bean id="methodInterceptor" class="com.bit.spring.aop.AroundPrint"></bean>

<bean id="pointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
  <property name="patterns" >
    <array>
      <value>.*All.*</value>
      <value>.*One.*</value>
    </array>
  </property>
</bean>
<bean id="beforePointcutAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
  <property name="pointcut" ref="pointcut"></property>
  <property name="advice" ref="methodBeforeAdvice"></property>
</bean>
<bean id="afterPointcutAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
  <property name="pointcut" ref="pointcut"></property>
  <property name="advice" ref="afterReturningAdvice"></property>
</bean>

<bean id="beanNameAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
  <property name="beanNames">
    <array>
      <value>deptDao</value>
    </array>
  </property>
  <property name="interceptorNames">
    <list>
      <value>beforePointcutAdvisor</value>
      <value>afterPointcutAdvisor</value>
    </list>
  </property>
</bean>

CoreService

public class CoreService {
	String msg;
	String name;
	int su;
	
	public void setMsg(String msg) {
		this.msg = msg;
	}
	public void setName(String name) {
		this.name = name;
	}
	public void setSu(int su) {
		this.su = su;
	}
	public String getMsg() {
		return msg;
	}
	public String printHelloMsg(String msg2) {
		String result = name + "님 "+msg2;
		System.out.println(msg2);
		return result;
	}
	public void printMsg() {
		System.out.println(msg);
	}
	public void printName() {
		System.out.println(name);
	}
	public void printDiv() throws Exception {
//		double result = msg.toCharArray()[su];
//		System.out.println(result);
		if(su==0) throw new Exception("내가 던진 오류");
	}
}

aop 클래스들

-------------------- BeforePrint (이전 실행)
public class BeforePrint implements MethodBeforeAdvice {
	Logger log = Logger.getLogger(this.getClass());
	public void before(Method method, Object[] args, Object target) throws Throwable {
		log.debug(method.getName()+":"+Arrays.toString(args));
//		System.out.println("<<<<<<<<<<");
//		System.out.println(method.getName());
//		System.out.println(method);
//		System.out.println(Arrays.toString(args));
//		System.out.println(target);
//		System.out.println("before : " +((CoreService)target).getMsg());
	}
}
------------------- AfterPrint (이후 실행)
public class AfterPrint implements AfterReturningAdvice {
	Logger log = Logger.getLogger(this.getClass());
	public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
//		System.out.println(">>>>>>>>>>" + returnValue);
		log.debug(returnValue);
	}
}
------------------ AroundPrint (이전 또는 이후 커스텀)
public class AroundPrint implements MethodInterceptor {

	public Object invoke(MethodInvocation invocation) throws Throwable {
		System.out.println("before");
		Object obj = null;
		try {
			obj = invocation.proceed();
			System.out.println("after returning");
		} catch (Exception e) {
			System.out.println("after throw");
		}
		return obj;
	}
}
----------------- AfterErr (에러 발생 후)
public class AfterErr implements ThrowsAdvice {
	
	public void afterThrowing(Exception ex) {
		System.out.println("에러발생...");
		System.out.println(ex);
	}
}

CRUD 예제

applicationContext.xml

	<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
		<property name="location" value="classpath:/db.properties"/>
	</bean>
	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
		<property name="url" value="${db.mysql.url}"/>
		<property name="username" value="${db.mysql.username}"/>
		<property name="password" value="${db.mysql.password}"/>
	</bean>
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"/>
	</bean>
	<bean id="deptDao" class="com.bit.spring.model.DeptDaoImpl">
		<property name="jdbcTemplate" ref="jdbcTemplate"/>
	</bean>

db.properties

db.mysql.url=jdbc:mysql://localhost:3306/lecture
db.mysql.username=scott
db.mysql.password=tiger

DeptDao (interface)

public interface DeptDao {
	List<DeptVo> findAll();
	DeptVo findOne(int rownum);
	void insertOne(DeptVo bean);
	int updateOne(DeptVo bean);
	int deleteOne(int rownum);
}

DeptDaoImpl

public class DeptDaoImpl extends JdbcDaoSupport implements DeptDao{

	public List<DeptVo> findAll() {
		String sql = "select * from dept";
		return getJdbcTemplate().query(sql, new RowMapper<DeptVo>() {
			public DeptVo mapRow(ResultSet rs, int rowNum) throws SQLException {
				return new DeptVo(rs.getInt("deptno"), rs.getString("dname"), rs.getString("loc"));
			}
		});
	}
	public DeptVo findOne(int rownum) {
		String sql = "select * from dept where deptno=?";
		return getJdbcTemplate().queryForObject(sql, new RowMapper<DeptVo>() {
			public DeptVo mapRow(ResultSet rs, int rowNum) throws SQLException {
				return new DeptVo(rs.getInt("deptno"), rs.getString("dname"), rs.getString("loc"));
			}
		}, rownum);
	}
	public void insertOne(DeptVo bean) {
		String sql = "insert into dept value (?,?,?)";
		getJdbcTemplate().update(sql, bean.getDeptno(), bean.getDname(), bean.getLoc());

	}
	public int updateOne(DeptVo bean) {
		String sql = "update dept set dname=?, loc=? where deptno=?";
		return getJdbcTemplate().update(sql, bean.getDname(), bean.getLoc(), bean.getDeptno());
	}
	public int deleteOne(int rownum) {
		String sql = "delete from dept where deptno=?";
		return getJdbcTemplate().update(sql, rownum);
	}
}

Controller

------------------ ListController
public class ListController implements Controller {
	DeptDao deptDao;
	public void setDeptDao(DeptDao deptDao) {
		this.deptDao = deptDao;
	}
	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		ModelAndView mav = new ModelAndView();
		mav.setViewName("dept/list");
		mav.addObject("list", deptDao.findAll());
		return mav;
	}
}
------------------ AddController
public class AddController implements Controller {

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		ModelAndView mav = new ModelAndView();
		mav.setViewName("dept/add");
		return mav;
	}

}
------------------ DetailController
public class DetailController implements Controller {
	DeptDao deptDao;
	public void setDeptDao(DeptDao deptDao) {
		this.deptDao = deptDao;
	}
	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		int deptno = Integer.parseInt(request.getParameter("deptno"));
		ModelAndView mav = new ModelAndView("dept/detail", "bean", deptDao.findOne(deptno));
		return mav;
	}
}
------------------ UpdateController
public class UpdateController extends AbstractCommandController{
	DeptDao deptDao;
	public void setDeptDao(DeptDao deptDao) {
		this.deptDao = deptDao;
	}
	@Override
	protected ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object command,
			BindException errors) throws Exception {
		DeptVo bean = (DeptVo) command;
		deptDao.updateOne(bean);
		return new ModelAndView("redirect:list.do");
	}
}
------------------ DeleteController
public class DeleteController implements Controller {
	DeptDao deptDao;
	public void setDeptDao(DeptDao deptDao) {
		this.deptDao = deptDao;
	}
	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		int deptno = Integer.parseInt(request.getParameter("deptno"));
		deptDao.deleteOne(deptno);
		return new ModelAndView("redirect:list.do");
	}
}

servlet-context.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
	
	<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"></bean>
<!-- Handler Mapping -->
	<bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
		<property name="urlMap">
			<props>
				<prop key="/index.do">index</prop>
				<prop key="/dept/list.do">list</prop>
				<prop key="/dept/add.do">add</prop>
				<prop key="/dept/insert.do">insert</prop>
				<prop key="/dept/detail.do">detail</prop>
				<prop key="/dept/update.do">update</prop>
				<prop key="/dept/delete.do">delete</prop>
			</props>
		</property>
	</bean>
	<bean id="index" class="com.bit.sts02.controller.IndexController"></bean>
	<bean id="list" class="com.bit.sts02.controller.ListController">
		<property name="deptDao" ref="deptDao"></property>
	</bean>
	<bean id="add" class="com.bit.sts02.controller.AddController"></bean>
	<bean id="insert" class="com.bit.sts02.controller.InsertController">
		<property name="supportedMethods" value="POST"></property>
		<property name="commandName" value="bean"></property>
		<property name="commandClass" value="com.bit.spring.model.DeptVo"></property>
		<property name="deptDao" ref="deptDao"></property>
	</bean>
	<bean id="detail" class="com.bit.sts02.controller.DetailController">
		<property name="deptDao" ref="deptDao"></property>
	</bean>
	<bean id="update" class="com.bit.sts02.controller.UpdateController">
		<property name="supportedMethods" value="POST"></property>
		<property name="commandName" value="bean"></property>
		<property name="commandClass" value="com.bit.spring.model.DeptVo"></property>
		<property name="deptDao" ref="deptDao"></property>
	</bean>
	<bean id="delete" class="com.bit.sts02.controller.DeleteController">
		<property name="deptDao" ref="deptDao"></property>
	</bean>
	<!-- View Resolve -->
	<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/views/"></property>
		<property name="suffix" value=".jsp"></property>
	</bean>
</beans>

junit

DaoDeptTest

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class DeptDaoTest {
	static ApplicationContext ac; 
	private DeptDao deptDao;
	private DeptVo target;
	
	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		ac = new ClassPathXmlApplicationContext("/applicationContext.xml");
	}

	@Before
	public void setUp() throws Exception {
		deptDao = (DeptDao) ac.getBean("deptDao");
		target = new DeptVo(99, "test", "test");
	}
	
	@Test
	public void testDataSource() throws SQLException {
		DataSource dataSource =(DataSource) ac.getBean("dataSource");
		Connection conn = dataSource.getConnection();
		assertNotNull(conn);
		conn.close();
	}
	
	@Test
	public void testJdbcTemplate() {
		JdbcTemplate jdbcTemplate =(JdbcTemplate) ac.getBean("jdbcTemplate");
		assertNotNull(jdbcTemplate);
	}

	@Test
	public void test1FindAll() {
		List<DeptVo> list = deptDao.findAll();
		assertNotNull(list);
		assertSame(6, list.size());
	}
	
	@Test
	public void test2InsertOne() {
		deptDao.insertOne(target);
		assertFalse(false);
	}
	
	@Test
	public void test3FindOne() {
		DeptVo bean = deptDao.findOne(target.getDeptno());
		assertEquals(target, bean);
	}
	
	@Test
	public void test4UpdateOne() {
		target.setDname("테스트");
		assertSame(1, deptDao.updateOne(target));
	}
	
	@Test
	public void test5DeleteOne() {
		assertSame(1, deptDao.deleteOne(target.getDeptno()));
	}

	@AfterClass
	public static void tearDownAfterClass() throws Exception {}

	@After
	public void tearDown() throws Exception {}

}