현재 진행하는 사이드 프로젝트 'developSpace'에서는 탈퇴하면 '우주미아'라는 닉네임으로 변경해주기로 결정을 했었습니다. 탈퇴 기능 구현을 맡았는데 기존 게시물은 삭제 하지 않으면서 게시물에 나오는 닉네임은 '우주미아'로 변경하고, 프로필 사진도 기본 사진으로 변경하고, 회원 칼럼을 없애려고 합니다. 

 

그런데 모든 탈퇴 회원이 '우주미아'라는 닉네임을 가지면 중복이 되어 문제가 생기니 랜덤 숫자와 형용사를 붙여주기로 했습니다. 

 

랜덤 닉네임 만들기

필수적으로 들어가야 하는 것은 '우주미아'입니다. 겹치지 않기 위해서 형용사와 숫자를 포함시킨 랜덤 닉네임을 만드려고 합니다.

 

public static String randomNickname(){
    List<String> adjective = Arrays.asList("행복한", "슬픈", "게으른", "슬기로운", "수줍은",
            "그리운", "더러운", "섹시한", "배고픈", "배부른", "부자", "재벌", "웃고있는", "깨발랄한");
    String name = "우주미아";
    String number = (int)(Math.random() * 99)+1 +"";
    Collections.shuffle(adjective);
    String adj = adjective.get(0);
    return adj+name+number;
}

 

String 값을 반환하는 randomNickname 메서드를 만들었습니다.

형용사는 제가 맘대로 적어넣은 겁니다.

적다보니 19개를 적었네요. 나중에 20개로 늘려야겠습니다.

 

Collections.shuffle을 사용하면 List의 값들을 섞어줍니다. 

말그대로 셔플을합니다.

셔플 한 값중 가장 첫번째 값을 adj 에 저장합니다.

 

기본으로 들어가는 "우주미아"는 String name에 저장했습니다.

 

마지막으로, 랜덤 숫자(number)를 저장합니다.

99까지 숫자 중에서 랜덤으로 숫자가 나갑니다.

 

이 3가지 값들을 하나로 더하면 adj + name + number 로 랜덤 닉네임이 완성됩니다.

 

랜덤 닉네임 만들기

 

"부자+우주미아+57" 이 나왔네요!

 

Could not find mysql:mysql-connector-java:.

하루종일 찾다가 이것 저것 다 시도해봐도 안되서 설마 이거인가 하고 해봤는데 이거였다.

 

 

springframework.book version 이 2.7.8 이었는데

version을 2.7.6으로 변경하자마자 잘되는 걸 확인했다.

 

 

3초만에 build 가 되었다.

 

아래는 전체 build.gradle 이다. 별걸 다 시도 해봤지만 아니어서 허탈하다. 하지만 해결해서 행복.

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.6'
    id 'io.spring.dependency-management' version '1.1.0'
}

group = 'developSpace.com'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-web'

    implementation 'io.springfox:springfox-boot-starter:3.0.0'
    implementation 'org.mapstruct:mapstruct:1.5.3.Final'
    implementation 'mysql:mysql-connector-java'

    compileOnly 'org.projectlombok:lombok'
    compileOnly group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
    runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
    runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'

//    runtimeOnly 'com.h2database:h2'
    runtimeOnly 'mysql:mysql-connector-java'

    annotationProcessor 'org.projectlombok:lombok'
    annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'

}

tasks.named('test') {
    useJUnitPlatform()
}

jar {
    enabled = false
}

swagger를 프론트와 소통하는 API 명세서로 계속 사용해왔는데 설정하는 법을 정리해보려고 합니다. Spring Boot 2.7.8, Gradle 환경에서 설정하는 방법입니다. swagger를 사용하면 따로 API 명세를 작성하지 않아도 되고 추후에 수정할 일이 생겨도 자동으로 수정이 되서 편합니다. 프론트랑 소통할 때도 문서로 하는 것보다 swagger로 바로바로 업데이트 되어서 생산성도 높아집니다.

 

1. Build.gradle 의존성 추가

implementation 'io.springfox:springfox-boot-starter:3.0.0'

 

build.gradle에 위의 dependency 코드를 추가합니다.

 

