학습하며 정리한 내용을 올리는 것이니, 참고용으로만 봐 주시길 바랍니다.
자바 스프링 프레임워크(renew ver.) - 신입 프로그래머를 위한 강좌

세션 & 쿠키

  1. 세션 생성
public String memLogin(Member member, HttpServletRequest request) {
    ...
    HttpSession session = request.getSession();
    session.setAttribute("member", mem);
    return "/member/loginOk";
}

public String memLogin(Member member, HttpSession session) {
    ...
    session.setAttribute("member", mem);
    return "/member/loginOk";
}

1.1 세션 회원정보 수정

public ModelAndView modifyForm(HttpServletRequest request) {
    HttpSession session = request.getSession();
    Member member = session.getAttribute("member");

    ModelAndView mav = new ModelAndView();
    mav.addObject("member", service.memberSearch(member));
    ...
  1. 세션 삭제
public String memLogout(Member member, HttpSession session) {
    ...
    session.invalidate();
    return "/member/logout ";
}

세션 주요 메소드

  • getId(): 세션 ID 반환
  • setAttribute(): 세션 객체에 속성을 저장
  • getAttribute(): 세션 객체에 저장된 속성을 반환
  • removeAttribute(): 세션 객체에 저장된 속성 제거
  • setMaxInactiveInterval(): 세션 객체의 유지 시간 설정
  • `get ~
  • invalidate(): 세션 객체의 모든 정보삭제
  1. 쿠키 생성
public String mallMain(Mall mall, HttpServletResponse) {
    Cookie genderCookie = new Cookie("gende", mall.getGender());

    if (mall.isCookieDel()) {
        genderCookie.setMaxAge(0);
        mall.setGender(null);
    } else {
        genderCookie.setMaxAge(60 * 60);
    }
    response.addCookie(genderCookie);
    return "/mall/main";
  1. 쿠키 사용
@public String mallIndex(Mall mall, @CookieValue(value="gende", required=false) Cookie genderCookie, HttpServletRequest request) {
    if (genderCookie != null)
        mall.setGender(genderCookie.getValue());
    return "/mall/index";
}
}

리다이렉트 & 인터셉터

리다이렉트

: reutrn "redirect:/page";
:

public ModelAndView removeForm(HttpServletRequest request) {
    ModelAndView mav = new ModelAndView();
    ...
    if (null == member) {
        mav.setViewName("redirect:/");
    } else {
        mav.addObject("member", member);
        mav.setViewName("/member/removeForm");
    }

    return mav;
}

인터셉터

: 리다이렉트를 사용해야 하는 경우가 많을 때 HandlerInterceptor를 이용할 수 있다.

--------------- ###########################
--------------- # ------ Request -------- #
--------------- ###########################
--------------------------- |
--------------- ###########################
--------------- #--- DispatcherServlet ---#
--------------- ###########################
--------------------------- |
#######################################################
#---------- HandlerInterceptor(인터페이스) ------------#
#-----------------------------------------------------#
# PreHandle()------PostHandler()-----afterCompletion()#
#######################################################
------ | -------------- ^ ----- | --------- ^
------ | -------------- | ----- | --------- |
--- ######################## -- | --------- |
--- #  Handler(Controller) # -- | --------- |
--- ######################## -- |  #####################
------------------------------- ㄴ># ------ View ----- #
---------------------------------- #####################
        
#######################################################
# ---------------------- Response ------------------- #
#######################################################
  • preHandle(): Controller가 작업하기 전 작업한다. 리다이렉트를 대체 가능하다. 주로 쓰인다.
  • postHandle(): Controller가 작업한 후 작업한다.
  • afterCompletion(): Controller 와 View가 작업한 후에 작업한다.

: 주로 HandlerInterceptor 인터페이스를 구현하기 보다는, 이들이 구현되어 있는 HandlerInterceptorAdapter 클래스를 상속받아 사용한다.

public class MemberLoginInterceptor extends HandlerInterceptorAdaptor {

    @Override
    public bollean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HpptSession session = request.getSession(false);
        if (session != null) {
            return true;
        }

        response.sendRedirect(request.getContextPath() + "/"); // main 페이지로 보낸다
        return false;
    }
}
// servlet-context.xml
// 스프링 설정파일

<interceptors>
    <interceptor>
        <mapping path="/member/modifyForm" /> // 
        <mapping path="/member/removeForm" /> // 이곳으로 요청이 들어왔을 때 
        <beans:bean class="com.bs.lec21.member.MemberLoginInterceptor" /> // 이 인터셉터가 동작을 해라
    </interceptor>
</interceptors>
<mapping path="/member/**" /> // 멤버 하위의 모든 경로에 대해 동작을 하는데
<exclude-mapping path="/member/joinForm" /> // 여기는 제외해라
<exclude-mapping path="/member/join" />
<exclude-mapping path="/member/login" />

'Backend > Spring' 카테고리의 다른 글

Spring - 데이터베이스  (0) 2021.02.14
Spring - MVC  (0) 2021.02.04
Spring - 스프링 컨테이너의 생성관련  (0) 2021.02.02
Spring - Bean과 의존객체 주입  (0) 2021.02.01
Spring - Dependency Injection  (0) 2021.01.31

학습하며 정리한 내용을 올리는 것이니, 참고용으로만 봐 주시길 바랍니다.
자바 스프링 프레임워크(renew ver.) - 신입 프로그래머를 위한 강좌

MVC를 이용한 웹 서비스

  1. MVC의 기본 구조: M1 구조에 비해 유지보수 및 기능개선에 이점

    브라우저 <-> Controller - Service - DAO <- (Model) -> DB
                 |
                View
    .
    Controller: 이것들을 Control(기능, 뷰 등을 Control)
    Service: 기능
    View: 사용자에게 보여주는 부분
    Dao: DB와 연동 
  2. 스프링 MVC 프레임워크 구조

               HandlerMapping   HandlerAdapter <-> Controller
                     |           / 
    브라우저 <-> DispatcherServlet <
                     |           \
                    View         ViewResolver

    2.1. 클라이언트가 브라우저를 통해 요청을 보내면 DispatcherServlet이 받는다.
    2.2. DispatcherServlet은 이를 HandlerMapping에게 보내 가장 적합한 Controller를 찾아준다.
    2.3. DispatcherServlet은 HandlerAdapter에게 요청을 보내고 HandlerAdapter는 HandlerMapping이 찾아준 Controller 객체 아래에 있는 메소드들 중 가장 적합한 메소드가 무엇인지 찾아준다. 그 후, 처리된 결과를 Model(View 포함)이라는 데이터를 가지고와서 DispatcherServlet에게 건네준다.
    2.4. 결과(?Model?, View)를 가지고 DispatcherServlet은 ViewResolver에게 보내 View에 해당하는 가장 적합한 JSP 문서를 찾아달라고 요청하여, ViewResolver는 View를 선택해준다.
    2.5. 찾아진 JSP가 있는 View에게 가서 응답(JSP)을 클라이언트에게 보내 주도록 한다.

  3. DispatcherServlet 설정하기

    // WEB-INF/web.xml
    //
    <servlet>
     <servlet-name>appServlet</servlet-name> // 서블릿 별칭
     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> // 서블릿명(패키지 이름을 포함한 전체 서블릿 명)
     <init-param> // 초기 파라미터
         <param-name>contextConfigLocation</param-name>
         <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value> // 스프링 설정파일 (DispatcherServlet이 등록될 때 스프링 설정파일까지 설정해 주도록 함)
     </init-param>
     <load-on-startup>1</load-on-startup>
    </servlet>
    //
    <servlet-mapping>
     <servlet-name>appServlet</servlet-name> // 위에서 별칭으로 지정한 appServlet이
     <url-pattern>/</url-pattern> // root(/)로 들어오는 모든 요청을 처리하도록
    </servlet-mapping>
    • DispatcherServlet 설정 시 위의 초기 파라미터(init-param)에서 지정한 파일을 이용해 스프링 컨테이너를 생성한다. 초기 파라미터를 지정하지 않은 경우 서블릿별칭(appServlet-context.xml)(이 파일이 존재해야함.)을 이용해 스프링 컨테이너를 생성한다.
    • 이를 통해 HandlerMapping, HandlerAdapter, ViewResolver가 생성된다.
    1. Controller 객체
      1. Controller 생성 방법
    // servlet-context.xml
    //
    <annotation-driven /> // 스프링 컨테이너 사용을 위한 부가적인 클래스들이 빈 객체로 스프링 설정파일에 등록됨.
    // Controller 객체로 사용할 클래스 정의
    //
    @Controller
    public class HomeController {
      ...
    }

    4.2. @RequestMapping
    :사용자로부터 들어오는 요청을 Controller가 메소드로 맵핑

    http://localhost:8090/ch08/success (ch08: project path?name?)
    //
    @RequestMapping("/success")
    public String success(Model model) {
     return "success";
    }

    4.3. Model 타입의 파라미터
    : Controller는 Model 객체에 데이터를 담아 DispatcherServlet에 전달

    @RequestMapping("/success")
    public String success(Model model) {
     model.setAttribute('tempData', "model has data");
    //
     return "success";
    }

    4.3. view 객체
    : 스프링 설정파일에 ViewResolver를 생성

    <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
     <beans:property name="prefix" value="/WEB-INF/views/" />
     <beans:property name="suffix" value=".jsp" />
    </beans:bean>
    @RequestMapping("/success")
    public String success(Model model) {
     return "success";
    }

    : return값의 success 앞뒤로 ViewResolver의 prefix, suffix를 붙여주어 /WEB-INF/views/success.jsp 라는 View를 만들어준다.

스프링 MVC 웹 서비스 파일의 구성

  1. web.xml

    • DispatcherServlet을 등록한다.
    • 사용자의 모든 요청을 받기 위해 서블릿 맵핑 경로를 /로 설정한다.
  2. servlet-context.xml

    • DispatcherServlet을 서블릿으로 등록할 때 contextConfigLocation이라는 초기화 파라미터를 servlet-context.xml로 지정하고 있었다.
    • 이는 스프링 설정파일이다.
    • 클래스로부터 객체(bean)을 생성하고 조립하는 역할을 한다.
    • 웹 구동을 위한 resource(jsp, html, css) 등을 맵핑해 준다.
    • InternalResourceViewResolver의 prefix, suffix를 정의하여 View로써 response를 응답하도록 해 준다.

Service Annotation

: 스프링 설정파일(servlet-context.xml)에 Bean 객체를 생성하지 않아도, 서비스 객체마다 @Service Annotation을 붙여주면 자동으로 스프링 컨테이너에 담겨지고, Controller에서 @Autowired만 붙여주면 자동으로 서비스 객체를 사용할 수 있음.
: @Component, Repository
: Dao 또한 동일하게 사용가능
: @Repository("memService"), @Repository(name="memService") 와 같이 key-value 형식으로 특정 이름을 지정할 수 있으나 권장되진 않음.

HttpServletRequest 대신 RequestParam 사용하기

@RequestMapping(value="/memLogin", method=RequestMethod.POST)
// public String memLogin(Model model, HttpServletRequest request) {
public String memLogin(Model model, @RequestParam("memId") String memId, @RequestParam("memPw") String memPw) {    
//    String memId = request.getParameter("memId");
//    String memPw = request.getParameter("memPw");
//    
    Member member = service.memberSearch(memId, memPw);
<form action="/lec17/memLogin" method="post">
    ID : <input type="text" name="memId" ><br>
    PW : <input type="password" name="memPw" ><br>
    <input type="submit" value="Login" >
</form>
  • @RequestParam(value="memPW", required=false, defaultValue="1234") 와 같이 필수로 넘겨주어야 하는지 여부, default 값 을 설정할 수 있다. 잘 사용되지는 않는다.

커맨드 객체를 이용한 HTTP 정보 얻어오기

// public String memJoin(Model model, HttpServletRequest request) {
public String memJoin(Member member) {
//
//  String memId = request.getParameter("memId");
    ...
//    
//    service.memberRegister(memId, ...);
    service.memberRegister(member.getMemId(), ...);

//    model.addAttribute("memId", memId);
      ...
//
    return "memJoinOk";
    <h1> memJoinOk </h1>
//  ID : ${memId}<br />
    ID : ${member.memId}<br />
    ...

MemberAttribute 사용하기

  1. 기존의 커맨드 객체를 이용하는 방식에서 커맨드 개체의 변수명을 사용하는 것이 아니라 닉네임을 사용할 수 있도록 한다.
    public String memJoin(Member member)
    -> ${member.memId}

    public String memJoin(@ModelAttribute("mem") Member member)
    -> ${mem.memId}

  2. 함수 위에 @ModelAttribute("serverTime")을 지정하면, 다른 함수가 호출되어도 항상 @ModelAttribute를 지정한 함수도 같이 호출된다.

    @ModelAttribute("serverTime")
    public String getServerTime(Locale locale) {
     ...
    }
    //   
    @RequestMapping(value="/memJoin", method=RequestMethod.POST) {
     ...
    }

    Model & ModelAndView

    : Model을 뷰에 데이터만을 전달하기 위한 객체이고,
    ModelAndView는 데이터와 뷰ㅜ의 이름을 함께 전달하는 객체이다.

    ////////// Model
    public String memModify(Model model, Member member) {
     ...
     model.addAttribute("memBef", value);
     return "memModifyOk";
    }
    //
    ///////// ModelAndView
    public ModelAndView memModify(Member member) {
     ...
     ModelAndView mav = new ModelAndView();
     mav.addObject("memBef", value);
     mav.setViewName("memModifyOk");
    //    
     return mav;
    }

학습하며 정리한 내용을 올리는 것이니, 참고용으로만 봐 주시길 바랍니다.
자바 스프링 프레임워크(renew ver.) - 신입 프로그래머를 위한 강좌

스프링컨테이너 생명주기

  1. 생성: 스프링컨테이너와 빈 객체들이 생성되고 주입(의존관계)된다.
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext("classpath:appCtx.xml");
  1. 사용
    : `BookRegisterService bookRegisterService = ctx.getBean("bookRegisterService", BookRegisterService.class);

  2. 소멸
    : ctx.close();

Bean 객체 생성/제거 시 특정작업을 수행하도록 하기

: 해당 객체가 인증절차(DB연결, 어떤 작업으로 인한인증작업) 시 주로 사용된다.

  1. 인터페이스 이용하기
public class BookRegisterService implements InitializingBean, DisposableBean {

    @Autowired 
    private BookDao bookDao;

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.prinltn("Bean 객체 생성 시");
    }

    @Override
    public void destory() throws Exception {
        System.out.println("Bean 객체 소멸 시");
    }
}
  • InitializingBean, DisposableBean 인터페이스를 상속받는다.
  • 상속받은 인터페이스의 afterPropertiesSet, destory 함수를 구현한다.
  1. 스프링 속성 이용하기
