이상한모임 글중에 나의 마음에 와닿는 글입니다.

TDD의 효용성에 대해 짧은 글로 하고자 하는 말을 정확히 전달해서 아주 인상적으로 읽었습니다.


구직 과정에 코딩 시험이 있었다. 면접관의 컴퓨터와 응사자의 컴퓨터가 코딩 시험 도구로 연결되어 면접관이 응시자의 코딩을 지켜보거나 개입할 수 있는 환경이었다. 어떤 문제의 답 코드를 쓰다 10줄 이상이 되니 자신감이 떨어져 TDD를 사용하기로 했다. 여기서 나는 흥미로운 경험을 했다.

물론 코딩 시험에 테스팅 도구는 주어지지 않았다. TDD는 테스팅 도구가 있어야만 할 수 있는 것이 아니다.

이 글에서 테스트 케이스는 테스트 코드가 아니라 테스트 데이터를 의미한다.

나는 시험 문제를 반영한 간단한 테스트 함수를 만들고 제시된 테스트 케이스와 몇가지 임의의 테스트 케이스를 추가했다. 답 코드를 쓰는 과정에서 지속적으로 테스트를 실행했고 모든 테스트 케이스가 성공한 후 면접관에게 코딩이 끝났다고 말했다.

즉시 면접관 중 한 명이 버그가 있으니 수정해 달라고 얘기했다. 나는 버그를 찾기 위해 무의식적으로 답 코드가 아니라 시험 문제 지문과 테스트 케이스 목록을 확인했다. 잠깐 살펴봤지만 안타깝게도 나는 버그를 발견하지 못했다. 면접관에게 버그를 찾지 못했다고 말하자 면접관은 버그를 설명하는 대신 테스트 케이스 하나를 추가했다. 나는 추가된 테스트 케이스가 실패하는 것을 확인한 후 답 코드에 논리 하나를 추가했다. 다시 테스트를 실행했고 모든 테스트 케이스에 대해 성공 표시가 출력됐다.

이것이 나에게 흥미로운 이유는 코딩 시험 과정이 소프트웨어를 만드는 한가지 유용한 프로세스 사례의 축소판이 되었기 때문이다. 프로그래머는 구현 코드가 아니라 요구사항과 요구사항의 구체적인 사례에 더 집중한다. 도메인 전문가나 품질 책임자는 발견된 버그를 재현 방법과 함께 프로그래머에 전달한다. 프로그래머는 버그를 반복 재현하며 코드를 고친다. 작업의 완료와 소프트웨어 회귀(software regression)가 없음은 자동화된 테스팅을 통해 확인한다.

항상 코딩 시험의 평가자 역할을 수행하다 정말 오랜만에 응시자가 되었는데 이런 놀라운 경험을 하게 될 것이라고는 전혀 생각하지 못했다. 처음부터 버그 없는 답을 썼어도 좋았겠지만 나는 원래 완벽한 코드를 쓸 수 있는 사람이 아니다. 오히려 이번 경험이 앞으로 나에게 큰 영향을 줄 것으로 생각된다.

-출처 : https://justhackem.wordpress.com/2019/01/05/coding-test-and-tdd/?utm_source=weirdmeetup&utm_medium=original_link_on_post&utm_campaign=%EC%BD%94%EB%94%A9+%EC%8B%9C%ED%97%98%EA%B3%BC+TDD


'Programming > Spring Framework' 카테고리의 다른 글

Spring Boot Auto Configuration 예제  (0) 2020.04.21
2way ssl 인증을 위한 JKS 만들기  (0) 2020.03.10
Annotation-based Controller  (0) 2017.02.01

slack을 쓰긴 쓰고 있지만....

사실 slack을 사용하고 있지만 협업툴로써 쓰기 보다는 메신저로 쓰고 있었다. 하지만 slack과 연동되는 많은 app의 기능은 단순 메신저로 사용하던 나를 slack API까지 보게 만들 정도로 매력이 넘친다. slack의 app directory를 가보면 category가 있는데 develop에 대한 app뿐만 아니라 finance, travel과 같은 app들과도 연동이 가능하다.

개발장이라 그런지 "project management"를 주목 하게 되었고 asana, trello, jira, gitlab등에 관심을 가지고 보게 되었다. 그 중에 MeisterTask는 유료긴 하지만 결제를 고민하게 될 정도로 괜찮아 보인다. 무료로도 사용 가능하지만 slack연동은 안되어서 일단 연동없이 무료로 써보고 포스팅도 할 계획이다.

왜 git과 slack을 연동하려고?

결론 부터 말하자면 처음 도입부에서 언급했던 것처럼 단순 메신저가 아닌 협업툴로 쓰기 위해서 첫단추를 "git연동"으로 잡았다. (그런데 쉽지 않는 주제를 잡아서 하고 나서 헥헥거리는 날 보게 됨...ㅠㅠ)

사실 github나 gitlab같은 서비스들은 연동이 쉽게 되긴한다. 서비스 자체에서 webhooks을 설정 해주는 것 같다. (물론 해보지 않고 블로그만 보고 얘기 하는 것임.) 하지만 불행하게도 git 형상관리 시스템은 다른 팀에서 관리 하고 있어서 gitlab을 설치 하는 그런 생각은 일찌감치 접었다. 대신 git의 hook를 써서 slack과 연동하기로 한다.

본격적인 slack 연동

먼저 slack page에서 자기의 workspace로 이동하면 위쪽에 Manage 탭을 클릭한다. 그럼 좌측에 Custom Integrations에 Incomming Webhooks가 보인다.

Incomming Webhooks를 클릭하면

Add Configuration을 클릭하면

특정 채널로 noti.하도록 설정하면

Configuration이 생긴다. edit버튼을 누르면

여러 내용들이 나오는데 제일 중요한것은 Webhook URL이다. 이 URL로 호출을 해줘야 해당 configuration이 동작하기 때문이다. 나머지 Setup Instructions나 Massage Attachments는 data structue와 richtext를 위한 내용이라고 보면 된다. 이러면 slack쪽 설정은 끝났다.

slack과 git 연동

지금까지는 slack에서 메세지를 받는 준비를 한 것이다. 이제 git에서 slack으로 메세지를 주도록 설정하면 된다. 그러기 위해서 나는 git push가 되었을 때를 기준으로 메세지를 보내려고 했다. 그래서 git hook을 사용하면 되는데 설명은 링크로 대체 한다. (GIT HOOK)

git hook은 크게 클라이언트 훅과 서버 훅으로 나뉘고 클라이언트 훅은 어떤 상황에서든 사용가능하나 서버 훅은 push의 전후로 사용된다는 특징이 있다. 당연하겠지만 서버 훅으로 설정되어야 어떤 push든 처리 가능하지 클라이언트 훅은 사용자별로 세팅되어야 전 인원이 적용되는 것이기 때문에 보통 서버 훅을 고려하지 않을까 생각 된다. server hook은 종류가 많지 않다. 그 중에 push후에 실행되는 post-receive를 사용했다.

먼저, remote git에 ssh로 접속해서 메세지를 보내고 싶은 repository의 hooks폴더로 이동한다. .sample로 끝나는 파일들은 git 설치하면 기본적으로 제공하는 sample들이다. 하지만 내가 쓰고자 하는 post-receive의 sample은 없었다... 지쟈스~ 결국 간단하지만 아래와 같이 shell을 만들었다!

#!/bin/bash

USER=$(whoami)
#GIT_LOG=$(git log --pretty=format:"commit:\t%H\nAuthor:\t%an\nDate:\t%ad\n%s" -1)
GIT_LOG=$(git log -1)
curl -X POST --data-urlencode "payload={\"channel\": \"#back-end\", \"username\": \"$USER\", \"icon_emoji\": \":bell:\", \
\"attachments\":[\
      {\
         \"fallback\":\"Please fix errors.\",\
         \"color\":\"good\",\
         \"fields\":[\
            {\
               \"title\":\"New Commit\",\
               \"value\":\"$GIT_LOG\",\
               \"short\":false\
            }\
         ]\
      }\
   ]}" https://hooks.slack.com/services/TAC434PG9/AAAAAAAA/BBBBBBBBBBBBBBBBBBBBBBBB

위치는 {remote git repository folder}/hooks/post-receive로 새로 만들었다. payload에 attachments는 richtext를 지원하는 slack api로 아래 갭쳐 처럼 보이게 한다.

attachments는 slack API로 꽤 많은 기능이 제공된다. API링크를 보고 입맛에 맞게 payload를 수정하면 될 것 같다. 쉬운줄 알았는데 4시간을 허비하니까 힘들어서 attachment는 이 정도만 하고 "이 정도면 됐어!"하고 셀프 위안 중이다.ㅋ

후기

이로써 slack을 메신저에서 협업툴로 조금은 업그레이드 된 것 같다. slack은 채널이라는 개념이 있어서 프로젝트별로(개발장이라 프로젝트임. 원래는 그룹방 개념이라 프로젝트 말고 다른 이미로도 쓰일 수있다.) 채널을 만들어서 그 프로젝트에 관련된 알림을 주고 받는게 핵심이라고 생각한다. 현재는 git을 연동 했지만 나중에는 jira나 meister task같은 management tool도 연동하면 굉장히 나이스 할 것 같다.

'Programming > Git' 카테고리의 다른 글

repo 를 사용하여 프로젝트 관리하기  (0) 2016.06.15
git commit 내용을 메일로 보내기  (0) 2016.01.27
GIT에 대한 내용정리  (0) 2015.08.27

개요

스프링 프레임워크는 2.5 버젼 부터 Java 5+ 이상이면 @Controller(Annotation-based Controller)를 개발할 수 있는 환경을 제공한다.
인터페이스 Controller를 구현한 SimpleFormController, MultiActionController 같은 기존의 계층형(Hierarchy) Controller와의 주요 차이점 및 개선점은 아래와 같다.

  1. 어노테이션을 이용한 설정 : XML 기반으로 설정하던 정보들을 어노테이션을 사용해서 정의한다.
  2. 유연해진 메소드 시그니쳐 : Controller 메소드의 파라미터와 리턴 타입을 좀 더 다양하게 필요에 따라 선택할 수 있다.
  3. POJO-Style의 Controller : Controller 개발시에 특정 인터페이스를 구현 하거나 특정 클래스를 상속해야할 필요가 없다. 하지만, 폼 처리, 다중 액션등 기존의 계층형 Controller가 제공하던 기능들을 여전히 쉽게 구현할 수 있다.