2. SwaggerConfig.java

 

@Configuration
public class SwaggerConfig {
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.OAS_30)
                .ignoredParameterTypes(AuthenticationPrincipal.class)
                .ignoredParameterTypes(Pageable.class)
                .useDefaultResponseMessages(false)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("developspace.com.developspace")) // api 스펙이 작성되어 있는 패키지 (controller)
                .paths(PathSelectors.any())
                .build();
    }

    public ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("DevelopSpace Rest API Documentation")
                .description("Develop Space API 명세")
                .version("0.1")
                .build();
    }
}

 

3. security 설정 풀어주기

security로 막아 놓은 것에 swagger url 설정 추가해줬습니다.

 

http.authorizeRequests().antMatchers("/api/member/signup","/api/member/login").permitAll()
        .antMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-resources/**").permitAll()
        .antMatchers(HttpMethod.GET,"/api/answer/**").permitAll()
        .antMatchers("api/question/**").permitAll()
        .anyRequest().authenticated();

 

 

4. Swagger 확인하기

localhost:8080/swagger-ui/index.html

 

위의 주소로 접속하면 swagger api 문서를 볼 수 있습니다.

💡문제 상황

첫번째 게시글에서는 좋아요 클릭 시, 반영이 잘 되는데 두번째 게시글부터 게시글 좋아요가 반영이 안되는 현상이 발견됨. 게시글을 조회하고 있는 멤버의 좋아요 여부를 나타내는 isLiked 값이 여러번 연속해서 처리할 때 계속해서 true로만 반환 됨. 트랜잭션이 충돌을 일으킨 것으로 여겨짐.

 

해당 게시글에 조회하는 멤버가 좋아요를 눌렀을 시, 생성되는 postLike 데이터를 조회하는 과정에서 에러가 나는 것으로 확인됨. 해당 멤버와 해당 게시글 아이디로 데이터를 조회하여 postLike가 이미 있으면 false로 반환(좋아요 취소)하고 없으면 true로 반환(좋아요 성공)하는 로직임.

 

 

📒선택지

1. 낙관적 락(Optimistic Lock): version 관리를 통해 application 레벨에서 처리하는 방법으로, JPA가 제공하는 버전 관리 기능(@Version)을 사용함. 대부분의 트랜잭션이 충돌하지 않는 다고 가정하는 방법임. 충돌이 나는 것을 막지 않고, 충돌이 난 것을 감지하면 처리함

 

2. 비관적 락(Pessimistic Lock): 트랜잭션의 충돌이 발생한다고 가정함. 하나의 트랜잭션이 자원에 접근 시 락을 걸고, 다른 트랜잭션이 접근하지 못하게 함. 데이터 수정 즉시 트랜잭션 충돌 여부 확인이 가능함. DB 단에서 Lock을 함.

 

 

🤔 문제 해결

  • 이미 충돌이 발생한 것을 확인 했기 때문에 비관적 락을 사용하기로 결정함. 복합키를 이용하여 좋아요 데이터를 찾아오는 메서드에 비관적 락을 걸어주었음.
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Transactional
@QueryHints({@QueryHint(name = "javax.persistence.lock.timeout", value ="3000")})
Optional<PostLike> findByPostLikedIdAndMemberId(Long postLikedId, Long memberId);
  • 비관적 락에는 두가지 종류가 있음:
    • Exclusive Lock: 데이터를 변경하고자 할 때 사용되며, 트랜잭션이 완료될 때까지 유지되어 해당 Lock이 해제될 때까지 다른 트랜잭션은 해당 데이터에 읽기를 포함하여 접근을 할 수 없음
    • Shared Lock: 다른 사용자가 동시에 데이터를 읽을 수는 있지만 Write는 할 수 없음
  • LockModeType으로 PESSIMISTIC_WIRTE 선택하였는데, PESSIMISTIC_WRITEExclusive Lock을 걸고, 데이터를 읽거나, 업데이트 하거나, 삭제하는 것을 막음
  • Lock이 길어지면 Latency가 길어지기 때문에,  @QueryHints 를 사용하여 Lock에 3초 타임 아웃을 적용함

 

→ 좋아요 여부 값(isLiked) 반환 정상적으로 실행 됨

 

 

 

기타 고려사항

 

  • 단일 DB 환경이기 때문에 비관적 락을 사용해서 문제가 발생하지 않지만 추후에 분산 DB를 사용하는 환경일 경우에는 분산 락(Distributed Lock) 도입도 고려해 볼 수 있음. (위의 이미지는 현재 아키텍쳐; RBS 하나를 사용)
  • 비관적 락은 모든 트랜잭션에 대해 Lock을 사용하기 때문에 트래픽이 많은 경우, O(N^2) 정도로 성능이 저하된다는 문제가 생길 수 있음.

읽지 않은 알림 갯수 반환 하기

FE로부터 요청이 왔습니다. 읽지 않은 알림의 갯수를 보내달라고 합니다. API를 새로 파서 보내기로 결정 했습니다. Event 발생할때 마다 넣어달라고 처음에 요청하셨는데 Transactional 이슈가 발생할 것 같아서 우선 API를 따로 만들어서 구현하기로 했습니다.

 

DB에서 바로 몇개인지 세서 보내기 위해서 Query를 직접 작성하기로 했습니다.

 

JPQL

@Transactional
@Query("SELECT COUNT(n) from Notification n where n.receiver.nickname =:nickname and n.isRead = false")
Long countUnreadNotifications(String nickname);

Count를 써서 조건에 해당하는 값들을 셌습니다.

 

해당 nickname을 가지고 있는 수신자(receiver)를 가지고 있는 notification의 isRead 칼럼이 false이면 count를 합니다.

 

생각보다 간단해서 다행이었습니다.

querydsl를 써보려다 더 복잡한 거 같아 간단한 JPQL을 사용해서 읽지않은 알림 갯수를 가져오는 메서드를 구현했습니다.

IllegalStateException

IllegalStateException 에러 발생

위의 사진처럼 IllegalStateException이 발생했습니다.

 

IllegalStateException 원인

@RequestMapping의 값이 중복되어 나타나는 에러입니다.

새로운 API를 만들었는데 기존에 있는 url과 겹쳐서 IllegalStateException 이 발생했습니다.

 

IllegalStateException 해결

겹치는 url을 해결해주니 에러가 나타나지 않고 해결되었습니다.

 

https://github.com/ProjectBlueHair/FinalProject_BE/pull/165

 

🐞 BugFix : url이 겹치는 문제 해결 by GGONG1956 · Pull Request #165 · ProjectBlueHair/FinalProject_BE

PR 체크사항 PR이 다음 사항을 만족하는지 확인해주세요. 커밋 제목 규칙 커밋 메시지 작성 가이드라인 라벨, 담당자, 리뷰어 지정 PR 타입 어떤 유형의 PR인지 체크해주세요. Bugfix Feature Code style up

github.com

 

에러

Inferred type 'S' for type parameter 'S' is not within its bound; should extend

Inferred type 'S' for type parameter 'S' is not within its bound; should extend

에러가 생긴 곳은 repository.save를 쓰던 Service 에서 였습니다.

에러 원인

어제 지성으로 코딩하게 되었다고 좋아했는데 바보같은 실수를 하고 말았습니다.

repository를 작성할 때 extends JpaRepository를 하는데

변수의 순서를 바꿔서 적으니 오류가 났던 겁니다.

 

에러 해결

알맞은 위치로 변경해주니 에러가 사라졌습니다.

 

지성으로 코딩합시다.

 

 

 

 

 

ModifedAt CreatedAt null 값으로 나오는 에러

시간이 null 로 나온다

강의 안보고 새로 혼자서 새로 게시판 구현을 하려고 했습니다. 그런데 시간이 null 값으로 떴습니다. 다른분들의 의견은 생성자 주입을 하지 않은 것이 아니냐 였는데, 생성자로 하도 혼이 많이나서 생성자 부터 다 꼼꼼히 만들고 서비스 구현을 시작했던 터라 아무리~ 봐도 생성자 문제는 아니었습니다. 

 

DB 저장 오류

일단 게시글 생성은 시간 빼고는 찍히니까 수정을 해보자 하고 POSTMAN으로 테스트를 해봤는데 아래와 같은 결과가 나왔습니다. 역시나 생성자를 체크했고 결국엔 전에 했던 코드를 가져와서 비교해봤지만 다른 점을 찾기가 어려웠습니다.

불러오는 값 null

@EnableJpaAuditing

각종 구글링 끝에 찾아낸 정답은 아래와 같습니다.

@EnableJpaAuditing

 

application에 @EnableJpaAuditing 어노테이션을 추가하니 아래와 같이 잘 실행되는 것을 확인 할 수 있었습니다.

잘 불러옵니다.

 

@EnableJpaAuditing이란?

Java에서는 ORM 기술인 JPA를 사용하여합니다.

도메인을 관계형 데이터베이스 테이블에 매핑할 때 공통적으로 도메인들이 가지고 있는 필드나 컬럼들이 존재합니다.

대표적으로 createdAt(생성일자), modifiedAt(수정일자), 식별자 같은 필드 및 컬럼이 있습니다.

도메인마다 공통으로 존재한다는 의미는 결국 코드가 중복되고 데이터베이스에서 누가, 언제하였는지 기록을 잘 남겨놓아야 합니다. 

 

JPA에서는 Audit이라는 기능을 제공하고 있다고 합니다. 회계용어로만 알고 있었는데 프로그래밍에도 등장을 하는 단어이네요.

 

Spring Data JPA에서 자동으로 시간값을 넣어주는 기능입니다. 도메인을 영속성 컨텍스트에 저장하거나 조회를 수행한 후에 update를 하는 경우 매번 시간 데이터를 입력하여 주어야 하는데, audit 기능을 이용하면 자동으로 시간을 매핑하여 데이터베이스의 테이블에 넣어줍니다.

 

Timestamped

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class Timestamped {

    @CreatedDate
    private LocalDateTime createdAt;


    @LastModifiedDate
    private LocalDateTime modifiedAt;
}

위의 코드는 제가 시간을 기록하기 위해 만든 클래스 입니다. @MappedSuperclass 와 @EntityListners(AuditingEntityListenr.class) 어노테이션은 잘 넣어놓고 이 기능들을 활성화시키는데 가장 중요한 @EnableJpaAuditing을 까먹은 개발자는 바로 접니다.

 

@MappedSuperclass는 JPA Entity 클래스들이 Timestamped class 를 상속할 경우 createdAt, modifedAt을 칼럼으로 인식하게 합니다.

 

@EntityListners는 문자 그대로 Entity들을 듣는(?) audit하는 기능을 하게 만듭니다.

 

이제는 절대 JPA 사용할 때 @EnableJpaAuditing 을 application 에 아래와 같이 넣는 것을 잊지 맙시다.

 

@EnableJpaAuditing
@SpringBootApplication
public class PostApplication {

    public static void main(String[] args) {
        SpringApplication.run(PostApplication.class, args);
    }

}

 

자바도 모르는데 스프링하는 개발자지망생

분기문 aka 조건문

어떠한 조건에 따라 다른 명령을 실행하게 하는 문법으로, 가장 대표적인 분기문에는 if문이 있습니다.

분기문은 조건문과 같은 의미로 사용됩니다.

 

분기문 (조건문) 종류

java 자바 언어에서 분기문은 if, else if, switch 가 있습니다.

if문은 분기를 처리할 때 가장 많이 쓰이는 문법입니다.

 

조건을 분기하여 처리할 때 사용합니다.

 

분기처리

분기문을 이용해 선택적으로 조건에 따라 코드를 실행하게 만드는 것을 의미합니다.

 

 

 

 

스프링을 사용할 때 스프링 MVC 흐름에 따라 코딩하는데 @Controller를 사용할 때도 있고 @RestController를 사용할 때가 있습니다. 이번 포스팅에서는 언제 @Controller를 사용하고 @RestController를 사용하는지 스프링 MVC 흐름과 함께 알아보겠습니다.

@Controller

Spring MVC의 컨트롤러는 View를 반환하기 위해 사용합니다. Client의 요청을 받아 View를 반환합니다. Spring MVC의 흐름은 아래와 같습니다:

  1. Client는 URI 형식으로 웹 서비스에 요청
  2. DispatcherServlet은 HandlerMapping에 요청 위임
  3. HandlerMapping을 통해 요청을 Controller로 위임
  4. Controller는 요청을 처리한 후에 ViewName을 반환
  5. DispatcherServlet은 ViewResolver를 통해 ViewName에 해당하는 View를 찾아 클라이언트에게 반환

View를 반환하기 때문에 데이터를 보낼 때는 @RequestBody 어노테이션을 붙여서 Json 형태로 반환해줍니다.

 

@RestController

  • @Controller에 @ResponseBody가 추가된 어노테이션입니다.
  • Json 형태로 객체 데이터를 반환합니다. 
  • @RequestBody를 사용할 필요가 없습니다.
  • REST API 를 개발할 때 주로 사용합니다.

데이터 형식으로 보내는지 반환할 view가 있는지 등을 생각해서 @RestController를 쓸지 @Controller를 사용할 지 선택하고 필요에 따라 @RequestBody를 붙여주면 되겠습니다.

 

 

 

글자수 양식 제한 구현 Spring boot

Jakarta Bean Validation Constraints 사용 방법

아이디나 비밀번호에 작성 제한(글자수, 한글, 특수문자 등)을 둘 때 쓰는 @Size 나 @Pattern 이 들어있는 패키지입니다.

 

아래 코드를 Build.gradle의 dependencies 에 입력하시면 사용할 수 있습니다.

 

implementation 'org.springframework.boot:spring-boot-starter-validation'

 

@Size @Pattern 사용 아이디 비밀번호 제한 두기

@Size 사용 방법

@Size 어노테이션이 붙은 element는 지정한 값의 사이(지정값 포함)에 있어야 합니다. 

 

최소값과 최대값을 결정할 수 있습니다.

 

jakarta.validation.constraints를 사용하기 위해 아래를 import 합니다.

import jakarta.validation.constraints.Size;

 

제한을 두고 싶은 변수 위에 아래 어노테이션을 입력하고,

 

@Size

최소길이값은 min = 4

최대길이값은 max = 10

 

@Size(min=4, max=10)

필요하다면 뒤에 메세지를 추가 할 수 있습니다.

 

@Pattern 사용 방법

@Pattern 어노테이션이 붙은 element는 특정한 조건을 만족해야 합니다.

 

 

jakarta.validation.constraints를 사용하기 위해 아래를 import 합니다.

import jakarta.validation.constraints.Pattern;

 

제한을 두고 싶은 변수 위에 아래 어노테이션을 입력합니다

@Pattern

정규표현식(Regular Expression)을 사용해서 조건을 입력합니다.

regexp= 조건

@Pattern(regexp = "[a-z0-9]*$")
정규 표현식(Regular Expression) 설명
^[0-9]*$ 숫자
^[a-zA-Z]*$ 영문
^[가-힣]*$ 한글
\\w+@\\w+\\.\\w+(\\.\\w+)? 이메일주소
[] 문자의 집합 범위
{} 횟수 또는 범위
* 문자가 1번이상 발생
^ 문자열의 시작
$ 문자열의 끝
\ 확장 문자의 시작
\w 알파벳이나 숫자

 

 

 

아이디, 비밀번호 입력 제한 예시

// nullable: null 허용 여부
// unique: 중복 허용 여부 (false 일때 중복 허용)
@Column(nullable = false, unique = true)
@Size(min = 4, max = 10, message = "아이디의 길이는 4자에서 10자 사이입니다")
@Pattern(regexp = "[a-z0-9]*$", message = "아이디 형식이 일치하지 않습니다")
private String username;

@Column(nullable = false)
@Size(min = 8, max = 15, message = "비밀번호의 길이는 8자에서 15자 사이입니다")
@Pattern(regexp = "[a-zA-Z0-9`~!@#$%^&*()_=+|{};:,.<>/?]*$", message = "비밀번호 형식이 일치하지 않습니다")
private String password;

 

공식 문서

아래 Jakarta Bean Validation 공식문서를 참조하시면 도움이 됩니다.

Jakarta Bean Validation에서 사용할 수 있는 어노테이션에 대한 설명이 나와있습니다.

 

Jakarta Bean Validation specification

BeanNode, PropertyNode and ContainerElementNode host getContainerClass() and getTypeArgumentIndex(). If the node represents an element that is contained in a container such as Optional, List or Map, the former returns the declared type of the container and

jakarta.ee

 

쿠키와 세션

HTTP는 상태를 저장하지 않는 Stateless 특성을 가지고 있습니다. HTTP 상태는 기억되지 않기 때문에 쿠키와 세션을 이용해서 HTTP에 상태 정보를 유지(Stateful)하기 위해 사용합니다. 

 

쿠키와 세션을 통해 서버에서 클라이언트 별로 인증 및 인가가 가능합니다.

 

쿠키 Cookie

  • 클라이언트에 저장 될 목적으로 생성한 파일
  • 개발자도구 - Application - Storage - Cookies 에서 확인 가능
  • 구성 요소
    • Name: 구별하는데 사용되는 키 (중복 x)
    • Value: 쿠키의 값
    • Domain: 쿠키가 저장된 도메인
    • Path: 쿠키가 사용되는 경로
    • Expires: 쿠키의 만료기한
  • 보안에 취약함
    • 클라이언트에서 쿠키 정보를 쉽게 변경, 삭제, 및 가로채기 당할 수 있음

 

세션 Session

  • 서버에서 일정시간 동안 클라이언트 상태를 유지하기 위해 사용
  • 서버에서 클라이언트 별로 유니크 '세션 ID'를 부여한 후 클라이언트 별 필요한 정보를 서버에 저장
  • 서버에서 생성한 세션ID는 클라이언트의 쿠키값(aka. 세션 쿠키)으로 저장되어 클라이언트 식별에 사용

 

세션 동작 방식

  1. 클라이언트가 서버에 1번 요청
  2. 서버가 세션ID를 생성 -> 응답 헤더에 전달
  3. 클라이언트가 쿠키를 저장 (세션 쿠키)
  4. 클라이언트가 서버에 2번째 요청  - 쿠키값(세션ID) 포함 요청
  5. 서버가 세션ID를 확인 -> 1번 요청과 같은 클라이언트임을 확인

 

쿠키와 세션 비교하기

  쿠키(Cookie) 세션(Session)
설명 클라이언트에 저장될 목적으로 생성한 작은 정보를 담은 파일 서버에서 일정시간 동안 클라이언트 상태를  유지하기 위해 사용
저장 위치 클라이언트 (웹 브라우저) 웹 서버
사용 예시 팝업 "오늘 다시보지 않기" 정보 저장 로그인 정보 저장
만료 시점 쿠키 저장 시 만료일시 설정 가능
(브라우저 종료시에도 유지 가능)
다음 조건 중 하나가 만족될 경우 만료됨
1. 브라우져 종료 시
2. 클라이언트 로그아웃 시
3. 서버에 설정한 유지기간까지 해당 클라이언트의 재요청이 없는 경우
용량 제한 브라우저 별로 상이
크롬기준:
   도메인 당 180개
   쿠키 당 4KB
개수 제한 없음
보안 취약 비교적 안전
(서버에 저장 되기 때문에)

 

 

결론

쿠키와 세션을 통해 클라이언트 별로 인증 및 인가를 할 수 있습니다. 

아래와 같이 오류가 뜬다면 사용중인 포트를 터미널로 찾아서 kill 할 수 잇습니다.

Web server failed to start. Port 8080 was already in use.

Action:

Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.


Process finished with exit code 1

web server failed to start. Port 8080 was already in use

사용중인 포트 찾아서 kill 하는 법

1. 터미널에 들어가서 kill을 원하는 포트를 다음 코드를 입력해서 찾습니다.

ex) port 8080

lsof -i: 8080

숫자부분에 원하는 포트 번호를 입력하시면 됩니다.

 

아래와 같이 포트 정보가 출력됩니다.

 

2. 사용중인 포트 kill 하기

kill -9

또는 

kill PID숫자 를 입력하여 사용중인 포트를 kill 합니다.

위의 예시의 PID 숫자 55149를 아래와 같이 입력하였습니다

kill 55149

 

다시 코드를 실행하면 port가 잘 돌아가는 것을 확인할 수 있습니다.

 

 

+ Recent posts