반응형

# 스프링 빈과 의존관계

## 컴포넌트 스캔과 자동 의존관계 설정

  • @Autowired : 생성자에 @Autowired 가 있으면 스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어준다. (DI (Dependency Injection = 의존관계를 외부에서 넣어주는 것 = 의존성 주입)).
  • 기존 소스코드 오류 제거를 위해 어노테이션 추가. (@Controller / @Service / @Repository / @Autowired)
  • 스프링 빈을 등록하는 2가지 방법.
- 컴포넌트 스캔과 자동 의존관계 설정
(컴포넌트 스캔 : @Controller / @Service / @Repository / @Autowired)

- 자바 코드로 직접 스프링 빈 등록하기
  • 컴포넌트 스캔 원리
- @Component 애노테이션이 있으면 스프링 빈으로 자동 등록 됨. 

- @Controller 컨트롤러가 스프링 빈으로 자동 등록된 이유도 컴포넌트 스캔 때문.
  • @Component 를 포함하는 다음 애노테이션도 스프링 빈으로 자동 등록 됨.
- @Controller 

- @Service

- @Repository
  • 생성자에 @Autowired 사용 시 객체 생성 시점에 스프링 컨테이너에서 해당 스프링 빈을 찾아서 주입. (생성자가 1개만 있는 경우 @Autowired 생략 가능.)
  • 스프링은 스프링 컨테이너에 스프링 빈을 등록할 때 기본으로 싱글톤(유일하게 하나만 등록해서 공유)으로 등록 함. 따라서 같은 스프링 빈이면 모두 같은 인스턴스이다. 설정으로 싱글톤이 아니게 설정할 수 있지만, 특별한 경우를 제외하면 대부분 싱글톤을 사용.

## 자바 코드로 직접 스프링 빈 등록하기.

  • 기존 회원 서비스와 회원 리포지토리의 @Service, @Repository, @Autowired 애노테이션을 제거하고 SpringConfig 생성하여 진행.
package hello.hellospring;

import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfig {
    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}
  • DI 방법 : 의존관계가 실행 중 동적으로 변하는 일은 없으므로 생성자 주입 방법 권장.
1. 생성자 주입 (권장)
@Autowired
public MemberController(MemberService memberService) {
	this.memberService = memberService;
}


2. 필드 주입
@Autowired private MemberService memberService;


3. setter 주입
@Autowired
public void setMemberService(MemberService memberService) {
	this.memberService = memberService;
}
  • 실무에서는 주로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용.
  • 정형화 되지 않거나, 상황에 따라 구현 클래스를 변경해야 하면 설정을 통해 스프링 빈으로 등록.
  • @Autowired 를 통한 DI는 helloConroller , memberService 등과 같이 스프링이 관리하는 객체에서만 동작. 스프링 빈으로 등록하지 않고, 직접 생성한 객체에서는 동작하지 않는다.

 

반응형
반응형

# 회원 관리 서비스 백엔드 개발

## 비즈니스 요구사항 정리

  • 데이터 : 회원 ID, 이름
  • 기능 : 회원 등록, 조회
  • 아직 데이터 저장소가 선정되지 않음 (가상의 시나리오)
  • 컨트롤러 : 웹 MVC의 컨트롤러 역할
  • 서비스 : 핵심 비즈니스 로직 구현
  • 리포지토리 : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리.
  • 도메인 : 비즈니스 도메인 객체 (예) 회원, 주문, 쿠폰 등 주로 데이터베이스에 저장하고 관리 됨)
  • 클래스 의존관계
MemberService -> MemberRepository (interface 로 생성, 가상 시나리오 상 아직 데이터 저장소가 선정되지 않았으므로)

MemberRepository 에 대한 구현체를 MemoryMemberRepository 로 생성.


- 아직 데이터베이스 저장소가 선정되지 않았으므로, 우선 인터페이스로 구현 클래스를 변경할 수 있도록 설계.
- 데이터 저장소는 RDB, NoSQL 등 다양한 저장소 고민중인 상황으로 가정.
- 개발을 진행하기 위해 초기 개발 단계는 구현체로 가벼운 메모리 기반의 데이터 저장소 사용.

## 회원도메인과 리포지토리 생성

  • 회원 객체.