계층형 Controller로 작성된 폼 처리를 @Controller로 구현하는 예도 설명한다.
예제 코드 easycompany의 Controller는 동일한 기능(또한 공통의 Service, DAO, JSP를 사용)을 계층형 Controller와 @Controller로 각각 작성했다.

  • 계층형 Controller - 패키지 com.easycompany.controller.hierarchy
  • @Controller - 패키지 com.easycompany.controller.annotation

설명

어노테이션을 이용한 설정

계층형 Controller들을 사용하면 여러 정보들(요청과 Controller의 매핑 설정 등)을 XML 설정 파일에 명시 해줘야 하는데, 복잡할 뿐 아니라 설정 파일과 코드 사이를 빈번히 이동 해야하는 부담과 번거로움이 될 수 있다.
@MVC는 Controller 코드안에 어노테이션으로 설정함으로써 좀 더 편리하게 MVC 프로그래밍을 할 수 있도록 했다.
@MVC에서 사용하는 주요 어노테이션은 아래와 같다.

이름설명
@Controller해당 클래스가 Controller임을 나타내기 위한 어노테이션
@RequestMapping요청에 대해 어떤 Controller, 어떤 메소드가 처리할지를 맵핑하기 위한 어노테이션
@RequestParamController 메소드의 파라미터와 웹요청 파라미터와 맵핑하기 위한 어노테이션
@ModelAttributeController 메소드의 파라미터나 리턴값을 Model 객체와 바인딩하기 위한 어노테이션
@SessionAttributesModel 객체를 세션에 저장하고 사용하기 위한 어노테이션
@RequestPartMultipart 요청의 경우, 웹요청 파라미터와 맵핑가능한 어노테이션(egov 3.0, Spring 3.1.x부터 추가)
@CommandMapController메소드의 파라미터를 Map형태로 받을 때 웹요청 파라미터와 맵핑하기 위한 어노테이션(egov 3.0부터 추가)
@ControllerAdviceController를 보조하는 어노테이션으로 Controller에서 쓰이는 공통기능들을 모듈화하여 전역으로 쓰기 위한 어노테이션(egov 3.0, Spring 3.2.X부터 추가)

@Controller

@MVC에서 Controller를 만들기 위해서는 작성한 클래스에 @Controller를 붙여주면 된다. 특정 클래스를 구현하거나 상속할 필요가 없다.

package com.easycompany.controller.annotation;
 
@Controller
public class LoginController {
   ...
}

앞서 DefaultAnnotationHandlerMapping에서 언급한 대로 <context:component-scan> 태그를 이용해 @Controller들이 있는 패키지를 선언해 주면 된다.
@Controller만 스캔 한다면 include, exclude 등의 필터를 사용하라.

<?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:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
				http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
 
        <context:component-scan base-package="com.easycompany.controller.annotation" />
 
</beans>

@RequestMapping

@RequestMapping은 요청에 대해 어떤 Controller, 어떤 메소드가 처리할지를 맵핑하기 위한 어노테이션이다. @RequestMapping이 사용하는 속성은 아래와 같다.

