관리 메뉴

거니의 velog

(21) 스프링 애너테이션 기능 4 본문

Java/Java_Spring Framework part1

(21) 스프링 애너테이션 기능 4

Unlimited00 2023. 11. 13. 08:31

4. @Autowired 이용해 빈 주입하기

* XML에서 빈을 설정한 후 애플리케이션이 실행될 때 빈을 주입해서 사용하면 XML 파일이 복잡해지면서 사용 및 관리가 불편한다는 단점이 있다.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
	id="WebApp_ID" version="3.0">
<!-- 	 
   <listener>
      <listener-class>
         org.springframework.web.context.ContextLoaderListener
     </listener-class>
   </listener>
	<context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>
          /WEB-INF/config/action-mybatis.xml
      </param-value>
   </context-param>  

	-->
	<filter>
		<filter-name>encodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>encodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping> 
	
	<servlet>
		<servlet-name>action</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>action</servlet-name>
		<url-pattern>*.do</url-pattern>
	</servlet-mapping>

</web-app>

* 현재 스프링에서는 @Autowired를 이용해서 개발자가 만든 클래스들의 빈을 직접 자바 코드에서 생성하여 사용한다. 

@Autowired 의 특징은 다음과 같다.

- 기존 XML 파일에서 각각의 빈을 DI로 주입했던 기능을 코드에서 애너테이션으로 자동으로 수행한다.

- @Autowired 를 사용하면 별도의 setter나 생성자 없이 속성에 빈을 주입할 수 있다.

* 그럼 이번에는 이전에 실습한 회원 관리 기능을 @Autowired를 적용하여 구현해 보자.

1. 먼저 회원 관리 기능과 관련된 XML 파일을 설정하자. action-mybatis.xml과 jdbc.properties 파일은 이전의 파일을 복사해 config 폴더에 붙여 넣는다.

2. web.xml을 다음과 같이 작성한다. ContextLoaderListener를 이용해 애플리케이션이 실행될 때 action-mybatis.xml을 읽어 들이도록 설정한다.

   <listener>
      <listener-class>
         org.springframework.web.context.ContextLoaderListener
     </listener-class>
   </listener>
	<context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>
          /WEB-INF/config/action-mybatis.xml
      </param-value>
   </context-param>

3. action-servlet.xml의 JSP 경로를 /WEB-INF/views/member/ 로 변경한다.

<?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 class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- <property name="prefix" value="/WEB-INF/views/test/" /> -->
        <property name="prefix" value="/WEB-INF/views/member/" /> <!-- JSP 경로를 변경한다. -->
        <property name="suffix" value=".jsp" />
    </bean>

    <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
    <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
    <context:component-scan base-package="com.spring" />
</beans>

4. 스프링에서 제공하는 클래스의 빈을 사용하려면 여전히 XML로 설정해야 한다. 그러나 MemberDAO는 개발자가 만든 클래스이므로 XML에서 설정하는 것이 아니라 자바 코드에서 애너테이션으로 설정해야 한다. 따라서 action-mybatis.xml에서 MemberDAO 빈 설정 부분을 주석 처리한다.

<?xml version="1.0" encoding="UTF-8"?>
<beans
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"
 xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans">

	<bean id="propertyPlaceholderConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<value>/WEB-INF/config/jdbc.properties</value>
		</property>
	</bean>
	
 	<bean id="dataSource"
		class="org.apache.ibatis.datasource.pooled.PooledDataSource">
		<property name="driver" value="${jdbc.driverClassName}" />
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>
 
 	<bean id="sqlSessionFactory"
		class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="configLocation"
			value="classpath:mybatis/model/modelConfig.xml" />
		<property name="mapperLocations" value="classpath:mybatis/mappers/*.xml" />
	</bean>

	<bean id="sqlSession"
		class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg index="0" ref="sqlSessionFactory"></constructor-arg>
	</bean>

	<!-- memberDAO는 개발자가 만든 클래스 이므로 XML에서 설정하지 않고 자바 코드에서 애너테이션을 설정한다. -->
	<!-- <bean id="memberDAO"
		class="com.spring.member.dao.MemberDAOImpl">
		<property name="sqlSession" ref="sqlSession"></property>
	</bean> -->

</beans>

5. 이번에는 회원 관리 기능 매퍼 파일을 설정해 보자. 매퍼 파일들은 이전에 만든 파일과 같은 패키지 구조로 만든 후 실습한 member.xml과 modelConfig.xml을 복사해 붙여 넣는다.