<bean id="memberRegisterService" class="com.member.dao.MemberRegisterService" init-method="initMethod" destroy-method="destroyMethod" />
...
public class MemberRegisterService {
    ...
    public void initMethod() {

    }

    public void destroyMethod() {

    }
}
  • bean 태그에 init-method, destroy-method 속성과 함수 이름을 넣어준다.
  • 해당 Class에 init, destroy 함수를 구현해 준다.

XML 파일이 아닌 Java를 이용해 스프링 설정하기

  • applicationContext.xml -> MemberConfig.java

  • 이 자바 클래스 파일(MemberConfig.java)이 스프링 설정파일로써 스프링 컨테이너를 만드는 데 사용될 것임을 명시 -> @Configuration

  • 이 메소드는 Bean객체를 만드는 데 사용될 것임을 명시 -> @Bean

  • 자바 클래스 파일 명세

@configuration
public class MemberConfig {
    /* 기본 Bean 객체 생성하기 */
    // <bean id="**studentDao**" class="*ems.member.dao.StudentDao*" />

    @Bean
    public *StudentDao* **studentDao**()  {
        return new StudentDao();
    }

    /* DI가 있는 Bean 객체 생성하기 */
    /*
    <bean id="registerService" class="ems.member.service.StudentRegisterService">
        <constructor-arg ref="studentDao"></constructor-arg>
    </bean>
    */