package hello.hellospring.domain;

public class Member {
    private Long id;
    private String name;

    public String getName() {
        return name;
    }

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

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}
  • 회원 리포지토리 인터페이스
package hello.hellospring.repository;

import hello.hellospring.domain.Member;

import java.util.List;
import java.util.Optional;

public interface MemberRepository {
    Member save(Member member);
    Optional<Member> findById(Long id);
    Optional<Member> findByName(String name);
    List<Member> findAll();
}
  • 회원 리포지토리 메모리 구현체
package hello.hellospring.repository;

import hello.hellospring.domain.Member;

import java.util.*;

public class MemoryMemberRepository implements MemberRepository {

    private static Map<Long, Member> store = new HashMap<>();
    private static long sequence = 0L;

    @Override
    public Member save(Member member) {
        member.setId(++sequence);
        store.put(member.getId(), member);
        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        return Optional.ofNullable(store.get(id));
    }

    @Override
    public Optional<Member> findByName(String name) {
        return store.values().stream()
                .filter(member -> member.getName().equals(name))
                .findAny();
    }

    @Override
    public List<Member> findAll() {
        return new ArrayList<>(store.values());
    }
}

## 회원 리포지토리 검증을 위한 테스트 케이스 작성.

  • 테스트 시 main 메서드를 통해 실행하거나, 웹 애플리케이션 컨트롤러를 통해서 해당 기능을 실행하는 방법이 있는데, 해당 방법들은 준비 및 실행하는데 오래걸리고, 반복 실행하기 어렵고, 여러 테스트를 한 번에 실행하기 어려운 단점이 있다.
  • 위의 문제들을 해결하기 위해 자바에서는 JUnit 이라는 프레임워크로 테스트를 실행하여 해결 함.
  • test 쪽에 main에 생성한 패키지와 동일한 패키지 생성.
  • 그리고 테스트하고자하는 클래스의 동일한 클래스명 + test 붙여서 생성.
  • 아래는 Assertions.assertEquals() 사용한 예.
package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class MemoryMemberRepositoryTest {
    MemoryMemberRepository repository = new MemoryMemberRepository();

    @Test
    public void save() {
        Member member = new Member();
        member.setName("Spring");

        repository.save(member);

        Member result = repository.findById(member.getId()).get();
        Assertions.assertEquals(member, result);
    }
}
  • 아래는 Assertions.assertThat() 사용한 예.
package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Optional;

import static org.assertj.core.api.Assertions.*;

class MemoryMemberRepositoryTest {
    MemoryMemberRepository repository = new MemoryMemberRepository();

    @Test
    public void save() {
        Member member = new Member();
        member.setName("Spring");

        repository.save(member);

        Member result = repository.findById(member.getId()).get();
        //Assertions.assertEquals(member, result);
        //Assertions.assertThat(member).isEqualTo(result);
        assertThat(member).isEqualTo(result);
    }

    @Test
    public void findByName() {
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        Member member2 = new Member();  // 변수 클릭 후 shift + f6 입력 시 rename.
        member2.setName("spring2");
        repository.save(member2);

        Member result = repository.findByName("spring1").get();

        assertThat(result).isEqualTo(member1);
    }

    @Test
    public void findAll() {
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);

        List<Member> result = repository.findAll();

        assertThat(result.size()).isEqualTo(2);
    }
}
  • ctrl + enter 로 static import 시 아래와 같이 더욱 간편하게 사용가능.
// 기본
Assertions.assertThat(member).isEqualTo(result);


// static import 시.
assertThat(member).isEqualTo(result);
  • 테스트 코드 작성 후 실행하여, 실행창 쪽 보고 성공/실패 여부 파악.

  • 위에서 작성한 코드를 이용하여 클래스 테스트 실행 시 오류 발생, 이를 해결하기 위해 아래와 같이 테스트 끝날때 마다 repository를 지워주는 코드를 추가해주면 됨. (@AfterEach 사용 -> 콜백메서드와 같음)
package hello.hellospring.repository;

import hello.hellospring.domain.Member;

import java.util.*;

public class MemoryMemberRepository implements MemberRepository {

    private static Map<Long, Member> store = new HashMap<>();
    private static long sequence = 0L;