이름타입설명
valueString[]URL 값으로 맵핑 조건을 부여한다.
@RequestMapping(value=”/hello.do”) 또는 @RequestMapping(value={”/hello.do”, ”/world.do” })와 같이 표기하며,
기본값이기 때문에 @RequestMapping(”/hello.do”)으로 표기할 수도 있다.
”/myPath/*.do”와 같이 Ant-Style의 패턴매칭을 이용할 수도 있다.
Spring 3.1부터 URL뒤에 중괄호를 이용하여 변수값을 직접 받을 수 있도록 하였다. 아래 설명(URI Template Variable Enhancements)을 참고하라
methodRequestMethod[]HTTP Request 메소드값을 맵핑 조건으로 부여한다.
HTTP 요청 메소드값이 일치해야 맵핑이 이루어 지게 한다.
@RequestMapping(method = RequestMethod.POST)같은 형식으로 표기한다.
사용 가능한 메소드는 GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE이다
paramsString[]HTTP Request 파라미터를 맵핑 조건으로 부여한다.
params=“myParam=myValue”이면 HTTP Request URL중에 myParam이라는 파라미터가 있어야 하고 값은 myValue이어야 맵핑한다.
params=“myParam”와 같이 파라미터 이름만으로 조건을 부여할 수도 있고, ”!myParam”하면 myParam이라는 파라미터가 없는 요청 만을 맵핑한다.
@RequestMapping(params={“myParam1=myValue”, “myParam2”, ”!myParam3”})와 같이 조건을 주었다면,
HTTP Request에는 파라미터 myParam1이 myValue값을 가지고 있고, myParam2 파라미터가 있어야 하고, myParam3라는 파라미터는 없어야 한다.
consumesString[]설정과 Content-Type request헤더가 일치 할 경우에만 URL이 호출된다.
producesString[]설정과 Accept request헤더가 일치 할 경우에만 URL이 호출된다.

@RequestMapping은 클래스 단위(type level)나 메소드 단위(method level)로 설정할 수 있다.

type level
/hello.do 요청이 오면 HelloController의 hello 메소드가 수행된다.

@Controller
@RequestMapping("/hello.do")
public class HelloController {
 
    @RequestMapping   //type level에서 URL을 정의하고 Controller에 메소드가 하나만 있어도 요청 처리를 담당할 메소드 위에 @RequestMapping 표기를 해야 제대로 맵핑이 된다.
    public String hello(){
	...		
    }
}

method level
/hello.do 요청이 오면 hello 메소드, 
/helloForm.do 요청은 GET 방식이면 helloGet 메소드, POST 방식이면 helloPost 메소드가 수행된다.

@Controller
public class HelloController {	
 
	@RequestMapping(value="/hello.do")
	public String hello(){
		...
	}
 
	@RequestMapping(value="/helloForm.do", method = RequestMethod.GET)
	public String helloGet(){
		...
	}
 
	@RequestMapping(value="/helloForm.do", method = RequestMethod.POST)
	public String helloPost(){
		...
	}	
}

type + method level
둘 다 설정할 수도 있는데, 이 경우엔 type level에 설정한 @RequestMapping의 value(URL)를 method level에서 재정의 할수 없다.
/hello.do 요청시에 GET 방식이면 helloGet 메소드, POST 방식이면 helloPost 메소드가 수행된다.

@Controller
@RequestMapping("/hello.do")
public class HelloController {
 
	@RequestMapping(method = RequestMethod.GET)
	public String helloGet(){
		...
	}
 
	@RequestMapping(method = RequestMethod.POST)
	public String helloPost(){
		...
	}
}

AbstractController 상속받아 구현한 예제 코드 LoginController를 어노테이션 기반의 Controller로 구현해 보겠다. 
기존의 LoginController는 URL /loginProcess.do로 오는 요청의 HTTP 메소드가 POST일때 handleRequestInternal 메소드가 실행되는 Controller였는데, 다음과 같이 구현할 수 있겠다.

package com.easycompany.controller.annotation;
...
@Controller
public class LoginController {
 
	@Autowired
	private LoginService loginService;
 
	@RequestMapping(value = "/loginProcess.do", method = RequestMethod.POST)
	public String login(HttpServletRequest request) {
 
		String id = request.getParameter("id");
		String password = request.getParameter("password");
 
		Account account = (Account) loginService.authenticate(id,password);
 
		if (account != null) {
			request.getSession().setAttribute("UserAccount", account);
			return "redirect:/employeeList.do";
		} else {
			return "login";
		}
	}	
}

위 예제 코드에서 서비스 클래스를 호출하기 위해서 @Autowired가 사용되었는데 자세한 내용은 여기를 참고하라.

type + method level + request
앞의 내용에서 추가되어 request의 header설정 일치 여부에 따라 URL호출이 가능하다.

다음 예제에서 URL이 /pets로 요청된 경우, POST타입의 request의 content-type이 application/json인 경우에만 다음 메소드가 호출된다.

@Controller
...
@RequestMapping(value = "/pets", method = RequestMethod.POST, consumes="application/json")
public void addPet(@RequestBody Pet pet, Model model) {
    // implementation omitted
}

다음 예제에서 GET타입으로 URL이 /pets/*로 요청된 경우, request의 accept header가 application/json인 경우에만 다음 메소드가 호출된다.

@Controller
...
@RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, produces="application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId, Model model) {
    // implementation omitted
}

URI Template Variale Enhancements
@RequestMapping의 value에 URL뒤에 중괄호로 Controller메소드의 파라미터로 받을 값의 변수명을 입력해주면 변수를 받을 수 있다.
다음 예제에서 Controller메소드의 URL을 ”/user/view/{id}“로 설정하였을 때, 만약 /user/view/12345 로 URL요청이 들어오면 view함수의 파라미터인 id가 12345로 설정된다.

@RequestMapping("/user/view/{id}")
public String view(@PathVariable("id") int id) {
    // implementation omitted
}

@RequestParam

@RequestParam은 Controller 메소드의 파라미터와 웹요청 파라미터와 맵핑하기 위한 어노테이션이다.
관련 속성은 아래와 같다.

이름타입설명
valueString파라미터 이름
requiredboolean해당 파라미터가 반드시 필수 인지 여부. 기본값은 true이다.

아래 코드와 같은 방법으로 사용되는데, 
해당 파라미터가 Request 객체 안에 없을때 그냥 null값을 바인드 하고 싶다면, pageNo 파라미터 처럼 required=false로 명시해야 한다.
name 파라미터는 required가 true이므로, 만일 name 파라미터가 null이면 org.springframework.web.bind.MissingServletRequestParameterException이 발생한다.

@Controller
public class HelloController {
 
    @RequestMapping("/hello.do")
    public String hello(@RequestParam("name") String name, //required 조건이 없으면 기본값은 true, 즉 필수 파라미터 이다. 파라미터 pageNo가 존재하지 않으면 Exception 발생.
			@RequestParam(value="pageNo", required=false) String pageNo){ //파라미터 pageNo가 존재하지 않으면 String pageNo는 null.
	...		
    }
}

위에서 작성한 LoginController의 login 메소드를 보면 파라미터 아이디와 패스워드를 Http Request 객체에서 getParameter 메소드를 이용해 구하는데,
@RequestParam을 사용하면 아래와 같이 변경할수 있다.

package com.easycompany.controller.annotation;
...
@Controller
public class LoginController {
 
	@Autowired
	private LoginService loginService;
 
	@RequestMapping(value = "/loginProcess.do", method = RequestMethod.POST)
	public String login(
			HttpServletRequest request,
			@RequestParam("id") String id,
			@RequestParam("password") String password) {		
 
		Account account = (Account) loginService.authenticate(id,password);
 
		if (account != null) {
			request.getSession().setAttribute("UserAccount", account);
			return "redirect:/employeeList.do";
		} else {
			return "login";
		}
	}
}

@ModelAttribute

@ModelAttribute의 속성은 아래와 같다.

이름타입설명
valueString바인드하려는 Model 속성 이름.

@ModelAttribute는 실제적으로 ModelMap.addAttribute와 같은 기능을 발휘하는데, Controller에서 2가지 방법으로 사용된다.

1.메소드 리턴 데이터와 Model 속성(attribute)의 바인딩. 
메소드에서 비지니스 로직(DB 처리같은)을 처리한 후 결과 데이터를 ModelMap 객체에 저장하는 로직은 일반적으로 자주 발생한다.

...
	@RequestMapping(value = "/updateDepartment.do", method = RequestMethod.GET)
	public String formBackingObject(@RequestParam("deptid") String deptid, ModelMap model) {
		Department department = departmentService.getDepartmentInfoById(deptid); //DB에서 부서정보 데이터를 가져온다.
		model.addAttribute("department", department); //데이터를 모델 객체에 저장한다.
		return "modifydepartment";
	}
...

@ModelAttribute를 메소드에 선언하면 해당 메소드의 리턴 데이터가 ModelMap 객체에 저장된다.
위 코드를 아래와 같이 변경할수 있는데, 사용자로 부터 GET방식의 /updateDepartment.do 호출이 들어오면,
formBackingObject 메소드가 실행 되기 전에 DefaultAnnotationHandlerMapping이 org.springframework.web.bind.annotation.support.HandlerMethodInvoker을 이용해서 
(@ModelAttribute가 선언된)getEmployeeInfo를 실행하고, 결과를 ModelMap객체에 저장한다.
결과적으로 getEmployeeInfo 메소드는 ModelMap.addAttribute(“department”, departmentService.getDepartmentInfoById(…)) 작업을 하게 되는것이다.

...
	@RequestMapping(value = "/updateDepartment.do", method = RequestMethod.GET)
	public String formBackingObject() {
		return "modifydepartment";
	}
 
	@ModelAttribute("department")
	public Department getEmployeeInfo(@RequestParam("deptid") String deptid){
		return departmentService.getDepartmentInfoById(deptid); //DB에서 부서정보 데이터를 가져온다.
	}
	또는
	public @ModelAttribute("department") Department getDepartmentInfoById(@RequestParam("deptid") String deptid){
		return departmentService.getDepartmentInfoById(deptid);
	}
...

2.메소드 파라미터와 Model 속성(attribute)의 바인딩. 
@ModelAttribute는 ModelMap 객체의 특정 속성(attribute) 메소드의 파라미터와 바인딩 할때도 사용될수 있다.
아래와 같이 메소드의 파라미터에 ”@ModelAttribute(“department”) Department department” 선언하면 department에는 (Department)ModelMap.get(“department”) 값이 바인딩된다.
따라서, 아래와 같은 코드라면 formBackingObject 메소드 파라미터 department에는 getDepartmentInfo 메소드가 ModelMap 객체에 저장한 Department 데이터가 들어 있다.

...
	@RequestMapping(value = "/updateDepartment.do", method = RequestMethod.GET)
	public String formBackingObject(@ModelAttribute("department") Department department) { //department에는 getDepartmentInfo에서 구해온 데이터들이 들어가 있다.
		System.out.println(employee.getEmployeeid());
		System.out.println(employee.getName());
		return "modifydepartment";
	}
 
	@ModelAttribute("department")
	public Department getDepartmentInfo(@RequestParam("deptid") String deptid){
		return departmentService.getDepartmentInfoById(deptid); //DB에서 부서정보 데이터를 가져온다.
	}
...

@SessionAttributes

@SessionAttributes는 model attribute를 session에 저장, 유지할 때 사용하는 어노테이션이다. @SessionAttributes는 클래스 레벨(type level)에서 선언할 수 있다. 관련 속성은 아래와 같다.

이름타입설명
typesClass[]session에 저장하려는 model attribute의 타입
valueString[]session에 저장하려는 model attribute의 이름

@RequestParam

Multipart request의 경우, 넘겨받은 Contents의 Content-Type에 따라 HttpMessageConverter를 통해 해당 타입대로 multipart컨텐츠를 얻을 때 사용하는 어노테이션이다.

예를 들어 다음과 같이 요청이 multipart로 들어올 때

POST /someUrl
Content-Type: multipart/mixed
 
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="meta-data"
Content-Type: application/json; charset=UTF-8
Content-Transfer-Encoding: 8bit
 
{
  "name": "value"
}
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="file-data"; filename="file.properties"
Content-Type: text/xml
Content-Transfer-Encoding: 8bit
... File Data ...

Controller에서 요청값은 아래와 같이 받을 수 있다.

@RequestMapping(value="/someUrl", method = RequestMethod.POST)
public String onSubmit(@RequestPart("meta-data") MetaData metadata,
                       @RequestPart("file-data") MultipartFile file) {
    // ...
}

@CommandMap

@CommandMap은 실행환경 3.0환경부터 추가된 Controller에서 Map형태로 웹요청 값을 받았을 때 다른 Map형태의 argument와 구분해주기 위한 어노테이션이다. @CommandMap은 파라미터 레벨(type level)에서만 선언할 수 있다.

사용 방법은 다음과 같다.

   @RequestMapping("/test.do")
   public void test(HttpServletRequest request, @CommandMap Map<String, String> commandMap){
	//생략
   }

자세한 사용방법은 AnnotationCommandMapArgumentResolver을 참고한다.

@ControllerAdvice

@ControllerAdvice 어노테이션을 통해 Controller에서 쓰이는 몇가지 어노테이션 기능들을 모듈화하여 전역으로 쓸 수 있다.
@ControllerAdvice은 @RequestMapping이 붙은 메소드를 지원하며 다음과 같은 Controller 어노테이션을 지원한다.

이름설명
@ExceptionHandler@ExceptionHandler 뒤에 붙은 Exception이 발생했을 때, 전역적으로 예외처리가 가능하다.
@InitBinder모델 검증과 바인딩을 하기 위한 Annotation으로써 JSR-303 빈 검증기능을 사용하는 스프링 validator를 사용할 수 있다.
@ModelAttribute도메인 오브젝트나 DTO프로퍼티에 요청파라미터를 한 번에 받을 수 있는 @ModelAttribute를 전역으로 사용 가능하다.
@ExceptionHandler with @ControllerAdvice

기존에는 예외발생시, AnnotationMethodHandlerExceptionResolver가 Controller내부에서 @ExceptionHandler가 붙은 메소드를 찾아 예외처리를 해준다. Controller 내부에서만 @ExceptionHandler가 동작하기 때문에 각 Controller별로 @ExceptionHandler 메소드를 만들어야했다.

@Controller
public class HelloController {
 
  @RequestMapping("/hello")
  public void hello() {    
    //DataAccessException이 일어날 가능성
  }
     
  // Controller 내부에서 DataAccessException발생시 호출
  @ExceptionHandler(DataAccessException.class)
  public ModelAndView dataAccessExceptionHandler(DataAccessException e) {
    return new ModelAndView("dataexception").addObject("msg", ex.getMessage();
  }
}

Spring 3.2부터는 @ControllerAdvice를 이용하여 @ExceptionHandler를 전역으로 쓸 수 있다. 
@ControllerAdvice + @ExceptionHandler를 통해 각각의 Exception에 대하여 전역적인 후처리 관리가 가능해진다. 즉, Controller마다 @ExceptionHandler를 만들지 않더라도 @ControllerAdvice가 붙은 Class안에서 여러 Exception에 대한 처리가 가능한 @ExceptionHandler 메소드를 만들면 로지컬한 Exception별 후처리가 가능해지는 것이다.

@ControllerAdvice와 함께 @ExceptionHandler를 쓰는 방법은 다음과 같다.
다음과 같이 쓰는 경우, Controller에서 발생하는 해당 Exception들이 예외처리가 된다.

@ControllerAdvice
public class CentralControllerHandler {
 
@ExceptionHandler({EgovBizException.class})
public ModelAndView handleEgovBizException(EgovBizException ee) {
//생략
}
 
@ExceptionHandler({BaseException.class})
public ModelAndView handleBaseException(BaseException be) {
//생략
}
}
@InitBinder with @ControllerAdvice

@ControllerAdvice가 붙은 Class내부에서 @InitBinder 메소드를 씀으로써 이를 전역으로 쓸 수도 있다. 

  • @InitBinder : WebDataBinder를 초기화하는 메소드를 지정할 수 있는 설정을 제공한다. WebDataBinder는 Web request parameter를 javaBean객체에 바인딩하는 특정한 DataBinder이다. 일반적으로 annotation handler메소드의 command와 form객체 인자를 조사하는데 사용된다.(CustomEditor를 등록하거나 Validator를 등록할 때 쓰인다

@ControllerAdvice 와 함께 @InitBinder를 쓰는 방법은 다음과 같다.
@InitBinder는 Controller에서 @Valid를 쓰는 경우에만 해당 파라미터의 데이터 검증이 적용된다.

Person객체를 모델바인딩 및 검증해주는 PersonValidator를 이용하는 경우이다.

@ControllerAdvice
public class CentralControllerHandler {
 
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.setValidator(new PersonValidator());
}
 
//생략
}

이 때 Controller의 구현 예이다.

@RequestMapping(value="/persons")
public void updatePerson(@Valid Person person, HttpServletRequest request, HttpServletResponse response) {
personService.updatePerson(person);
//생략
}

@ModelAttribute와 @ControllerAdvice

@ControllerAdvice를 통해 @ModelAttribute의 메소드 또한 전역으로 쓰일 수 있다.

@ControllerAdvice
public class GlobalControllerAdvice {
 
  ... 
 
  @ModelAttribute("model")
  public Model getModel() {
    return this.model();
  }
}

유연해진 메소드 시그니쳐

@RequestMapping을 적용한 Controller의 메소드는 아래와 같은 메소드 파라미터와 리턴 타입을 사용할수 있다.
특정 클래스를 확장하거나 인터페이스를 구현해야 하는 제약이 없기 때문에 계층형 Controller 비해 유연한 메소드 시그니쳐를 갖는다.

@Controller의 메소드 파라미터

사용가능한 메소드 파라미터는 아래와 같다.

  • Servlet API - ServletRequest, HttpServletRequest, HttpServletResponse, HttpSession 같은 요청,응답,세션관련 Servlet API들.
  • WebRequest, NativeWebRequest - org.springframework.web.context.request.WebRequest, org.springframework.web.context.request.NativeWebRequest
  • java.util.Locale
  • java.io.InputStream / java.io.Reader
  • java.io.OutputStream / java.io.Writer
  • @RequestParam - HTTP Request의 파라미터와 메소드의 argument를 바인딩하기 위해 사용하는 어노테이션.
  • java.util.Map / org.springframework.ui.Model / org.springframework.ui.ModelMap - 뷰에 전달할 모델데이터들.
  • Command/form 객체 - HTTP Request로 전달된 parameter를 바인딩한 커맨드 객체, @ModelAttribute을 사용하면 alias를 줄수 있다.
  • Errors, BindingResult - org.springframework.validation.Errors / org.springframework.validation.BindingResult 유효성 검사후 결과 데이터를 저장한 객체.
  • SessionStatus - org.springframework.web.bind.support.SessionStatus 세션폼 처리시에 해당 세션을 제거하기 위해 사용된다.

메소드는 임의의 순서대로 파라미터를 사용할수 있다. 단, BindingResult가 메소드의 argument로 사용될 때는 바인딩 할 커맨드 객체가 바로 앞에 와야 한다.

public String updateEmployee(...,@ModelAttribute("employee") Employee employee,			
			BindingResult bindingResult,...) <!-- (O) -->
 
public String updateEmployee(...,BindingResult bindingResult,
                        @ModelAttribute("employee") Employee employee,...) <!-- (X) -->

이 외의 타입을 메소드 파라미터로 사용하려면?
스프링 프레임워크는 위에서 언급한 타입이 아닌 custom arguments도 메소드 파라미터로 사용할 수 있도록 org.springframework.web.bind.support.WebArgumentResolver라는 인터페이스를 제공한다.
WebArgumentResolver를 사용한 예제는 이곳을 참고하라.

@Controller의 메소드 리턴 타입

사용가능한 메소드 리턴 타입은 아래와 같다.

  • ModelAndView - 커맨드 객체와 @ModelAttribute이 적용된 메소드의 리턴 데이터가 담긴 Model 객체와 View 정보가 담겨 있다.
            @RequestMapping(value = "/updateDepartment.do", method = RequestMethod.GET)
    	public ModelAndView formBackingObject(@RequestParam("deptid") String deptid) {
    		Department department = departmentService.getDepartmentInfoById(deptid);
    		ModelAndView mav = new ModelAndView("modifydepartment");
    		mav.addObject("department", department);
    		return mav;
    	}
    또는
    	public ModelAndView formBackingObject(@RequestParam("deptid") String deptid, ModelMap model) {
    		Department department = departmentService.getDepartmentInfoById(deptid);
    		model.addAttribute("department", department);
    		ModelAndView mav = new ModelAndView("modifydepartment");
    		mav.addAllObjects(model);
    		return mav;
    	}
  • Model(또는 ModelMap) - 커맨드 객체와 @ModelAttribute이 적용된 메소드의 리턴 데이터가 Model 객체에 담겨 있다.
    View 이름은 RequestToViewNameTranslator가 URL을 이용하여 결정한다. 인터페이스 RequestToViewNameTranslator의 구현클래스인 DefaultRequestToViewNameTranslator가 View 이름을 결정하는 방식은 아래와 같다.
    http://localhost:8080/gamecast/display.html -> display
    http://localhost:8080/gamecast/displayShoppingCart.html -> displayShoppingCart
    http://localhost:8080/gamecast/admin/index.html -> admin/index
    	@RequestMapping(value = "/updateDepartment.do", method = RequestMethod.GET)
    	public Model formBackingObject(@RequestParam("deptid") String deptid, Model model) {
    		Department department = departmentService.getDepartmentInfoById(deptid);
    		model.addAttribute("department", department);
    		return model;
    	}
    또는
    	@RequestMapping(value = "/updateDepartment.do", method = RequestMethod.GET)
    	public Model formBackingObject(@RequestParam("deptid") String deptid) {
    		Department department = departmentService.getDepartmentInfoById(deptid);
    		Model model = new ExtendedModelMap();
    		model.addAttribute("department", department);
    		return model;
    	}
  • Map - 커맨드 객체와 @ModelAttribute이 적용된 메소드의 리턴 데이터가 Map 객체에 담겨 있으며, View 이름은 역시 RequestToViewNameTranslator가 결정한다.
    	@RequestMapping(value = "/updateDepartment.do", method = RequestMethod.GET)
    	public Map formBackingObject(@RequestParam("deptid") String deptid) {
    		Department department = departmentService.getDepartmentInfoById(deptid);
    		Map model = new HashMap();
    		model.put("department", department);
    		return model;
    	}
    또는 
    	@RequestMapping(value = "/updateDepartment.do", method = RequestMethod.GET)
    	public Map formBackingObject(@RequestParam("deptid") String deptid, Map model) {
    		Department department = departmentService.getDepartmentInfoById(deptid);
    		model.put("department", department);
    		return model;
    	}
  • String - 리턴하는 String 값이 곧 View 이름이 된다. 커맨드 객체와 @ModelAttribute이 적용된 메소드의 리턴 데이터가 Model(또는 ModelMap)에 담겨 있다. 리턴할 Model(또는 ModelMap)객체가 해당 메소드의 argument에 선언되어 있어야 한다.
            <!--(O)-->
    	@RequestMapping(value = "/updateDepartment.do", method = RequestMethod.GET)
    	public String formBackingObject(@RequestParam("deptid") String deptid, ModelMap model) {
    		Department department = departmentService.getDepartmentInfoById(deptid);
    		model.addAttribute("department", department);
    		return "modifydepartment";
    	}
     
            <!--(X)-->
    	@RequestMapping(value = "/updateDepartment.do", method = RequestMethod.GET)
    	public String formBackingObject(@RequestParam("deptid") String deptid) {
    		Department department = departmentService.getDepartmentInfoById(deptid);
    		ModelMap model = new ModelMap();
    		model.addAttribute("department", department);
    		return "modifydepartment";
    	}
  • View - View를 리턴한다. 커맨드 객체와 @ModelAttribute이 적용된 메소드의 리턴 데이터가 Model(또는 ModelMap)에 담겨 있다.
  • void - 메소드가 ServletResponse / HttpServletResponse등을 사용해서 직접 응답을 처리하는 경우. View 이름은 RequestToViewNameTranslator가 결정한다.

POJO-Style의 Controller

@MVC는 Controller 개발시에 특정 인터페이스를 구현 하거나 특정 클래스를 상속해야할 필요가 없다.
Controller의 메소드에서 Servlet API를 반드시 참조하지 않아도 되며, 훨씬 유연해진 메소드 시그니쳐로 개발이 가능하다.
여기서는 SimpleFormController의 폼 처리 액션을 @Controller로 구현함으로써, POJO-Style에 가까워졌지만 기존의 계층형 Controller에서 제공하던 기능들을 여전히 구현할 수 있음을 보이고자 한다.

FormController by SimpleFormController -> @Controller

앞서 SimpleFormController을 설명하면서 예제로 작성된 com.easycompany.controller.hierarchy.UpdateDepartmentController를 @ModelAttribute와 @RequestMapping을 이용해서 같은 기능을 @Controller로 작성해 보겠다.
JSP 소스는 동일한 것을 사용한다. 이곳의 예제 화면 이미지 및 JSP 코드를 참고하라.
기존의 UpdateDepartmentController를 보면 3가지 메소드로 이루어졌다.

  • referenceData - 입력폼에 필요한 참조데이터인 상위부서정보를 가져와서 Map 객체에 저장한다. 이후에 이 Map 객체는 스프링 내부 로직에 의해 ModelMap 객체에 저장된다.
  • formBackingObject - GET 방식 호출일때 초기 입력폼에 들어갈 부서 데이터를 리턴한다. 이 데이터 역시 ModelMap 객체에 저장된다.
  • onSubmit - POST 전송시에 호출되며 폼 전송을 처리한다.
package com.easycompany.controller.hierarchy;
...
 
public class UpdateDepartmentController extends SimpleFormController{
 
	private DepartmentService departmentService;
 
	public void setDepartmentService(DepartmentService departmentService){
		this.departmentService = departmentService;
	}
 
	//상위부서리스트(selectbox)는 부서정보클래스에 없으므로 , 상위부서리스트 데이터를 DB에서 구해서 별도의 참조데이터로 구성한다.
	@Override
	protected Map referenceData(HttpServletRequest request, Object command, Errors errors) throws Exception{
 
		Map referenceMap = new HashMap();
		referenceMap.put("deptInfoOneDepthCategory",departmentService.getDepartmentIdNameList("1"));	//상위부서정보를 가져와서 Map에 담는다.
		return referenceMap;
	}
 
	@Override
	protected Object formBackingObject(HttpServletRequest request) throws Exception {
		if(!isFormSubmission(request)){	// GET 요청이면
			String deptid = request.getParameter("deptid");
			Department department = departmentService.getDepartmentInfoById(deptid);//부서 아이디로 DB를 조회한 결과가 커맨드 객체 반영.
			return department;
		}else{	// POST 요청이면
			//AbstractFormController의 formBackingObject을 호출하면 요청객체의 파라미터와 설정된 커맨드 객체간에 기본적인 데이터 바인딩이 이루어 진다.
			return super.formBackingObject(request);
		}
	}
 
	@Override
	protected ModelAndView onSubmit(HttpServletRequest request,
			HttpServletResponse response, Object command, BindException errors) throws Exception{
 
		Department department = (Department) command;
 
		try {
			departmentService.updateDepartment(department);
		} catch (Exception ex) {
			return showForm(request, response, errors);
		}
 
		return new ModelAndView(getSuccessView(), "department", department);
	}
}

@Controller로 작성된 com.easycompany.controller.annotation.UpdateDepartmentController은 3개의 메소드로 이루어져 있다.
계층형 Controller인 기존의 UpdateDepartmentController와는 달리 각 메소드는 Override 할 필요없기 때문에 메소드 이름은 자유롭게 지을 수 있다.
쉬운 비교를 위해 SimpleFormController과 동일한 메소드 이름을 선택했다.

  • referenceData - 입력폼에 필요한 참조데이터인 상위부서정보를 가져와서 ModelMap에 저장한다.(by @ModelAttribute)
  • formBackingObject - GET 방식 호출일때 처리를 담당한다. 초기 입력폼 구성을 위한 부서데이터를 가져와서 ModelMap에 저장한다.
  • onSubmit - POST 전송시에 호출되며 폼 전송을 처리한다.

(POJO에 가까운) 프레임워크 코드들은 감춰졌고, 보다 직관적으로 비지니스 내용을 표현할 수 있게 되었다고 생각한다.

package com.easycompany.controller.annotation;
 
...
@Controller
public class UpdateDepartmentController {
 
	@Autowired
	private DepartmentService departmentService;
 
	//상위부서리스트(selectbox)는 부서정보클래스에 없으므로 , 상위부서리스트 데이터를 DB에서 구해서 별도의 참조데이터로 구성한다.
	@ModelAttribute("deptInfoOneDepthCategory")
	public Map<String, String> referenceData() {
		return departmentService.getDepartmentIdNameList("1");
	}
 
	// 해당 부서번호의 부서정보 데이터를 불러와 입력폼을 채운다
	@RequestMapping(value = "/updateDepartment.do", method = RequestMethod.GET)
	public String formBackingObject(@RequestParam("deptid") String deptid, ModelMap model) {
		Department department = departmentService.getDepartmentInfoById(deptid);
		model.addAttribute("department", department); //form tag의 commandName은 이 attribute name과 일치해야 한다. <form:form commandName="department">.
		return "modifydepartment";
	}
 
	//사용자가 데이터 수정을 끝내고 저장 버튼을 누르면 수정 데이터로 저장을 담당하는 서비스(DB)를 호출한다.
	//저장이 성공하면 부서리스트 페이지로 이동하고 에러가 있으면 다시 입력폼페이지로 이동한다.
	@RequestMapping(value = "/updateDepartment.do", method = RequestMethod.POST)
	public String onSubmit(@ModelAttribute("department") Department department, BindingResult bindingResult) {
 
		//validation code
		new DepartmentValidator().validate(department, bindingResult);		
		if(bindingResult.hasErrors()){
			return "modifydepartment";
		}
 
		try {
			departmentService.updateDepartment(department);
			return "redirect:/departmentList.do?depth=1";
		} catch (Exception e) {
			e.printStackTrace();
			return "modifydepartment";
		}
	}
}

참고자료

- 출처 : http://www.egovframe.go.kr/wiki/doku.php?id=egovframework:rte2:ptl:annotation-based_controller


'Programming > Spring Framework' 카테고리의 다른 글

Spring Boot Auto Configuration 예제  (0) 2020.04.21
2way ssl 인증을 위한 JKS 만들기  (0) 2020.03.10
코딩 시험과 TDD  (0) 2019.02.14

안드로이드는 수많은 git 프로젝트를 묶어서 하나의 프로젝트로 관리하고 있다. 이를 위해서 구글에서 python 기반의 새로운 관리 툴을 하나 선보였는데, 이것이 repo 이다.

repo 를 받고 설치하는 법등은 인터넷을 잘 찾아보면 내용이 있으니, 여기서 다루지는 않겠다. 여기서는 간단하게 여러개의 프로젝트를 하나의 repo 로 관리하는 법을 알아보려고 한다.

참고로 python 을 모르기 때문에 repo 가 어떻게 동작하는지를 분석하지는 않는다. 다만 단순하게 사용하는 입장에서 기술할 것이다.


1. git 프로젝트 등록

git 프로젝트를 등록하는 방법도 인터넷을 검색하면 설명이 잘 된 사이트를 찾을 수 있을 것이다. 간단하게 절차를 기록해보면 다음과 같다.

# mkdir <my_project>

# cd <my_project>

# git init

< add files into my_project >

# git add .

# git commit -a --allow-empty -m "Initial Code Commit"

이 상태로 생성된 git proejct 를 --bare 형태로 복사하고 공개 git 이라는 것을 알려준다.

# cd ..

# git clone --bare <my_project> <my_project>.git

# touch <my_project>.git/git-daemon-export-ok

다음의 명령은 git 을 http 프로토콜을 사용해서 clone 이 가능하도록 제공해주는 것이다.

# cd <my_project>.git

# git --bare update-server-info

# mv hooks/post-update.sample hooks/post-update

# chmod 755 hooks/post-update

==> git clone http://yourserver.com/~you/proj.git 으로 가능함

(물론 이때 웹서버가 사용자별 웹페이지에 접근 할 수 있도록 설정되어 있어야 한다. 그렇지 않다면, 기본 웹서버 위치에 git 프로젝트를 cloning 시켜야 한다)

아래 사이트를 참고하도록 한다.

참고 : 

http://khmirage.tistory.com/309

http://wiki.kldp.org/Translations/html/Git-User-Manual/


2. manifest git 만들기

우선 repo 프로젝트를 만들기 위해서 manifest.xml 이라는 파일을 만들어야 한다. repo 는 manifest.xml 파일을 갖는 git 프로젝트가 있는지를 확인하기 때문이다. 우선 default.xml 파일을 가지는 manifest 라는 프로젝트를 만들도록 한다.

# mkdir manifest

# cd manifest

# git init

# touch default.xml

# git add .

# git commit -a --allow-empty -m "Initial Code Commit"

default.xml 파일 내용은 다음과 같은 형식으로 구성한다. 추가하고 싶은 프로젝트를 <project> 태그 사이에 아래와 같은 형식으로 추가한다. 

<?xml version="1.0" encoding="UTF-8"?>
<manifest>
  <remote  name="origin"  : 원격 저장소 이름
           fetch=".."
           review="git://yourserver.com/" /> : 저장소 주소 --> repo upload 에 의해서 review 시스템에 upload 할 gerrit 의 호스트네임 입니다.

  <default revision="master"  : 브랜치명
           remote="origin" />
  <project path="your/path" name="your/path" />

  : path : 로컬에 저장될 path, name: git 저장소 이름
  <project path="your/path2" name="your/path2" />
...

...

</manifest>


3. repo 를 사용하기 전에 git daemon 을 설정해준다. 간단하게 git daemon 명령을 실행해도 되고, 데몬 실행 스크립트에 추가하여 자동으로 실행하게 하도록 해도 된다.

# git daemon --verbose --export-all --base-path=/your/base_path --enable=receive-pack &

--export-all : 이 옵션이 없는 경우 git-daemon-export-ok 파일이 있는 git 프로젝트에 한해서 git 프로토콜을 지원해준다.

--enable=receive-pack : 이 옵션이 없는 경우, 외부에서 push 가 불가능하다.


4. repo init 

위와 같이 manifest git 프로젝트가 만들어지면, repo 를 사용해서 초기화 할 수 있다.

# repo init -u git://yourserver.com/your_manifest_path/manifest

# repo sync

이제 부터 repo 를 사용하 프로젝트 관리가 가능해졌다. 필요한 프로젝트를 manifest 에 추가하면 repo 에서 관리가 가능해진다.


- 출처 : http://pinocc.tistory.com/137

'Programming > Git' 카테고리의 다른 글

Git Slack을 webhooks로 연동  (0) 2018.12.05
git commit 내용을 메일로 보내기  (0) 2016.01.27
GIT에 대한 내용정리  (0) 2015.08.27

How to Use git send-email

The preferred way to send patches is by email, using git send-email (more information about sending patches can be found on the Community page). This page explains how to use git send-email.

Installing send-email

You probably already have git already installed, but that's not necessarily enough to have also the send-email command available. You can check if send-email is available by running "git send-email --help". If it shows the man page for send-email, then send-email is available. Otherwise, you need to install the send-email command. Your distribution probably has a package for it; on Debian the package name is "git-email".

Configuring Your Name and Email Address

You should tell git your name and email address. You have probably done this already, but if not, run these commands:

git config --global user.name "My Name"
git config --global user.email "myemail@example.com"

Configuring the Mail Sending Options

git send-email sends the emails through your SMTP server, so you need to configure the server parameters. Refer to your email provider documentation to find the right parameters. This is how I would configure my mail settings:

git config --global sendemail.smtpencryption tls
git config --global sendemail.smtpserver mail.messagingengine.com
git config --global sendemail.smtpuser tanuk@fastmail.fm
git config --global sendemail.smtpserverport 587
git config --global sendemail.smtppass hackme

Storing the password in the git configuration file is obviously a security risk. It's not mandatory to configure the password. If it's not configured, git send-email will ask it every time the command is used.

Configuring the Default Destination Address

For PulseAudio, the patches should be sent to our mailing list. In order to avoid having to remember it and retyping it all the time, you can configure the address to be used by default by git send-email. As you may contribute to many projects using git, it does not make sense to set this option globally so instead we'll only set it in our clone of the PulseAudio code.

git config sendemail.to pulseaudio-discuss@lists.freedesktop.org

Avoiding sending mail to yourself

By default, git send-email will add the author of the patch to the Cc: field. When you send patches that you have written yourself, this means that a copy of each patch will be sent to your email address. If you don't like this, you can avoid this by setting this configuration option (see "git send-email --help" for the full list of possible values):

git config --global sendemail.suppresscc self

Using the send-email Command

See "git send-email --help" for the full reference. I'll go through only the basic usage here.

git send-email will ask a few questions before the patches are sent (update: newer git versions ask fewer questions, sometimes no questions at all). Most of the questions have a sensible default value shown in square brackets. Just press enter to use the default value. Type the answer to the question if you don't want to use the default answer. The questions are:

  • Who should the emails appear to be from?
    • This will be used as the "From" header. You should have configured your name and email address earlier, so the default is usually correct.
  • Who should the emails be sent to?
  • Message-ID to be used as In-Reply-To for the first email?
    • This should usually be left empty. Don't send patches as replies to regular discussion, that makes it harder to keep track of the patches.
  • Send this email?
    • The mail headers are visible above the question, so that you can check that everything looks OK.

Sending a Single Patch

Sending the last commit in the current branch:

git send-email -1

Sending some other commit:

git send-email -1 <commit reference>

Sending Multiple Patches

Sending the last 10 commits in the current branch:

git send-email -10 --cover-letter --annotate

The --cover-letter option creates an extra mail that will be sent before the actual patch mails. You can add write some introduction to the patch set in the cover letter. If you need to explain the patches, be sure to include the explanations also in the commit messages, because the cover letter text won't be recorded in the git history. If you don't think any introduction or explanation is necessary, it's fine to only have the shortlog that is included in the cover letter by default, and only set the "Subject" header to something sensible.

The --annotate option causes an editor to be started for each of the mails, allowing you to edit the mails. The option is always necessary, so that you can edit the cover letter's "Subject" header.

Adding Patch Version Information

By default the patch mails will have "[PATCH]" in the subject (or "[PATCH n/m]", where n is the sequence number of the patch and m is the total number of patches in the patch set). When sending updated versions of patches, the version should be indicated: "[PATCH v2]" or "[PATCH v2 n/m]". To do this, use the -v option (or the --annotate option and edit the "Subject" header of the mails).

Adding Extra Notes to Patch Mails

Sometimes it's convenient to annotate patches with some notes that are not meant to be included in the commit message. For example, one might want to write "I'm not sure if this should be committed yet, because..." in a patch, but the text doesn't make sense in the commit message. Such messages can be written below the three dashes "---" that are in every patch after the commit message. Use the --annotate option with git send-email to be able to edit the mails before they are sent.

Formatting and sending in two steps

Instead of using the --annotate option, one can first run "git format-patch" to create text file(s) (with the -o option to select a directory where the text files are stored). These files can be inspected and edited, and when that is done, one can then use "git send-email" (without the -1 option) to send them.


- 출처 : http://www.freedesktop.org/wiki/Software/PulseAudio/HowToUseGitSendEmail/

'Programming > Git' 카테고리의 다른 글

Git Slack을 webhooks로 연동  (0) 2018.12.05
repo 를 사용하여 프로젝트 관리하기  (0) 2016.06.15
GIT에 대한 내용정리  (0) 2015.08.27
           http://dogfeet.github.com/articles/2012/progit.html (
 Pro Git 
 
번역)

개념 정리

1. Git의 데이터는 파일 시스템의 Snapshot이라 할 수 있으며, 크기가 아주 작다. Git은 Commit 하거나 프로젝트의 상태를 저장할 때마다 파일이 존재하는 그 순간을 중요하게 여기며, 파일이 달라지지 않았으면 Git은 성능을 위해서 파일을 저장하지 않는다. 단지 이전 상태의 파일에 대한 링크만 저장한다. Git은 아래의 그림과 같이 동작한다.


2. Git은 파일을 Commited, Modified, Staged 세가지 상태로 관리한다. 
  - commited : 데이터가 로컬 저장소에 안전하게 저장됐다는 것을 의미함
  - modified : 수정한 파일을 아직 로컬 저장소에 commit하지 않은 것을 의미함
  - staged : 현재 수정한 파일을 곧 Commit할 것이라고 표시한 상태를 의미함.

3. Git은 Git Directory, Working Directory, Staging Area 세가지 영역으로 분리된다. 
  - Git Directory : Git이 프로젝트의 메타데이터와 객체 데이터베이스를 저장하는 곳을 말한다.  (.git)
  - Working Directory : 수정할 파일들이 있는 디렉토리
  - Staging Area : Git Directory에 있으며, 단순한 파일이고 곧 commit할 파일에 대한 정보를 저장한다. 

 

4. Working Directory의 모든 파일은 Tracked와 Untracked로 나뉜다. 
  - Tracked 파일은 이미 Snapshot에 포함돼 있던 파일이며, Unmodified, Modified, Staged 상태중 하나의 상태를 갖는다. 
  - Tracked 파일이 아닌 모든 파일은 Untracked 파일이다.
  - Git의 파일 라이브 사이클은 아래의 그림과 같다. 


5. Git에서 branch는 커밋 사이를 가볍게 이동할 수 있는 어떤 포인터 같은 것이다. git은 기본적으로 master 브랜치를 만든다.  Git은 최초로 커밋하면 master라는 이름의 브랜치를 만들고, 자동으로 master 브랜치가 가장 마지막 커밋을 가리키게 한다.

6. Git은 HEAD라는 특수한 포인터를 갖고 있다. 이 포인터는 지금 작업하고 있는 로컬 브랜치를 가리킨다. "git branch <brnach명>" 명령은 브랜치를 만들기만 하고 브랜치를 옮기지는 않는다. "git checkout <branch명>"명령으로 HEAD가 가리키는 브랜치로 이동할 수 있다.


7. Git의 commit Data 구조





Git 설치 후 초기 환경 설정 (~/.gitconfig 파일 설정)

1. gitconfig --global user.name dimdim  ==> 작업자 이름 설정

2. gitconfig --global user.email dimdim@test.com  ==> 작업자 email 설정

3. gitconfig --global --list ==> 설정값 확인


GIT 저장소 만드는 방법

1. 이미 존재하는 프로젝트 디렉토리에 git 저장소 생성
   - 
git init 명령은 
"master" 라는 이름을 갖는 local branch를 생성한다. 

$ git init             // CWD에 ".git" 하위 디렉토리 생성
$ git add *         // CWD의 모든 파일 및 하위 디렉토리를 staged 상태로 만든다.
$ git  commit -m 'initail project version'    // stage된 파일을 commit 한다. (commit message가 반드시 필요함!!!)
$ git commit -a -m 'commit message'     // 수정된 Tracked 파일들을 staging 단계 없이 바로 commit 함  


 2. 이미 존재하는 Repository로 부터 복사한다.
   - git clone 명령은 "origin" 이라는 이름을 갖는 remote 와 "master" 라는 이름을 갖는 local branch를 생성한다.
$ git clone  git://githuyb.com/schacon/grit.git   // CWD에 "grit"이라는 디렉토리를 만들고 그 안에 .git 디렉토리를 만든다.
 혹은
$ git clone  git://githuyb.com/schacon/grit.git mygrit // CWD에 "mygrit"이라는 디렉토리를 만들고 그 안에 .git 디렉토리를 만든다. 


Ignore 설정
-".gitignore" 파일 생성 후 ignore 할 파일 목록을 기록 (라인 분리)
- 예제(eclipse 설정파일 무시하기)

/.classpath

/.springBeans

/.project

/target/

/.settings 


용어 정리???

fast forward merge : A 브랜치에서 다른 B 브랜치를 Merge할 때 B가 A 이후의 커밋을 가리키고 있으면 A가 그저 B의 커밋을 가리키게 한다. 이런 머지를 Fast Forward 머지라고 한다.



명령어 정리 

git add [Directory|파일 경로//Untracked 파일을 Tracked 파일로 변경하거나, Tracked&수정된 파일을 staging 한다.
git status  //현재 상태 보기 


git commit -m "commit message"  //로컬 저장소에 commit(저장)

git commit -a -m "commit message"  // staging area 생략 commit (git add 과정 생략)

git commit --amend //수정한 내용이 없으면 커밋 메세지만 변경하고 수정한 내용이 있으면 이전 커밋으로 수정내용을 포함하여 새로운 커밋으로 덮어쓴다.

git rebase -i HEAD~2 // HEAD으로 부터 2개의(HEAD 포함) commit을 합친다.


git rm <filename>  // 파일을 삭제한 후 staged 상태로 만든다. (work directory의 파일도 삭제됨)

git rm --cached <filename> // 해당 파일을 untracked 상태로 변경한다. (work directory의 파일은 그대로 있는다. ignore를 빼먹은 경우 사용)

git mv <origin_file> <target_file> // 히스토리를 유지한 상태로 파일이름을 변경하고, 해당 변경사항이 staged 상태가 된다. 

     ==> "mv <origin_file> <target_file>; git rm <origin_file>; git add <target_file>" 과 동일하다.

git log [-p] -5  [--pretty=online|short|full|fuller]  //commit 로그를 확인한다.(최근 5개의 로그 확인) (-p 옵션은 각 커밋의 diff 결과를 보여준다.)
git log --pretty=format:"%h %s" --graph // 브랜치/머지 히스토리를 아스키 그래프로 보여준다.

git log --stat // 히스토리 통계를 보여준다.


git checkout <commit_id> // 특정 커밋시점으로 되돌린다. 
git checkout <branch명>  //지정된 브랜치가 가리키는 커밋을 HEAD가 가리키게 한다. (작업 디렉토리의 파일은 그대로 유지됨)

git checkout -b <branch명> //새로운 브랜치를 만들고  해당 브랜치를 checkout 한다.

git checkout -b <new branch name> <server branch name> // 서버에서 생성되어 있는 브랜치 가져오기

git checkout  <commit_id> -- <file> // 특정 커밋시점의 특정 파일만 되돌린다.

git checkout -- <file>  // work directory의 변경된 unstaged 파일을 최신 커밋된 내용으로 덮어쓴다. (작업중이던 내용이 있으면 날라가며, 복구 불가!!!)

git checkout -f // 현재 HEAD가 가리키는 커밋으로 작업 디렉토리의 파일을 되돌려 놓는다. (작업중이던 내용이 있으면 날라가며, 복구 불가!!!)

git checkout -f <branch>//  branch가 가리키는 커밋으로 작업 디렉토리의 파일을 되돌려 놓는다. (작업중이던 내용이 있으면 날라가며, 복구 불가!!!)

git reset HEAD <staged_file> // staged file을 unstaged로 변경한다.


git reset HEAD^    // 최종 커밋을 취소. 워킹트리는 보존됨. (커밋은 했으나 push하지 않은 경우 유용)

git reset HEAD~2     //마지막 2개의 커밋을 취소. 워킹트리는 보존됨.

git reset --hard HEAD~2    // 마지막 2개의 커밋을 취소. index 및 워킹트리 모두 원복됨.

git reset --hard ORIG_HEAD    // 머지한 것을 이미 커밋했을 때,  그 커밋을 취소. (잘못된 머지를 이미 커밋한 경우 유용)

git revert HEAD    // HEAD에서 변경한 내역을 취소하는 새로운 커밋 발행(undo commit). (커밋을 이미 push 해버린 경우 유용)


git diff  // work directory와 staged 영역 비교

git diff --cached // staged 영역과 & 저장소 commit 간의 비교 ("git diff --staged" 명령과 동일)


git branch <branch명> //새로운 branch를 만든다

git branch [-a]  //branch 목록을 조회한다.
git branch -m [old_branch] [new_branch]

git branch -d <branch명> //브랜치 삭제

git tag [-l] [v.1.4.*]   //Tag 목록조회 [v1.4 버전들의 목록만 조회]
git tag -a <tag명> -m '[tag message]'  //주석 태그 추가   ex>git tag -a v1.0 -m 'add first release tag'
git tag -s <tag명> -m '[tag message]' //서명된주석 태그 추가
git tag <tag명>  //경량 버전 태그 추가 
git tag -a <tag명> [commit hash]  // 특정 commit의 태그 추가

git show <tag명>
  // 태그정보와 해당 태그의 commit 정보를 확인한다. 

git remote [-v] // 원격 저장소 정보 확인
git remote show <remote명> //리모트 저장소 살펴보기 
git remote add <remote명> <원격저장소URL> //원격 저장소를 추가한다.

git remote rename <old_name> <new_name> //remote repository 이름 변경
git remote rm <remote-name>  //remote repository 삭제 
git fetch [remote명]  //Remote Repository의 최신 변경사항을 Local Respository로 가져옮
git pull [remote명] [branch명]   //remote 저장소 브랜치에서 데이터를 가져와 자동으로 로컬 브랜치와 머지함
git push [remote명] [branch명] //local branch의 내용을 remote 저장소에 push 한다
git push [remote명] [tag명]  //local에 생성한 tag remote 에 추가한다.
git push [remote명] --tags  //local에 존재하는 tag중에 remote에 존재하지 않는 모든 태그들을 push 한다 

git push <remote명> <local branch>:<remote branch> // 내가 최초로 서버에 브랜치 만들기


git merge <from_branch> <to_branch> // from branch를 to branch와 머지한다.

git mergetool // merge할 툴을 실행시켜 준다.


git submodule add <Git Repository URL> <Directory Path>  // Git Submodule 추가

git submodule init  // Git Submodule 초기화 [init -> update]

git submodule update // Git Submodule 소스 코드 받아오기 [init -> update]


git hash-object -w <filename> // 파일을 Git에 저장하고 해쉬코드를 출력한다.

git cat-file -p <hashcode> // 해쉬코드에 해당하는 개체의 내용을 출력한다.

git cat-file -t <hashcode> // 해쉬코드에 해당하는 개체의 타입을 출력한다.

git cat-file -p master^{tree} // master 브랜치가 가리키는 Tree 개체의 내용을 출력한다.


- 출처 : http://dimdim.tistory.com/entry/GIT에-대한-내용정리-정리중

'Programming > Git' 카테고리의 다른 글

Git Slack을 webhooks로 연동  (0) 2018.12.05
repo 를 사용하여 프로젝트 관리하기  (0) 2016.06.15
git commit 내용을 메일로 보내기  (0) 2016.01.27

1. 최초 요청 시 HTTP Request와 Response

Client's request

Client  ------------------------------> Server

Request URL: http://i1.daumcdn.net/cfs.tistory/static/images/logo.gif


Server's response

Client <------------------------------ Server

Header에 "Last-Modified"를 설정하여 응답한다. (아래의 그림 참조)




2. 재 요청 시 HTTP Request Response

Client's request

Client  ------------------------------> Server

Request URL: http://i1.daumcdn.net/cfs.tistory/static/images/logo.gif

최초 응답 시 받은 "Last-Modified"를 참조하여 Header에 "If-Modified-Since"를 설정하여 요청한다.
(Browser가 알아서 설정)

Server's response

Client <------------------------------ Server

Server는 "If-Modified-Since" 값과 비교하여 변경 사항이 없으면 HTTP Status Code 304로  응답한다.

(아래의 그림 참조)



3. Code Snipet


public void downloadResource(@PathVariable String resourceName,
						       HttpServletRequest request,
						      HttpServletResponse response) {
																				
		WebResource webResource = ResourceService.getResource(resourceName);
		long lastModified = webResource.getLastModified();
		Calendar calendar = Calendar.getInstance();
		calendar.setTimeInMillis(lastModified);
		calendar.set(Calendar.MILLISECOND, 0);
		lastModified = calendar.getTimeInMillis();
		
		long ifModifiedSince = request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
		if (ifModifiedSince != -1 && ifModifiedSince >= lastModified) {
			response.setStatus(HttpStatus.SC_NOT_MODIFIED);
			return;
		}
		
		response.setDateHeader(HttpHeaders.LAST_MODIFIED, lastModified);
		...
	}

- 출처: http://irooti.tistory.com/8

소스코드 문서화

 

개발자들은 개발 문서작업을 진행하는 데 많은 노력과 시간을 투자한다.

직접 모든 비즈니스 로직이나 모듈을 문서로 작성하기란 만만치 않은 작업이다.

MSDN형태의 도움말을 구독성이 뛰어나고 쉽게 구현하는 방법이 있어 이를 소개하고자 한다.

 

우선 소스코드 문서를 XML 파일로 바꿔주는 C#컴파일러의 기능에 대해 알아보자.

C# 컴파일러가 코드 주석을 XML로 변경하는 과정을 담당한다.

소스코드 주석 : /// , /** */

 

 XML 주석에서 사용하는 엘리먼트 정보들은 아래와 같다.

 