    @Bean
    public StudentRegisterService registerService() {
        return new StudentRegisterService(studentDao());
    }

    /* setter가 있는 Bean 객체 생성하기 */
    /*
    <bean id="dataBaseConnectionInfoDev" class="...DataBaseConnectionInfo">
        <property name="jdbcUrl" value="jdbc:oracle:...">
    </bean>
    */

    @Bean
    public DataBaseConnectionInfo dataBaseConnectionInfoDev() {
        DataBaseConnectionInfo infoDev = new DataBaseConnectionInfo();
        infoDev.setJdbcUrl("jdbc:oracle:...");

        return infoDev;
    }

    /* setter에 다른 Bean객체를 넣는? Bean 객체 생성하기 */
    /*
    <bean id="...">
        <property name="dbInfos">
            <map>
                <entry>
                    <key>
                        <value>name</value>
                    </key>
                    <value>Jay</value>
                </entry>
                <entry>
                    <key>
                        <value>dev</value>
                    </key>
                    <ref bean="dataBaseConnectionInfoDev" />
                </entry>
            </map>
        </property>
    */

    @Bean
    ...
    dbInfos.put("name", "Jay");
    dbInfos.put("dev", dataBaseConnectionInfoDev());
    info.setDbInfos(dbInfos);
}
  • 스프링 컨테이너 생성하기