    @Override
    public Member save(Member member) {
        member.setId(++sequence);
        store.put(member.getId(), member);
        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        return Optional.ofNullable(store.get(id));
    }

    @Override
    public Optional<Member> findByName(String name) {
        return store.values().stream()
                .filter(member -> member.getName().equals(name))
                .findAny();
    }

    @Override
    public List<Member> findAll() {
        return new ArrayList<>(store.values());
    }

	// AfterEach 에서 사용할 클리어 추가.
    public void clearStore() {
        store.clear();
    }
}
package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Optional;

import static org.assertj.core.api.Assertions.*;

class MemoryMemberRepositoryTest {
    MemoryMemberRepository repository = new MemoryMemberRepository();

    @AfterEach
    public void afterEach() {
        repository.clearStore();
    }

    @Test
    public void save() {
        Member member = new Member();
        member.setName("Spring");

        repository.save(member);

        Member result = repository.findById(member.getId()).get();
        //Assertions.assertEquals(member, result);
        //Assertions.assertThat(member).isEqualTo(result);
        assertThat(member).isEqualTo(result);
    }

    @Test
    public void findByName() {
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        Member member2 = new Member();  // 변수 클릭 후 shift + f6 입력 시 rename.
        member2.setName("spring2");
        repository.save(member2);

        Member result = repository.findByName("spring1").get();

        assertThat(result).isEqualTo(member1);
    }

    @Test
    public void findAll() {
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);

        List<Member> result = repository.findAll();

        assertThat(result.size()).isEqualTo(2);
    }
}

## 회원 서비스 개발

package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;

import java.util.List;
import java.util.Optional;

public class MemberService {

    private final MemberRepository memberRepository = new MemoryMemberRepository();

    /*
    *   회원가입
    * */
    public Long Join(Member member) {
        // 동일 이름 중복회원 방지
        //Optional<Member> result = memberRepository.findByName(member.getName());
        validateDuplicateMember(member);    // 중복회원 검증
        memberRepository.save(member);
        return member.getId();
    }

    private void validateDuplicateMember(Member member) {
        memberRepository.findByName(member.getName())
                .ifPresent(m -> {
                    throw new IllegalStateException("이미 존재하는 회원입니다.");
                });
    }

    /*
    *   전체 회원 조회
    * */
    public List<Member> findMembers() {
        return memberRepository.findAll();
    }

    public Optional<Member> findOne(Long memberId) {
        return memberRepository.findById(memberId);
    }
}

## 회원 서비스 테스트 케이스 작성.

기존에는 test 에 직접 클래스 생성하여 진행 했는데, 단축키를 이용하면 손 쉽게 test 관련 로직 생성 가능.

윈도우 기준 Ctrl + Shift + T 입력하여 Create New Test

  • 해당하는 클래스에서 Ctrl + Shift + T 클릭하여 Create New Test 생성.
  • 테스트 코드의 경우 한글로 작성해서 무방하다.
package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;

import java.util.List;
import java.util.Optional;

public class MemberService {

    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    /*
    *   회원가입
    * */
    public Long join(Member member) {
        // 동일 이름 중복회원 방지
        //Optional<Member> result = memberRepository.findByName(member.getName());
        validateDuplicateMember(member);    // 중복회원 검증
        memberRepository.save(member);
        return member.getId();
    }

    private void validateDuplicateMember(Member member) {
        memberRepository.findByName(member.getName())
                .ifPresent(m -> {
                    throw new IllegalStateException("이미 존재하는 회원입니다.");
                });
    }

    /*
    *   전체 회원 조회
    * */
    public List<Member> findMembers() {
        return memberRepository.findAll();
    }

    public Optional<Member> findOne(Long memberId) {
        return memberRepository.findById(memberId);
    }
}
package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemoryMemberRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;
import static org.assertj.core.api.Assertions.*;

class MemberServiceTest {

    MemberService memberService;
    MemoryMemberRepository memberRepository;

    @BeforeEach
    public void beforeEach() {  // 실행 전
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
    }

    @AfterEach
    public void afterEach() {   // 실행 후
        memberRepository.clearStore();
    }