Element

정의

<c>

설명에 있는 텍스트를 코드로 표시하는 데 사용

<code>

여러 줄을 코드로 표시하는 데 사용

<example>

설명하고 있는 항목에 대한 코드 예제를 나타냄

<param>

파라미터를 설명한다

<remarks>

해당 멤버에 대한 설명을 작성하는 데 사용된다.

<returns>

멤버의 반환 값을 문서화한다.

<see>

관련된 항목들을 링크하는 데 사용한다.

<summary>

해당 항목에 대한 요약을 문서화 하는 데 사용한다.

<value>

해당 속성을 문서화하는데 사용한다.

[ 1]XML 주석 Element 설명

VS.NET 툴에서 기본적으로 IDE 기능들을 제공한다.

 

[
그림 1]VS.NET에서의 XML 주석

 

[그림 1]처럼 메소드 앞에 /// 3번 누르면 자동적으로 클래스나 메소드 및 필드에 맞는 element들이 생성되는 것을 볼 수 있다.

 

[
그림 2] VS.NET에서의 XML 주석


이것 외에 부연 설명이나 예제코드참고자료 등을 작성하고 싶을 경우에는 [그림 2]처럼 element를 추가하여 작성할 수 있다.

 

소스 코드에 네임스페이스클래스메소드나 멤버 변수에 XML 주석을 작성을 다 했으면 컴파일 시 /doc 옵션을 이용해 XML 문서를 추출해야 한다.

 