/*
GenericXmlApplicationContext ctx =
    new GenericApplicationContext("classpath:applicationContext.xml");
*/

AnnotationConfigApplicationContext ctx = 
    new AnnotationConfigApplicationContext(MemberConfig.class);

Java 파일 분리

: DB관련 Dao, Service 객체, DB관련 기능, Utils 로 보통 분리한다.

  • 분리된 자바 파일은 다음과 같이 comma를 이용해 여러개를 불러올 수 있다.
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MemberConfig1.class, MemberConfig2.class, MemberConfig3.class);
  • 또는, MemberConfig1.java 파일 내 @Import({MemberConfig2.class, MemberConfig3.class}) 를 기입해주고 MemberConfig1.class만 불러오면 된다.

'Backend > Spring' 카테고리의 다른 글

Spring - 데이터베이스  (0) 2021.02.14
Spring - 세션과 쿠키 && 리다이렉트와 인터셉트  (0) 2021.02.09
Spring - MVC  (0) 2021.02.04
Spring - Bean과 의존객체 주입  (0) 2021.02.01
Spring - Dependency Injection  (0) 2021.01.31

학습하며 정리한 내용을 올리는 것이니, 참고용으로만 봐 주시길 바랍니다.
자바 스프링 프레임워크(renew ver.) - 신입 프로그래머를 위한 강좌

스프링 설정파일(/src/main/resources/applicationContext.xml) 분리

  1. .

     String[] appCtxs = {"classpath:appCtx1.xml", "classpath:appCtx2.xml", "classpath:appCtx3.xml"};
     GenericXmlApplicationContext ctx = new GenericXmlApplicationContext(appCtxs);
  2. appCtxImport.xml 중

    <import resource="classpath:appCtx2.xml"/>
    <import resource="classpath:appCtx3.xml"/>
    GenericXmlApplicationContext ctx = 
    new GenericXmlApplicationContext("classpath:appCtxImport.xml");

Bean의 범위

: Bean은 기본적으로 Singleton의 개념이다.
: getBean("A") 를 하면 Spring Container에 생성되어있는 A 객체를 참조하는 값을 가지고 올 뿐 객체를 새롭게 생성하는 것은 아니다.
: Singleton 과 반대대는 Prototype의 개념도 있는데, 이 방식을 적용하기 위해서는
<bean id="~" class="" scope="prototype"> </bean>을 해 주어야한다. (흔치 않음)

의존객체 자동주입