    @Test
    void 회원가입() {
        // given
        Member member = new Member();
        member.setName("hello");

        // when
        Long saveId = memberService.join(member);

        // then
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());
    }

    @Test
    public void 중복_회원_예외() {
        // given
        Member member1 = new Member();
        member1.setName("spring");

        Member member2 = new Member();
        member2.setName("spring");

        // when
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));

        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
/*

        try {
            memberService.join(member2);
            fail();
        } catch (IllegalStateException e) {
            assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원 입니다.");
        }
*/

        // then
    }

    @Test
    void findMembers() {
    }

    @Test
    void findOne() {
    }
}

 

반응형
반응형

# 스프링 웹 개발 기초

## 정적 컨텐츠

 

Spring Boot Features

Graceful shutdown is supported with all four embedded web servers (Jetty, Reactor Netty, Tomcat, and Undertow) and with both reactive and Servlet-based web applications. It occurs as part of closing the application context and is performed in the earliest

docs.spring.io

  • resources/static/hello-static.html
<!DOCTYPE HTML>
<html>
<head>
 <title>static content</title>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
정적 컨텐츠 입니다.
</body>
</html>

## MVC와 템플릿 엔진

  • 서버에서 html 을 변형하여 동적으로 하는 것으로, 이를 위해 MVC (Model View Controller) 필요.
  • Controller
package hello.hellospring.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class HelloController {

    @GetMapping("hello")
    public String hello(Model model) {
        model.addAttribute("data", "hello!!");
        return "hello";
    }

    @GetMapping("hello-mvc")
    public String helloMvc(@RequestParam("name") String name, Model model) {
        model.addAttribute("name", name);
        return "hello-template";
    }
}
  • View
<html xmlns:th="http://www.thymeleaf.org">
<body>
<p th:text="'hello ' + ${name}">hello! empty</p>
</body>
  • 확인 : http://localhost:8081/hello-mvc?name="고양이"

## API

  • JSON 데이터 구조 포멧으로 클라이언트에게 데이터 전달.
  • @Responsebody 문자 반환 : 뷰 리졸버(viewResolver)를 사용하지 않음, 대신 HTTP의 BODY에 문자 내용을 직접 반환.(HTML TAG를 말하는것 아님)
@Controller
public class HelloController {
     @GetMapping("hello-string")
     @ResponseBody	// 응답 바디에 직접 넣어주겠다는 것.
     public String helloString(@RequestParam("name") String name) {
         return "hello " + name;
     }
}
  • @Responsebody 객체 반환 : 객체를 반환하면 객체가 JSON 형식으로 변환 됨. 