VS.NET 에서 프로젝트명 – 속성 – 빌드 탭에 XML문서 파일 설정을 체크하고 저장한다.

[
그림 3] VS.NET을 이용하여 XML 문서 출력

 

[그림 3]와 같이 설정한 후 컴파일을 하게 되면  bin\Debug\ePlayon.Batoo.BLL.XML 파일에 주석 문서가 생성된다.

아래 그림은 문서의 내용을 일부 발췌하였다.

 

[
그림 4] XML 문서 형식

 

파란색으로 표시된 내용을 보면 M:, T:와 같은 포맷 문자가 있다.

아래 [ 2]는 포맷 문자를 정리한 것이다.

 

Element

정의

N

네임스페이스

T

형식(클래스인터페이스구조체열거형델리게이트)

F

필드

P

형식 속성

M

메소드(생성자와 오버로드된 연산자 포함)

E

이벤트

!

에러에 대한 정보를 나타내는 에러 문자열

[표 2]XML 주석 포맷 문자

C# 컴파일러를 통해 생성된 이 xml 파일은 사용자 도움말 형태로 변환하기 위한 xml 정의 파일이라 할 수 있다.

그럼 사용자 도움말 형태로 만들기 위해 마이크로소프트에서 프리웨어로 제공하는 sandcastle 이라는 프로그램을 사용하여 작업을 진행해 보자.