: 기존에는 <constructor-arg>, <property> 태그를 이용해 의존객체를 명시하였다.
: @Autowired, @Resource 어노테이션을 이용해 스프링 컨테이너가 자동으로 필요한 의존대상 객체를 찾아 의존 대상 객체가 필요한 객체에 주입해준다.
: 스프링 컨테이너 설정파일에 <context:annotation-config />를 기입해 주어야 한다.

  1. @Autowired

    • 주입하려고 하는 객체의 타입이 일치하는 객체를 자동으로 주입
    • 생성자, property(member), method에 쓸 수 있음
    • @Autowired 구문을 보면, 해당 구문에 필요한 객체의 데이터 타입을 갖고있는 빈 객체를 찾아 넣어준다.
      • @Autowired public void wordConstructor(WordDao wordDao) {} > **WordDao** wordDao
      • <bean id = "wordDao" class="com.word.dao.WordDao" /> > class="com.word.dao.WordDao"
  2. @Resource

    • 주입하려고 하는 객체의 이름이 일치하는 객체를 자동으로 주입
    • property(member), method에만 쓸 수 있음
    • 이름: private WordDao wordDao; 에서 변수명(wordDao)
    • @Autowired public void wordConstructor(WordDao wordDao) {} > WordDao **wordDao**
    • <bean id = "wordDao" class="com.word.dao.WordDao" /> > id = "wordDao"

의존객체 선택

: 동일한 객체가 2개 이상인 경우 스프링 컨테이너는 자동 주입 대상 객체를 판단하지 못해 Exception을 발생시킴

<bean id="wordDao" class="com.word.dao.WordDao">
   <qualifier value="useDao" />
</bean>
<bean id="wordDao2" class="com.word.dao.WordDao" />
<bean id="wordDao3" class="com.word.dao.WordDao" />

...

@Autowired
@Qualifier("useDao")
private WordDao wordDao;

: 위 처럼 @Qualifier 어노테이션을 사용하면 해결이 된다.

  • bean 객체에 태그에 value를 할당해 주면, @Qualifier를 만났을 때 해당 value에 매칭되는 bean 객체를 넣어준다.
    : 단, bean 객체의 id와 객체의 이름(변수명)이 일치할 경우 @Qualifier를 쓰지 않아도 되긴한다. 하지만 혼동을 줄 수 있으니 @Qualifier를 쓰도록하자.

의존객체 자동주입 체크

@Autowired(required = false)
private WordDao wordDao;

: 의존객체가 존재하면 주입하고, 없으면 주입하지마!
: required = false를 이용하면, 스프링컨테이너에서 bean 객체를 만들지 않았을 때 Exception이 발생하는 상황을 제거해 줄 수 있음
: 선호되는 방식은 아님

@Inject

: @Autowired와 동일한 기능을 제공하는 것으로, 의존 객체를 자동으로 주입할 수 있다.
: 차이점은, required 속성을 제공하지 않는다.
: 차이점은, @Qualifier 대신 @Named를 쓰며, 스프링컨테이너에서 qualifier 태그를 쓸 필요 없다.
: Autowired가 일반적으로 더 많이 쓰인다.

<bean id="wordDao1" class="com.word.dao.WordDao" />
<bean id="wordDao2" class="com.word.dao.WordDao" />
<bean id="wordDao3" class="com.word.dao.WordDao" />

@Inject
@Named(value="wordDao1")
private WordDao wordDao;

'Backend > Spring' 카테고리의 다른 글

Spring - 데이터베이스  (0) 2021.02.14
Spring - 세션과 쿠키 && 리다이렉트와 인터셉트  (0) 2021.02.09
Spring - MVC  (0) 2021.02.04
Spring - 스프링 컨테이너의 생성관련  (0) 2021.02.02
Spring - Dependency Injection  (0) 2021.01.31

학습하며 정리한 내용을 올리는 것이니, 참고용으로만 봐 주시길 바랍니다.
자바 스프링 프레임워크(renew ver.) - 신입 프로그래머를 위한 강좌

Dependency Injection

  1. 생성자를 이용한 DI
<bean id="studentDao" class="ems.member.dao.StudentDao" ></bean>


    <bean id="registerService" class="ems.member.service.StudentRegisterService">
        **<constructor-arg ref="studentDao" ></constructor-arg>**
    </bean>

: src/main/resources/applicationContext.xml 파일에 <constructor-arg ref="studentDao" >를 이용해 의존성 주입

  1. setter를 이용한 DI
public class DataBaseConnectionInfo {

    private String jdbcUrl;
    private String userId;
    private String userPw;

    public String getJdbcUrl() {
        return jdbcUrl;
    }

    ...
<bean id="dataBaseConnectionInfoDev" class="ems.member.DataBaseConnectionInfo">
        <property name="jdbcUrl" value="jdbc:oracle:thin:@localhost:1521:xe" />
        <property name="userId" value="scott" />
        <property name="userPw" value="tiger" />
    </bean>

: <property name="jdbcUrl" value= ... /> 를 이용해 주입

  1. List 타입 DI 주입
public void setDevelopers(List<String> developers) {
        this.developers = developers;
}
<bean id="informationService" class="ems.member.service.EMSInformationService">
    <property name="developers">
            <list>
                <value>Cheney.</value>
                <value>Eloy.</value>
                <value>Jasper.</value>
                <value>Dillon.</value>
                <value>Kian.</value>
            </list>
        </property>
  1. Map 타입 DI