6. 회원 관리 기능 관련 자바 파일과 JSP를 다음과 같이 준비한다. 일단 JSP는 이전에 실습한 파일과 동일하므호 views 폴더 하위에 member 폴더를 만든 후 파일을 복사해 붙여 넣는다.

7. MemberControllerImpl 클래스를 작성한다. @Controller 애너테이션으로 컨트롤러 빈을 자동 생성하고 @Autowired 애너테이션을 이용해 setter를 사용하지 않고 생성된 빈을 속성에 주입한다. 또한 @ModelAttribute 애너테이션을 이용해 회원 가입 창에서 전달된 회원 정보를 바로 MemberVO 객체 속성에 설정한다. @RequestMapping(value = "/member/*Form.do", method = RequestMethod.GET) 구문은 여러 요청에 대해 한 개의 메서드를 호출할 경우 정규식을 이용해 매핑하는 역할을 한다. 즉, Form.do로 끝나는 모든 요청에 대해 동일한 메서드를 호출한다.

package com.spring.member.controller;

import java.util.List;

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import com.spring.member.service.MemberService;
import com.spring.member.vo.MemberVO;


@Controller("memberController") // @Controller 애너테이션을 이용해 MemberControllerImpl 클래스에 대해 id가 memberController인 빈을 자동 생성한다.
public class MemberControllerImpl implements MemberController {
	
	@Autowired
	private MemberService memberService; // @Autowired를 이용해 id가 memberService인 빈을 자동 주입한다.
	@Autowired
	private MemberVO memberVO; // @Autowired를 이용해 id가 memberVO인 빈을 자동 주입한다.
	
	@Override
	@RequestMapping(value="/member/listMembers.do", method = RequestMethod.GET) // 두 단계로 요청시 바로 해당 메서드를 호출하도록 매핑한다.
	public ModelAndView listMembers(HttpServletRequest request, HttpServletResponse response) throws Exception {
		String viewName = getViewName(request);
		List membersList = memberService.listMembers();
		ModelAndView mav = new ModelAndView(viewName);
		mav.addObject("membersList", membersList);
		return mav;
	}

	@Override
	@RequestMapping(value="/member/addMember.do", method = RequestMethod.POST)
	public ModelAndView addMember(@ModelAttribute("member") MemberVO member, // 회원 가입창에서 전송된 회원 정보를 바로 MemberVO 객체에 설정한다.
			                  HttpServletRequest request, HttpServletResponse response) throws Exception {
		request.setCharacterEncoding("utf-8");
		int result = 0;
		result = memberService.addMember(member); // 설정된 memberBO 객체를 SQL문으로 전달해 회원 등록을 한다.
		ModelAndView mav = new ModelAndView("redirect:/member/listMembers.do");
		return mav;
	}
	
	@Override
	@RequestMapping(value="/member/removeMember.do", method = RequestMethod.GET)
	public ModelAndView removeMember(@RequestParam("id") String id, // 전송된 ID를 변수 id에 설정한다.
			           HttpServletRequest request, HttpServletResponse response) throws Exception{
		request.setCharacterEncoding("utf-8");
		memberService.removeMember(id);
		ModelAndView mav = new ModelAndView("redirect:/member/listMembers.do");
		return mav;
	}
	
	/*@RequestMapping(value = { "/member/loginForm.do", "/member/memberForm.do" }, method =  RequestMethod.GET)*/
	@RequestMapping(value = "/member/*Form.do", method = RequestMethod.GET) // 정규식을 이용해서 요청명이 Form.do로 끝나면 form() 메서드를 호출한다.
	public ModelAndView form(HttpServletRequest request, HttpServletResponse response) throws Exception {
		String viewName = getViewName(request);
		ModelAndView mav = new ModelAndView();
		mav.setViewName(viewName);
		return mav;
	}

	private String getViewName(HttpServletRequest request) throws Exception {
		String contextPath = request.getContextPath();
		String uri = (String) request.getAttribute("javax.servlet.include.request_uri");
		if (uri == null || uri.trim().equals("")) {
			uri = request.getRequestURI();
		}

		int begin = 0;
		if (!((contextPath == null) || ("".equals(contextPath)))) {
			begin = contextPath.length();
		}

		int end;
		if (uri.indexOf(";") != -1) {
			end = uri.indexOf(";");
		} else if (uri.indexOf("?") != -1) {
			end = uri.indexOf("?");
		} else {
			end = uri.length();
		}

		String viewName = uri.substring(begin, end);
		if (viewName.indexOf(".") != -1) {
			viewName = viewName.substring(0, viewName.lastIndexOf("."));
		}
		if (viewName.lastIndexOf("/") != -1) {
			viewName = viewName.substring(viewName.lastIndexOf("/"), viewName.length());
		}
		return viewName;
	}

}