Microsoft의 개발자들이 자신들이 도움말을 만들 때 사용하는 툴을 공개하였는데 그것이 바로 Sandcastle 이다.

Sandcastle 을 설치하기에 앞서 도움말 형태의 뷰어를 설치해야 한다.

아래 링크에서 HTML Help workshop  sandcastle, 그리고 sandcastle UI 프로그램을 다운받아 차례로 설치하자.

 

Microsoft HTML Help Downloads :

http://go.microsoft.com/fwlink/?linkid=14188

 

sandcastle Downloads:

http://www.codeplex.com/Sandcastle/Release/ProjectReleases.aspx?ReleaseId=9921

 

sandcastle help file builder Downloads :

http://www.codeplex.com/SHFB/Release/ProjectReleases.aspx?ReleaseId=9848

 

Sandcastle 원리

 

[
그림 5] Sandcastle 처리 원리

 

[그림 5] sandcastle이 내부적으로 소스코드를 문서화 해주는 과정을 나타내고 있다.

우선 VS.NET 툴에서 컴파일 시 XML 문서 파일을 출력하도록 하는 명령이 csc /doc 임을 알 수 있다컴파일이 성공하면 바이너리 폴더에 dll 파일과 xml 파일이 생성된다.

이때 SandCastle  BuildAssembler 에서 xml 파일과 dll 파일을 분석하여 필요한 정보들을 생성한 후 이 결과를 HTML Help Compiler에서 컴파일하여 도움말 형태로 출력시킨다.

 