    <property name="administrators">
            <map>
                <entry>
                    <key>
                        <value>Cheney</value>
                    </key>
                    <value>cheney@springPjt.org</value>
                </entry>
                <entry>
                    <key>
                        <value>Jasper</value>
                    </key>
                    <value>jasper@springPjt.org</value>
                </entry>
            </map>
        </property>

    : 아래와 같이 value 위치에 bean 객체를 넣을 수 있음

    <entry>    
    <key>
        <value>dev</value>
    </key>
    <ref bean="dataBaseConnectionInfoDev"/>
    </entry>

Tensorflow 2.0의 변화

  • tf.compat.v1을 통해 2.0 버전에서도 1.x 버전의 API를 사용할 수 있다.
  • tf_upgrade_v2로 1.x 버전의 파이썬 코드를 2.0 버전으로 자동 변환할 수 있다. 하지만 일부 제대로 변환되지 않아 사용자가 직접 제거 또는 수정해야하는 경우도 존재하니 유의하기 바란다. (tf.nn.conv2d()use_cudnn_on_gpu 인자 등)
  • 일관성 없는 1.x버전의 API들을 개선하기 위하여, 최적화 함수는 tf.optimizer에, loss 함수는 tf.losses에 모두 모았다.
  • 기존에는 그래프를 정의한 뒤 생성된 그래프를 이용하여 계산을 수행하였으나, 2.0버전부터는 그래프를 생성하지 않고도 계산을 할 수 있다. 이를 eager execution(즉시 실행 모드)이라 한다. 1.x버전에서 이를 수행하기 위해서는 tf.enable_eager_execution()을 통해 활성화 해야한다. 이러한 즉시 실행모드는 편리하지만 그래프에 비해 연산 속도가 비교적 느림에 주의하자.
  • @tf.function을 이용해 일반 연산을 그래프 연산으로 변환할 수 있다. 이는 1.x 버전의 tf.Session과 동일하다.
  • 1.4 버전부터 지원된 텐서플로 API인 keras가 2.0 버전부터는 텐서플로의 유일한 고수준 API로써 제공된다. 이를 통해 간결한 프로그래밍을 가능케한다.

텐서플로 2.0 설치 (Ubuntu)

Python3 설치

: 다음 명령어를 통해 Python3와 관련된 툴들을 설치한다.

apt-get update
apt-get install python3 python3-dev python3-pip python3-venv
apt-get install build-essential

...

jay@:~/tutorial $ python3 --version
Python 3.8.5
jay@:~/tutorial $ pip3 --version
pip 20.0.2 from /usr/lib/python3/dist-packages/pip (python 3.8)

: 다음 명령으로 가상환경을 만들고 활성화 시킨다.

python3 -m venv --system-site-packages ~/venv # 새로운 가상환경을 만든다.(경로는 임의 설정)

source ~/venv/bin/activate # 가상 환경을 활성화 한다 (sh, bash or zsh의 경우)
. ~/venv/bin/activate.fish # 가상 환경을 활성화 한다 (fish의 경우)
source ~/venv/bin/activate.csh # 가상 환경을 활성화 한다 (csh or tcsh의 경우)

# 가상환경이 활성화 되었다. 프롬프트에는 (venv) 가 보일 것이다.
# 가상환경을 종료하고 싶다면 deactivate 를 수행한다.

Tensorflow 및 CUDA 설치

: 다음 명령으로 가상 환경 내에 tesnorflow를 설치 및 정상 설치여부를 확인한다.

pip3 install --upgrade tensorflow

python3 -c "import tensorflow as tf;print(tf.reduce_sum(tf.random.normal([1000, 1000])))"

:다음과 같은 실행 결과가 보일 것이다. 아래의 Warning들은 적절한 cuda version이 없다는 내용이므로 무시해도 좋다. 하지만 GPU를 이용한 가속을 원한다면 https://medium.com/@stephengregory_69986/installing-cuda-10-1-on-ubuntu-20-04-e562a5e724a0 를 참고해 CUDA를 설치하길 바란다.
(사용중인 GPU의 갯수는 다음 명령어 python3 -c "import tensorflow as tf;print(len(tf.config.experimental.list_physical_devices('GPU')))" 로 확인할 수 있다.)

(venv) jay@:~/tutorial $ python3 -c "import tensorflow as tf;print(tf.reduce_sum(tf.random.normal([1000, 1000])))"
2020-12-06 16:45:18.518048: W tensorflow/stream_executor/platform/default/dso_loader.cc:59] Could not load dynamic library 'libcudart.so.10.1'; dlerror: libcudart.so.10.1: cannot open shared object file: No such file or directory
2020-12-06 16:45:18.518152: I tensorflow/stream_executor/cuda/cudart_stub.cc:29] Ignore above cudart dlerror if you do not have a GPU set up on your machine.
2020-12-06 16:45:19.110871: W tensorflow/stream_executor/platform/default/dso_loader.cc:59] Could not load dynamic library 'libcuda.so.1'; dlerror: libcuda.so.1: cannot open shared object file: No such file or directory
2020-12-06 16:45:19.110971: W tensorflow/stream_executor/cuda/cuda_driver.cc:312] failed call to cuInit: UNKNOWN ERROR (303)
2020-12-06 16:45:19.111008: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:156] kernel driver does not appear to be running on this host (jay-VirtualBox): /proc/driver/nvidia/version does not exist
2020-12-06 16:45:19.111228: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN)to use the following CPU instructions in performance-critical operations:  AVX2
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2020-12-06 16:45:19.116012: I tensorflow/core/platform/profile_utils/cpu_utils.cc:104] CPU Frequency: 3593250000 Hz
2020-12-06 16:45:19.116114: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x54fbc80 initialized for platform Host (this does not guarantee that XLA will be used). Devices:
2020-12-06 16:45:19.116150: I tensorflow/compiler/xla/service/service.cc:176]   StreamExecutor device (0): Host, Default Version
tf.Tensor(-23.87439, shape=(), dtype=float32)