package hello.hellospring.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HelloController {

    @GetMapping("hello")
    public String hello(Model model) {
        model.addAttribute("data", "hello!!");
        return "hello";
    }

    @GetMapping("hello-mvc")
    public String helloMvc(@RequestParam("name") String name, Model model) {
        model.addAttribute("name", name);
        return "hello-template";
    }

    @GetMapping("hello-string")
    @ResponseBody   // 응답 바디에 직접 넣어주겠다는 것.
    public String helloString(@RequestParam("name") String name) {
        return "hello " + name;
    }

    @GetMapping("hello-api")
    @ResponseBody
    public Hello helloApi(@RequestParam("name") String name) {
        Hello hello = new Hello();
        hello.setName(name);
        return hello;
    }

    static class Hello {
        private String name;

        public String getName() {
            return this.name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}
  • 출력결과 (JSON : key-value)
{"name":"\"호랑이\""}

  • @ResponseBody 사용 시
HTTP의 BODY에 문자 내용을 직접 반환

viewResolver 대신 HttpMessageConverter가 동작.

기본 문자처리 : StringHttpMessageConverter
기본 객체처리 : MappingJackson2HttpMessageConverter 
(Jackson : JSON 으로 바꿔는 라이브러리 중 하나, 스프링은 기본적으로 Jackson 탑재, 구글의 Gson도 존재함.)
byte 처리 등등 기타 여러 HttpMessageConverter가 기본적으로 등록되어 있다.
  • 클라이언트의 HTTP Accept 헤더와 서버의 컨트롤러 반환 타입 정보 둘을 조합해서 HttpMessageConverter가 선택됨. 
반응형
반응형

# 라이브러리

  • 기존에 스프링 부트로 생성한 프로젝트의 build.gradle 을 살펴보면 아래와 같이 3가지의 라이브러리 존재.
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
  • External Libraries를 확인해 보면 다수의 라이브러리 존재.
  • gradle, maven 의 경우 의존관계에 의해 build.gradle 에 존재하는 라이브러리와 의존관계에 있는 것들을 모두 가져와서 External Libraries에 가져오게 됨.

라이브러리 의존관계 확인.

  • 위 처럼 인텔리제이의 경우 왼쪽하단 네모에 마우스 호버 후 gradle 클릭시 우측상단에 gradle 표시. 해당 팝업에서 dependencies 에서 라이브러리 의존관계 확인 가능.

  • 실무에서는 System.out.println 으로 출력하면 않되고 log로 출력해야 함 그래야 관리가 됨. (logback, slf4j 조합하여 많이 사용)
  • 테스트 관련하여 사용하는 핵심라이브러리는 junit. (추가로 테스트 도와주는 라이브러리로 mockito, assertj 사용)

## 핵심 라이브러리 정리

  • spring-boot-starter-web
// spring-boot-starter-web에 아래 라이브러리가 들어있음.
spring-boot-starter-tomcat:톰캣(웹서버)
spring-webmvc : 스프링 웹 MVC
  • spring-boot-starter-thymeleaf : 타임리프 템플릿 엔진 (View)
  • spring-boot-starter (공통) : 스프링 부트 + 스프링 코어 + 로깅
spring-boot
	- spring-core
spring-boot-starter-logging
	- logback, slf4j

## 테스트 라이브러리 정리

  • spring-boot-starter-test
junit : 테스트 프레임워크
mockito : 목 라이브러리
assertj : 테스트 코드를 좀 더 편하게 작성하게 도와주는 라이브러리
spring-test : 스프링 통합 테스트 지원

 

# View 환경설정

## Welcome 페이지 만들기

  • resources/static/index.html 생성. (정적 페이지)
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Title</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
HELLO
<a href="/hello">hello</a>
</body>
</html>

## thymeleaf 템플릿 엔진

 

Thymeleaf

Integrations galore Eclipse, IntelliJ IDEA, Spring, Play, even the up-and-coming Model-View-Controller API for Java EE 8. Write Thymeleaf in your favourite tools, using your favourite web-development framework. Check out our Ecosystem to see more integrati

www.thymeleaf.org

 

Serving Web Content with Spring MVC

this guide is designed to get you productive as quickly as possible and using the latest Spring project releases and techniques as recommended by the Spring team

spring.io

 

Spring Boot Features

Graceful shutdown is supported with all four embedded web servers (Jetty, Reactor Netty, Tomcat, and Undertow) and with both reactive and Servlet-based web applications. It occurs as part of closing the application context and is performed in the earliest

docs.spring.io

  • 컨트롤러 : 웹 에플리케이션에서 첫 번째 진입점.

패키지 생성 &gt; 컨트롤러 생성

패키지 생성 : hello.hellospring.controller

컨트롤러 생성 : HelloController
package hello.hellospring.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HelloController {

    @GetMapping("hello")
    public String hello(Model model) {
        model.addAttribute("data", "hello!!");
        return "hello";
    }
}
  • 동적 페이지 생성 (hello.html)
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Hello</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<p th:text="'안녕하세요. ' + ${data}" >안녕하세요. 손님</p>
</body>
</html>

  • 컨트롤러에서 리턴 값으로 문자를 반환하면 뷰 리졸버(viewResolver) 가 화면을 처리.
스프링 부트 템플릿엔진 기본 viewName 매핑

resources : templates/ + {ViewName} + .html
  • spring-boot-devtools 라이브러리 : 해당 라이브러리 추가 시, html 파일을 컴파일만 해주면 서버 재시작 없이 View 파일 변경이 가능하다. 
  • 인텔리제이 컴파일 방법 : 메뉴 build > Recompile

 

# 빌드 후 실행하기

  • 명령 프롬프트 cmd 실행하여 해당 프로젝트 레포지토리로 이동.
  • gradlew 입력하여 빌드 진행.

  • 완료 후 gradlew build 입력.

  • 완료 후 build/libs 진입하여 java -jar jar파일명 입력하여 실행.

 

반응형

+ Recent posts