그럼 sandcastle 툴을 사용하여 직접 소스 코드 도움말을 생성해보자.

앞서 봤듯이 dll 파일과 xml 파일은 있다는 가정 하에 테스트를 진행해 보겠다.

Sandcastle Help File Builder에서 “Sandcastle Help File Builder GUI” 를 실행시킨다.

 

[
그림 6] Sandcastle Help File Builder 메인 화면

 

[그림 6]은 Sandcastle의 기본 UI 이다. Add 버튼을 이용하여 바이너리(dll) 파일을 등록할 수 있다.

하지만 테스트할 바이너리는 웹 사이트의 모든 참조 라이브러리 프로젝트를 생성해야 하므로 수동으로 직접 입력하는 방법보다 솔루션 파일을 이용해 도움말을 생성해 보도록 하겠다.

 

[
그림 7] 솔루션 파일로 어셈블리 참조

 

[그림 7]에서 보듯이 Project 메뉴에 New Project from Visual Studio Project… 를 실행하여

솔루션 파일을 바인딩 시켜보자.

 

[
그림 8] 솔루션 파일로 바인딩 시 선택화면

 

솔루션 파일을 등록하게 되면 [그림 8]과 같이 나오면 Select 를 클릭한다.

 

[
그림 9] 솔루션 파일로 바인딩 시 UI

 