필자는 Virtual Box에서 tutorial 목적으로 사용중인데, Virtual Box에서는 GPU 사용이 안되는 듯 하다...ㅠ

참고링크

Tensorflow 설치 공식 가이드

GitHub CLI 설치 (Ubuntu)

: 다음의 절차를 차례로 수행한다. 참고링크

sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-key C99B11DEB97541F0
sudo apt-add-repository https://cli.github.com/packages
sudo apt update
sudo apt install gh

GitHub CLI 시작하기

GitHub 계정 연결

: gh auth login 을 입력하면 로그인할 수 있도록 Interactive CLI가 나타난다.
- Authentication token의 경우 [GitHub] - [Settings] - [Developer settings] - [Personal access token]에서 만들 수 있다.
- gh auth status 명령으로 로그인이 잘 되었는지 확인할 수 있다.



: 또는 위의 Personal access token을 이용하여 gh auth login --with-token < mytoken.txt 명령어를 통해 손쉽게 로그인 할 수 있다. Enterprise 계정의 경우 --hostname blah.blah.enterprise.GitHub.com 옵션을 사용하면 된다. 다시말해 다음의 gh auth login --hostname enterprise.internal --with-token < mytoken.txt 명령어를 사용하면 된다.

: 연결을 해제하고 싶다면 gh auth logout 명령어를 수행하면 된다.

PR 생성 및 comment 추가

: 현재 작업중인 branch에서 commit 생성 및 git push ~ 작업을 수행하였을 때, GitHub Web에서 PR을 생성할 수 있다. 하지만 GitHub CLI를 이용해 Web에서 하던 작업을 대신 해 보자.

  • gh pr create --title "Test for GitHub CLI" --base main 명령으로 PR을 생성할 수 있다.
  • gh pr list 명령을 통해 생성된 PR들의 리스트를 확인할 수 있다.
  • gh pr review --comment -b "Comment What you need" 명령으로 현재 branch에 대해 리뷰 코멘트를 남길 수 있다.
  • gh pr review 7 --comment -b "Hmm.. This is GitHub CLI" 명령으로 특정 PR Number에 comment를 남길 수 있다.




참고자료
GitHub CLI

Pipeline이란?

: Jenkins Pipeline은 CD(Continuous Delevery)를 지원하는 플러그인의 집합이다. 이 과정에서 소프트웨어를 빌드하고 여러 단계의 테스트, 배포를 진행한다. Pipeline은 Pipelie Domina Specific Language라는 문법을 통해 마치 코드를 작성하는 것과 같이 해당 기능을 제공한다. Pipeline을 사용하면 다음과 같은 이점이 있다.

  • 모든 Pull Request에 대해 Pipeline build process를 자동으로 제공한다.
  • Pipeline 선언을 코드로써 관리하여 프로젝트의 여러 구성원이 함께 보고 이해하며 편집할 수 있다.

이러한 Pipeline을 작성하는 데에는 크게 두 가지, Declarative 방식, Scripted 방식이 있으며, Declarative 방식은 Scripted방식보다 풍부한 구문 기능을 제공하며, 간편하게 작성할 수 있으며, 이해하기 쉬운 구조로 구성되어 있다.

Declarative Pipeline 기본 구조

기본 구조는 아래와 같다.

// Jenkinsfile

pipeline { // 최 상단 element로 정의되어 있어야 한다.
    agent any // pipeline 블록 내 최 상단에 정의되어야 하며, 말 그대로 실행할 Agent가 할당된다. 여기서 'any'는 사용가능한 어떠한 agent로도 실행해도 된다는 걸 나타낸다. 이는 pipeline 아래 위치해도 되고 각 stage 블록에 위치해도 된다.
    options {
        skipStagesAfterUnstable()
    }
    stages {
        stage('Build') { // stage는 Pipeline의 단계를 나타낸다.
            steps { // 해당 stage에서 실행할 명령을 기술하는 부분이다.
                sh 'make' // 주어진 shell 명령을 수행한다.
            }
        }
        stage('Test'){
            steps {
                sh 'make check'
                junit 'reports/**/*.xml' // junit 플러그인을 통해 제공하는 기능으로, 테스트 보고서를 집계한다.
            }
        }
        stage('Deploy') {
            steps {
                sh 'make publish'
            }
        }
    }
}