8. MemberServiceImpl 클래스를 다음과 같이 작성한다. 여기서는 @Service("memberService")를 이용해 id가 memberService인 빈을 자동 생성한다. 그리고 @Autowired를 이용해 memberDAO 빈을 속성에 자동 주입한다.

package com.spring.member.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.spring.member.dao.MemberDAO;
import com.spring.member.vo.MemberVO;

@Service("memberService") // MemberServiceImpl 클래스를 이용해 id가 memberService인 빈을 자동 생성한다.
@Transactional(propagation = Propagation.REQUIRED)
public class MemberServiceImpl implements MemberService {
	
	@Autowired
	private MemberDAO memberDAO; // id가 memberDAO인 빈을 자동 주입한다.

	@Override
	public List listMembers() throws DataAccessException {
		List membersList = null;
		membersList = memberDAO.selectAllMemberList();
		return membersList;
	}

	@Override
	public int addMember(MemberVO member) throws DataAccessException {
		return memberDAO.insertMember(member);
	}

	@Override
	public int removeMember(String id) throws DataAccessException {
		return memberDAO.deleteMember(id);
	}
	
}

9. 계속해서 MemberDAOImpl 클래스를 다음과 같이 작성한다.

package com.spring.member.dao;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Repository;

import com.spring.member.vo.MemberVO;

@Repository("memberDAO") // id가 memberDAO인 빈을 자동 생성한다.
public class MemberDAOImpl implements MemberDAO {
	
	@Autowired
	private SqlSession sqlSession; // XML 설정 파일에서 생성한 id가 sqlSession인 빈을 자동 주입한다.

	@Override
	public List selectAllMemberList() throws DataAccessException {
		List<MemberVO> membersList = null;
		membersList = sqlSession.selectList("mapper.member.selectAllMemberList");
		return membersList;
	}

	@Override
	public int insertMember(MemberVO memberVO) throws DataAccessException {
		int result = sqlSession.insert("mapper.member.insertMember", memberVO);
		return result;
	}

	@Override
	public int deleteMember(String id) throws DataAccessException {
		int result = sqlSession.delete("mapper.member.deleteMember", id);
		return result;
	}
	
}

10. MemberVO 클래스의 경우 @Component("memberVO")를 이용해 id가 memberVO인 빈을 자동으로 생성하도록 설정한다.

package com.spring.member.vo;

import java.sql.Date;

import org.springframework.stereotype.Component;

@Component("memberVO")
public class MemberVO {
	
	private String id;
	private String pwd;
	private String name;
	private String email;
	private Date joinDate;

	public MemberVO() {}
	public MemberVO(String id, String pwd, String name, String email) {
		this.id = id;
		this.pwd = pwd;
		this.name = name;
		this.email = email;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getPwd() {
		return pwd;
	}

	public void setPwd(String pwd) {
		this.pwd = pwd;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public Date getJoinDate() {
		return joinDate;
	}

	public void setJoinDate(Date joinDate) {
		this.joinDate = joinDate;
	}

}

* 다음 그림은 애플리케이션을 실행한 후 애너테이션을 이용해 빈이 주입되는 과정을 나타낸 것이다. 주입되는 과정은 XML 파일로 설정한 것과 동일하다.

11. 다음의 주소로 요청하면 다음과 같이 회원 목록을 표시한다. 회원가입을 클릭하여 http://localhost:8090/pro26/member/memberForm.do로 요청하면 회원 가입창이 표시된다.

- http://localhost:8090/pro26/member/listMembers.do

12. 새 회원 '류현진'에 대한 정보를 입력한 후 가입하기를 클릭하여 http://localhost:8090/pro26/member/addMember.do로 요청하면 회원 등록 결과가 표시된다.

13. 이번에는 '류현진' 회원을 삭제해 보자. 삭제하기를 클릭하여 http://localhost:8090/pro26/member/removeMember.do로 요청하면 회원이 삭제되어 표시된다.

* 애너테이션이 적용되는 클래스는 주로 개발자들이 만든 클래스이다. 그러나 앞의 예제처럼 스프링에서 제공하는 클래스로 생성되는 빈인 sqlSession은 여전히 XML 파일에서 설정하여 생성한다.