[그림 9]에서 보듯이 관련 참조된 어셈블리와 XML파일들이 바인딩 되는 것을 볼 수 있다.

이제 모든 준비가 끝이 났다.

 

우선 현재 sandcastle 프로그램을 저장하여 파일명.shfb파일을 도움말을 설치할 폴더에 저장한 후

Documentation 메뉴에 Build Project를 실행시켜보자.

 

[
그림 10] 도움말 생성 컴파일

 

[그림 10]의 아래 Output에서 보듯이 성공적으로 컴파일을 완료하였다.

저장한 폴더의 Help 폴더에 보면 Documentation.chm 파일이 생성된 것을 확인할 수 있다.

 

[그림 11] 도움말 생성 UI

 

[그림 11]에서 보듯이 성공적으로 도움말이 생성된 것을 확인하였다.


객체의 구조와 각 네임스페이스클래스메소드필드속성별로 설명이 되어 있다.


-출처 : http://tit99hds.egloos.com/1590155

'Programming > C#, ASP' 카테고리의 다른 글

Mono project  (0) 2015.04.22
Deployment Dependency Problems  (0) 2015.02.06
C# XML/XmlReader  (0) 2015.01.20
Troubleshooting Common Problems with the XmlSerializer  (0) 2015.01.20
ASP.NET Web API 도움말 페이지 작성하기  (0) 2015.01.15
"올바른 성장과 따뜻한 나눔"이 있는 넥스트리

HTTP 는 대형 네트워크 시스템 아키텍처를 염두한 프로토콜이기에 단순하면서도 다양한 스펙들이 존재합니다. 대형 네트워크 시스템이다 보니 서로 주고 받는 정보가 많을 것이고 이에 네트워크 부하나 서버 부하가 발생할 소지가 매우 큽니다. 그런 상황을 위해 HTTP에는 단순하면서도 강력한 캐싱 메커니즘을 제공하고 있습니다.



브라우저와 웹서버와의 대화

브라우저는 웹서버에게 이미지, CSS 그리고 동적생성 정보(HTML, XML, JSON... 등 많은 컨텐츠를 요청합니다. 그런데 동일한 리소스(이하 컨텐츠)를 여러번 요청하는 경우가 굉장히 많습니다. 전형적인 예가 이미지나 CSS 같은 정적 컨텐츠인데 이런 것을 요청할 때 브라우저와 웹서버가 어떻게 대화하는지 아래 그림으로 살펴보겠습니다.

  • 1: /AAA 컨텐츠를 주세요
  • 2: OK 주었습니다. 이 컨텐츠의 마지막 갱신일은 2009년 9월 7일... 입니다. (컨텐츠 전송)
  • 잠시 후...
  • 3: /AAA 컨텐츠를 주세요. 아. 제가 캐시에 가지고 있는 건 마지막 갱신일이 2009년 9월 7일 ... 이네요
  • 4: 변경사항 없습니다. 그냥 캐시에 있는 것 쓰세요. (아무것도 전송 안함)
브라우저 기본설정이라면 캐시에 있을 경우 브라우저가 시작하고 한 번 확인하고 두 번 다시는 GET 확인 요청을 하지 않습니다. 그냥 캐시에 있는 걸 씁니다. 브라우저를 다시 시작할 경우만 확인 요청을 합니다. 서버에 아예 요청도 안 가니 네트워크나 서버 부하는 무지 감소합니다. (IE 인터넷 옵션 설정에서 "페이지를 열 때마다"로 설정된 경우는 매번 확인 요청을 합니다.)


동적 컨텐츠와 304

서블릿이나 JSP 등으로 생성하는 동적 컨텐츠는 매번 정보가 갱신되어야 하기에 이런 메커니즘을 사용하지 않습니다. 그런데 간혹 서버 컨텐츠 마지막 갱신일을 알고 매우 요청이 많은 페이지 일 경우 위 메커니즘을 이용하는 것도 좋은 방법이겠네요. 다음 예는 JSP로 구현한 소스 예입니다.

01<%
02  // 서버 컨텐츠의 마지막 갱신을 얻어온다.
03  java.util.Date date = getLastModified();
04  long clientDate = request.getDateHeader("If-Modified-Since");
05  long serverDate = date.getTime();
06  if (clientDate != -1 && clientDate >= serverDate) {
07      response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
08      return;
09  }
10  response.setDateHeader("Last-Modified", serverDate);
11%>

비즈니스 로직이 실행되기 전 해당 정보 갱신일을 살펴보고 다시 줄 것인지 304를 던질 것인지 판단하는게 좋겠습니다. 그래야 필요없는 서버 로직이 수행되지 않습니다.


웹 애플리케이션 성능 향상

기본에 충실하는게 좋습니다. 우선은 문제가 되는 비즈니스 로직을 충분히 튜닝하십시오. 그런 후 특별한 캐싱 장치를 사용하거나 가능하면 위의 경우처럼 표준 캐싱 메키니즘을 충분히 활용하십시오.

그리고 비즈니스 로직의 성능을 높이기 위해 특정 비즈니스 로직을 열심히 튜닝하기 전에 우선 Fiddler 같은 도구로 브라우저와 서버가 무슨 통신을 하는지 살펴보는게 좋습니다. 생각보다 필요없는 요청이 많이 발생할 수도 있습니다.

--

웹사이트를 빠르게 하는 다른 관점의 방법들도 있네요.


 - 출처 : http://greatkim91.tistory.com/118

C# 개발 툴 정하기

구글에 'free c# ide' 를 검색하니 사이트에 다양한 C# 개발 툴들이 있었다.

http://stackoverflow.com/questions/3640866/is-there-a-free-opensource-c-sharp-ide-in-windows

 

여러 개발 툴중에 평이하게 괜찮은 것은 비주얼 스튜디오 익스프레스이겠지만

크로스 플랫폼을 지원하는 툴을 사용하고 싶었다.

 

구글에 '리눅스 c#' 이라고 검색하니 나오는 것이 모노디벨롭이다.

모노디벨롭을 c# 개발 툴로 선정하였다.

 

 

 

 

 

모노 프로젝트 (C# .NET 크로스 플랫폼) 홈페이지

http://www.mono-project.com/

 

 

 

 

다운로드

http://monodevelop.com/

 

 

 

 

 

설치 파일

XamarinStudio-5.0.1.3-0.msi    38.4MB (40,300,544 바이트)

gtk-sharp-2.12.25.msi    24.5MB (25,690,112 바이트)

 

 

 

 

XamarinStudio-5.0.1.3-0.msi 를 설치하려 했는데

gtk-sharp-2.12.25.msi 를 먼저 설치하라고 한다.

 

 

 

 

설치 경로

홈 폴더 --

C:\CSharp\

 

gtk --

C:\CSharp\GtkSharp\2.12\

 

Xamarin Studio --

C:\CSharp\Xamarin Studio\    실패함

원래 위 장소에 설치하려 했는데 설치후 업데이트를 하면 설치장소가 기본 디렉터리로 변경된다.

C:\Program Files (x86)\Xamarin Studio\

 

워크스페이스 --

C:\CSharp\Projects\

 

설치파일 저장 --

C:\CSharp\util\

 

 

 

 

 

실행하기

C:\Program Files (x86)\Xamarin Studio\bin\XamarinStudio.exe

 

 

 

개발 툴 이름이 바뀌었다.

mono develop --> Xamarin Studio

 

별다른 설정없이 한글버전으로 설치되었다.


 - 출처 : http://blog.naver.com/lobolook/220185654654

'Programming > C#, ASP' 카테고리의 다른 글

.NET 소스코드 문서화  (0) 2015.07.13
Deployment Dependency Problems  (0) 2015.02.06
C# XML/XmlReader  (0) 2015.01.20
Troubleshooting Common Problems with the XmlSerializer  (0) 2015.01.20
ASP.NET Web API 도움말 페이지 작성하기  (0) 2015.01.15

+ Recent posts