여기서 agent에 들어올 수 있는 parameter에 대해 자세히 알아보자.

agent type 설명
any 사용가능한 어떠한 agent를 써도 좋다 agent any
none pipeline 블록에 선언된 경우, 전체 파이프라인을 실행하는 동안 어떠한 global agent도 할당되지 않는다.
이 때, 각 stage 블록에 agent가 선언되어야 한다.
agent none
label 제공된 label을 이용해 파이프라인을 실행한다. agent { label 'my-defined-label' }
agent { label 'my-label1 && my-label2' }
agent { label 'my-label1 || my-label2' }
node agent { node { label 'labelName' } }agent { label 'labelName' }와 동일한 의미를 가진다.
하지만 customWorkspace와 같은 추가적인 옵션을 가질 수 있다.
agent { node { label 'labelName' } }
docker 도커 기반 파이프라인을 제공한다. docker는 아래에서 자세히 다루어 보자. -

docker

: 제공된 컨테이너를 이용해 도커 기반의 파이프라인 실행을 제공한다. 이는 args 파라미터를 이용하여 docker run 호출 시 제공할 파라미터를 줄 수 있다. 또한 alwaysPull 옵션은 이미지가 이미 존재하더라도 docker pull을 강제로 수행하도록 한다.

  • agent { docker 'maven:3-alpine' }
agent {
    docker {
        image 'maven:3-alpine'
        label 'my-defined-label'
        args  '-v /tmp:/tmp'
    }
}

: docker는 추가적으로 registryUrl, registryCredentialsId 옵션을 통해 Docker Registry와 이에 필요한 Credential을 명시할 수 있도록 지원한다. registryCredentialsId 는 도커허브의 private repository를 위한 기능이다.

agent {
    docker {
        image 'myregistry.com/node'
        label 'my-defined-label'
        registryUrl '<https://myregistry.com/>'
        registryCredentialsId 'myPredefinedCredentialsInJenkins'
    }
}

dockerfile

: source repository(GitHub?)에 존재하는 Dockerfile을 이용해 pipeline을 실행한다. 이를 위해서는 JenkinsfileMultibranch pipeline 또는 Pipeline from SCM을 이용해 로딩되어야 한다. Dockerfile의 최 상단에는 agent { dockerfile true }이 정의되어 있어야 한다. dir 옵션을 통해 Dockerfile의 경로를 지정할 수 있다. filename을 통해 Dockerfile의 이름(Dockerfile이 아닌 경우)을 지정할 수 있다. additionalBuildArgs 옵션을 통해 docker build 명령에 들어가는 추가적인 인자를 지정할 수 있다.

agent {
    // Equivalent to "docker build -f Dockerfile.build --build-arg version=1.0.2 ./build/
    dockerfile {
        filename 'Dockerfile.build'
        dir 'build'
        label 'my-defined-label'
        additionalBuildArgs  '--build-arg version=1.0.2'
        args '-v /tmp:/tmp'
    }
}

dockerfile 또한 docker와 마찬가지로 registryUrl, registryCredentialsId 옵션을 사용할 수 있다.

agent {
    dockerfile {
        filename 'Dockerfile.build'
        dir 'build'
        label 'my-defined-label'
        registryUrl '<https://myregistry.com/>'
        registryCredentialsId 'myPredefinedCredentialsInJenkins'
    }
}

Jenkinsfile 사용하기

: Jenkinsfile을 편집할 때 groovy 문법을 지원하는 에디터를 사용하면 편집에 편리하다. 그냥 파일명에 Jenkinsfile.groovy를 사용해도 무방하다. Jenkinsfile은 Make, CMake, Gradle, Maven과 같은 빌드도구가 아닌 빌드 및 테스팅을 간편하게 도와주는 중간 layer라고 이해하면 된다. 아래에서 sh 명령어는 Unix/Linux 기반 시스템에서, bat 명령어는 Window기반 시스템에서 사용하면 명령어 호출 예약어이다.
: 다음은 간단한 Jenkinsfile 의 예제이다.

// Declarative Jenkinsfile

pipeline {
    agent any

    stages {
        stage('Build') {
            steps {
                sh 'make' 
                archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true
            }
        }
        stage('Test') {
            steps {
                /* `make check` returns non-zero on test failures,
                * using `true` to allow the Pipeline to continue nonetheless
                */
                sh 'make check || true' 
                junit '**/target/*.xml' 
            }
        }
        stage('Deploy') {
            when {
              expression {
                currentBuild.result == null || currentBuild.result == 'SUCCESS' 
              }
            }
            steps {
                sh 'make publish'
            }
        }
    }
}
  • archiveArtifacts는 이후에 기술된 */target/*.jar과 패턴 매칭되는 파일들을 찾아 이들을 나중에 찾아볼 수 있도록 Jenkins controller 에 저장한다.
  • currentBuild.result 변수는 파이프 라인이 테스트 실패했는지 성공했는지를 알 수 있다. fail 시의 값은 UNSTABLE이다.

Jenkins 환경 변수

: TBD
https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#using-environment-variables

참고 링크
Jenkins pipeline
Jenkinsfile
Jenkins pipeline syntax

+ Recent posts