Spring MVC의 @ModelAttribute 어노테이션에 대한 개념 정리

 

Spring MVC에서 @ModelAttribute을 메소드의 파라미터로 사용할 경우 프로그램이 어떤 식으로 돌아가는지를 정리하고자 한다.

다른 어노테이션에 비해 @ModelAttribute는 내부적으로 돌아가는 부분이 많은 것 같다.

즉 Spring framework이 내부에서 알아서 처리해 주는 부분이 다른 어노테이션에 비해 더  많은 것 같다. 따라서 개발자의 손을 떠나 보이지 않는 가운데서 처리되는 부분에 대한 개념이 없다면 어둠 속에서 더듬이가 될수 밖에 없는 것이다.

 

여기 다음과 같은 빈 클래스가 있다고 할때

 

public class MemberInfo 

{

     private int seq;

     private String name;

     private int age;

 

     //이하 getter, setter는 생략

}

 

http://localhost:8080/member/info?name=Gildong&age=25seq=327

 

와 같이 접속되어 들어올 때 Controller 클래스의 아래 메소드가 실행될 것이다.

 

@Controller

@RequestMapping("/member/*")

public class MemberController

{

  ... 중 략 ...

@RequestMapping(value = "/info", method=RequestMethod.GET)

public void show(@RequestParam("seq") int seq, @ModelAttribute("myMEM") MemberInfo info, Model model)

{

     logger.info("####### info.getName() "+info.getName());

     logger.info("####### info.getAge() "+info.getAge());

 

     try {

          //service.read(seq)가 MemberVO 객체를 반환한다고 할 경우

          model.addAttribute(service.read(seq));

     }catch(Exception e) {

         e.printStackTrace();

     }

}

  ... 후 략 ...

}

 

이 메소드가 실행되면 info.jsp가 자동으로 실행되게 된다. 따라서 info.jsp가 만들어져 있어야 되고 없으면 404 Not found 에러가 발생할 것이다.

그리고 info.jsp의 아래 코드에서 결과가 나오게 될 것이다.

 

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<!DOCTYPE html>

<html>

<head>

<meta charset="EUC-KR">

<title>Insert title here</title>

</head>

<body>

     <h3>회원 이름(info.name) : ${info.name }</h3> <%-- 여기서는 아무것도 안 나옴 --%>

     <hr/>

     <h3>회원 이름(myMEM.getName()) : ${myMEM.getName() }</h3>   <%-- 회원 이름이 정상적으로 출력된다. --%>

     <h3>현재 이름(myMEM.name) : ${myMEM.name }</h3>             <%-- 회원 이름이 정상적으로 출력된다 --%>

     <hr/>

     <h3>회원 번호 : ${memberVO.seq }</h3>    <%-- 회원 번호가 정상적으로 출력된다 --%>

</body>

</html>

 

JSP의 코드는 위의 내용이 전부이다. 무엇이 어떻게 돌아가기에 http url로 들어온 회원 이름과 회원 번호가 Controller에 저장이되고 Controller에서 JSP로 특별하게 보내는 코드도 없어 보이는데 JSP 코드에서 저렇게 값이 정상적으로 출력이 된단 말인가?

이것이 @ModelAttribute의 위력이고 편리함이면서 동시에 개발자를 더듬이로 만드는 측면이기도 하다.

단순한 @ModelAttribute가 무슨 역할을 했단 말인가?

 

@ModelAttribute 선언 후 자동으로 진행되는 작업들은 다음과 같다.

     ① @ModelAttribute 어노테이션이 붙은 객체를 자동으로 생성한다. 

         위의 코드에서는 MemberInfo 클래스의 객체 info를 자동으로 생성한다. 

         이때 @ModelAttribute가 지정되는 클래스는 빈 클래스라야 한다. 

         즉 MemberInfo 클래스는 beans 클래스라야 한다.

         그리고 getter와 setter가 명명 규칙에 맞게 만들어져 있어야 한다.

 

   ② 생성된 오브젝트에(info) HTTP로 넘어 온 값들을 자동으로 바인딩한다. 

       위의 코드의 경우는 name=Gildong&age=25seq=327 이렇게 들어오는 

       name, age, seq의 값이 MemberInfo의 해당 변수의 setter를 통해서 

       해당 멤버 변수에로 binding된다.

 

   ③ @ModelAttribute 어노테이션이 붙은 객체가(여기서는 MemberInfo 객체) 
       자동으로 Model 객체에 추가되고 따라서 MemberInfo 객체가 .jsp 뷰단까지 전달이 된다.

 

이상의 작업이 개발자를 대신해서 Spring framework가 알아서 다 처리해 준다. 편리하긴하다. 

이때 @ModelAttribute() 괄호 안에 지정한 문자열(위의 경우에는 myMEM)의 의미를 알아야 한다. 이 문자열의 이름으로(이것이 객체이다) Model 객체에 자동으로 추가가 되고 따라서 JSP 뷰단으로 안전하게 넘어가게 된다. 즉 MemberInfo 객체가 Model 객체에 추가될 때 @ModelAttribute()의 괄호 안에 지정한 문자열의 이름으로 추가된다는 점이다. 그리고 이 문자열 이름은 MemberInfo의 객체인 것이다. 

만일 @ModelAttribute()의 괄호 안에 아무런 문자열도 지정하지 않으면 JSP 페이지에서 MemberInfo 객체에 저장되어 있는 값을 사용할수가 없게된다.

 

보이지 않는 가운데서 내부적으로 Spring에 의해 처리되는 이상의 작업들로 인해 info.jsp에서 다음 코드가 유효하게 동작하는 것이다.

 

<h3>회원 이름(myMEM.getName()) : ${myMEM.getName() }</h3>   <%-- 회원 이름이 정상적으로 출력된다. --%>

<h3>현재 페이지(myMEM.name) : ${myMEM.name }</h3>             <%-- 회원 이름이 정상적으로 출력된다 --%>

 

여기서 ${myMEM.getName()}과 ${myMEM.name}의 차이가 무엇인고 하면 전자의 경우는 MemberInfo의 메소드를 직접 호출해서 사용한 경우이고 후자의 경우는 MemberInfo의 멤버 변수 name을 JSTL에서 사용하면 자동으로 name의 getter인 getName()이 호출되게 되는 것이다.

jQuery attr() 함수에 대해


jQuery의 attr() 함수는 선택된 특정 element의 값을 반환하거나 해당 element에 값을 설정하거나의 동작을 하는 함수이다.


$(selector).attr(attribute)

 ⇒ 해당 attribute의 값을 반환한다.


$(selector).attr(attribute, value)

 ⇒ 해당 attribute의 값을 value의 값으로 설정한다.


$(selector).attr({attribute:value, attribute:valeu, ...})

 ⇒ 여러개의 attribute에 값을 설정하기


아래는 예제코드이다.


<!DOCTYPE html>

<html>

<head>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>


<script>

$(document).read(function(){

//$("button").click(function(){

$(".orgn").click(function(){

//img some.jpg의 width를 400으로 설정하기

$("img").attr("width", 400);

});


$(".joe").click(function(){

//img some.jpg의 width 값을 반환하기

alert("Image's width : " + $("img").attr("width"));

});

});

</script>

</head>

<body>


<img src="some.jpg" alt="Some Image" width="300" height="200"><br>


<button class="orgn">이미지 width 값 설정</button><br/>

<button class="joe">Image Width 보기</button>


</body>

</html>



JavaScript의 preventDefault() 메소드에 관해


HTML에서 a 태그는 기본적으로  href가 지정한 URL로 이동하게되어 있다. 그런데 기본적인 링크 동작을 다르게 변경하거나 추가할 뭔가가 있을 때 JavaScript에서 기본적인 연결을 해제하고 JavaScript 단에서 다른 원하는 동작을 하게 할수도 있다.


<!DOCTYPE html>

<html>

<body>

<hr/>

<a id="myBlog" href="http://developer-joe.tistory.com/">코드조각 블로그로 이동</a>

<p>JavaScript의 preventDefault() 함수는 위의 링크에 있는 url 연결 동작을 해제한다.</p>


<script>

document.getElementById("myBlog").addEventListener("click", function(event){

event.preventDefault();

});

</script>


</body>

</html>



※ 본 내용은 구멍가게 코딩단의 "코드로 배우는 스프링 웹 프로젝트"라는 책의 내용을 공부하다가 정리하게 된 내용이다.


Spring MVC의 동작 구조, 동작 흐름을 파악하는 것이 그렇게 간단하지가 않다.

게시판 관련 작업을 한다고 할때 다음과 같은 클래스들이 있다고 가정해 보자.


BoardDAO : interface로 MyBatis XML Mapper를 활용하여 DB 연동 작업

BoardDAOImpl : BoardDAO interface를 구현한 클래스

BoardController : @RequestMapping 어노테이션을 활용하여 HTTP URL과 jsp 뷰단을 mapping(연결)하는 역할


이상과 같을 때 게시물에 대한 페이징 처리를 위해 Criteria라는 클래스가 있다고 하자.


public class Criteria 

{

private int myPage;          //MySQL limit 구문에서 시작 페이지로 지정할 변수

private int perPageNum;   //한 페이지당 보여질 게시물 갯수


public int getMyPage() {  //myPage에 대한 getter

... 생 략 ...

}

public void setMyPage(int myPage) {  //myPage에 대한 setter

... 생 략 ...

}


... 이하 생략 ...

}


이 클래스는 전체 게시물을 하나의 페이지당 몇 개씩 보여줄 것인지의 정보와 전체 페이지들 중에서 몇 번째 페이지의 게시물을 가져올 것인지에 대한 정보를 관리하는 역할하는 클래스이다.

이를 때에 BoardController에 아래와 같은 페이징 처리하는 메소드가 있다고 가정하자.


@Controller

@RequestMapping("/board/*")

public class BoardController 

{

private static final Logger logger = LoggerFactory.getLogger(BoardController.class);


... 중략 ...


@RequestMapping(value = "/listCri", method=RequestMethod.GET)

public void listAll(Criteria cri, Model model) throws Exception

{

model.addAttribute("list", service.listCriteria(cri));

}


... 이하 생략 ...

}


이상의 상황에서 다음과 같은 url 접속이 있을 때 위의 각각의 클래스들이 어떻게 연결지어가면서 동작하는가 하는 것이다.

(아래에서 myPage와 perPageNum은 Criteria의 멤버 변수명과 일치해야 한다)


http://localhost:8080/board/listCri?myPage=3&perPageNum=15


와 같이 접속해 오면 @RequestMapping 어노테이션에 의해 BoardController의 다음 메소드가 실행이 될 것이다.


@RequestMapping(value = "/listCri", method=RequestMethod.GET)

public void listAll(Criteria cri, Model model) throws Exception

{

model.addAttribute("list", service.listCriteria(cri));

}


그리고 여기서 개발자의 손을 떠난 보이지 않는 영역가운데서 Spring에 의해서 모종의 동작들이 내부적으로 진행이 되는 것이다. 위의 listAll() 메소드에 개발자가 작성한 코드는 딸랑 한 줄 뿐이다. 이런 면이 편리하기도 하지만 내막을 모르면 개발자는 눈감고 더듬는 더듬이가 되는 것이다. 

위의 메소드에서 내부적으로 Spring 프레임웤이 자동으로 Criteria 클래스의 객체를 생성하면서 http url에 있는 myPage, perPageNum의 값을 각각 Criteria의 두 멤버 변수의 getter, setter를 이용해서 값을 할당하게 된다.

그런 후에 model을 이용해서 jsp 단에 해당 값을 넘기고 @RequestMapping의 value에 지정한 값인 listCri라는 이름의 jsp인 listCri.jsp를 실행한다(이것 또한 명시적으로 return "listCri"라고 하지 않았지만 또 역시 내부적으로 Spring에 의해처 처리가 되는 영역이다).


그런데 여기서 Criteria의 멤버 변수 perPageNum을 pgNum으로 변경할 경우 다음과 같이 접속을 하면 어떻게 될까?


http://localhost:8080/board/listCri?myPage=3&pgNum=15


만일 멤버 변수만 perPageNum을 pgNum으로 변경했다면 위의 접속은 15페이지 값이 적용되지 않는다.

원리상 되야 될것 같은데 정상동작하지 않는다.

해법은 perPageNum에 맞게 명명된 getter와 setter를 pgNum에 맞게 변경해 주어야 한다. 이것까지 변경해 주어야 위의 url은 비로소 정상 동작을 하는 것이다.

pgNum에 대한 getter, setter 명명 규칙대로 변경하면 getter는 getPgNum()이 될 것이고 setter는 setPgNum()이 될 것이다.

이렇게 getter, setter 명명 규칙에 맞게 메소드 명을 변경하지 않으면(대소문자도 정확히) 아래 URL은 정상 동작을 기대하지 말아야 한다.


http://localhost:8080/board/listCri?myPage=3&pgNum=15


순수 Java/JSP로 개발할 때는 개발자가 직접 코딩해야 하던 것들이 Spring을 이용하면 개발자의 손 가락을 쉬게해 주는 여러 편리한 면도 있지만 이 편리함이라는 게 개발자의 손을 떠난 영역에서 동작하는 것들이 점점 많아 지게 된다는 이야기가 되고 개발자는 더듬이가 되어가야 한다는 이야기로 귀결하는 것 같다.

그래도 아무튼 뭐 어쩌겠는가?




Spring에서의 @RequestMapping 어노테이션에 대한 간단 정리


-. @RequestMapping 어노테이션은 Spring 웹 애플리케이션에서 가장 자주 사용되는 annotation이다.

-. @RequestMapping은 http request로 들어오는 url을 특정 controller 클래스나 메소드로 연결시키는 역할을 한다.

-. @RequestMapping은 controller에 있어서 class에 적용할수도 있고 특정 method에 적용할수도 있다.


아래의 예를 통해서 확인해 보자.


@Controller

@RequestMapping("/home")

public class TestController 

{

     //아래 @RequestMapping은 

     //hostname:port/home/에 대한 http request url에 대응하는 역할

    @RequestMapping("/")

    String getName(){

        return "Hello from getName() method";

    }

 

     //아래 @RequestMapping은 

     //hostname:port/home/info/에 대한 http request url에 대응하는 역할

    @RequestMapping("/info")

    String showInfo(){

        return "Hello from showInfo() method";

    }

}


-. http://localhost:8080/home/은 getName()을 호출하게 된다.

-. http://localhost:8080/home/info은 showInfo()을 호출하게 된다.


아래와 같이 @RequestMapping을 이용해서 특정한 method에 Multiple URI를 적용할수도 있다. 


@Controller

@RequestMapping("/home")

public class TestController 

{

    @RequestMapping(value={"", "/info", "info*","view/*,**/msg"})

    String myMultiMapping(){

        return "Hello from myMultiMapping()";

    }

}


위와 같이 @RequestMapping은 와일드 카드(wildcards)도 사용할수 있다. 

위의 코드에서 아래의 모든 URL들은 myMultiMapping() 메소드로 연결될 것이다.


localhost:8080/home

localhost:8080/home/

localhost:8080/home/info

localhost:8080/home/infosub

localhost:8080/home/view/

localhost:8080/home/view/view



외부의 텍스트 파일로 작성된 SQL문을 이용하여 MySQL의 table 만들기


d:\mydir\member.sql이라는 이름으로 다음과 같은 SQL문이 작성되어 있다고 할때 SQL문이 작성된 외부의 파일로부터 MySQL의 테이블 생성하는 방법이다.


create table tbl_member (

userid varchar(50) not null,

userpw varchar(50) not null,

username varchar(50) not null,

email varchar(100),

regdate timestamp default now(),

updatedate timestamp default now(),

primary key(userid)

);


먼저 mysql에 로그인한다. 만일 tbl_mamber라는 테이블을 생성할 데이터베이스가 book이라고 한다면

C:\>mysql -uroot -p


mysql>use book;

Database changed


mysql>source d:/mydir/member.sql

Query OK, 0 rows affected (0.03 sec)


여기서 중요한 것은 윈도우즈에서 경로 표시때 사용되는 \(backslash)를 사용하는 것이 아니라 /(forwardslash)를 사용한다는 점이다.

이상을 tbl_member라는 book이라는 데이터베이스에 tbl_member라는 테이블을 생성했다.


mysql> show tables;

+--------------------+

| Tables_in_book     |

+--------------------+

| tbl_member         |

+--------------------+

1 row in set (0.00 sec)


mysql> desc tbl_member;

+------------+--------------+------+-----+-------------------+-------+

| Field      | Type         | Null | Key | Default           | Extra |

+------------+--------------+------+-----+-------------------+-------+

| userid     | varchar(50)  | NO   | PRI | NULL              |       |

| userpw     | varchar(50)  | NO   |     | NULL              |       |

| username   | varchar(50)  | NO   |     | NULL              |       |

| email      | varchar(100) | YES  |     | NULL              |       |

| regdate    | timestamp    | NO   |     | CURRENT_TIMESTAMP |       |

| updatedate | timestamp    | NO   |     | CURRENT_TIMESTAMP |       |

+------------+--------------+------+-----+-------------------+-------+

6 rows in set (0.01 sec)




왜 interface인가, interface가 갖는 의의와 목적


Java 등에서 interface가 갖는 의의는 interface를 "구현(implements)"하는 하위 클래스들 모두에 동일한 기능(메소드)를 갖도록 함을 통해서 동일한 사용자 경험을 갖도록 강제하는 의의를 가지고 있는 것이 interface이다.

이에 대한 자세한 이야기는 여기를 클릭


이와 함께 또 하나 interface가 갖는 의의는 전체적으로 어떤, 어떤 기능이 있어야 할 것인지 전체적인 그림을 그릴수 있도록 하는 것이 interface가 하는 역할이다.


inteface는 각각의 메소드의 몸체를 구현하지 않아도 되기에 전체적으로 있어야 할 기능들에 대한 메소드 이름만 지정하면서 필요한 기능들의 전체 그림을 그릴수 있도록 하는 역할을 하는 것이 interface이다.


이렇게 지정한 각 메소드들의 detail한 실제 기능구현은 interface를 "구현(implements)"하는 하위 클래스들에서 숙고하고 고려하면 되므로 interface는 전체 방향 설정 역할 혹은 나아갈 방향에 대한 지도(map) 혁할을 한다는데 또 하나의 중요한 의의가 있다.


전체적인 숲의 모양을 그려주고 그 숲의 세부적인 부분들은 하위 클래스에서 구현하면 되도록 함을 통해서 즉 전체 그림과 세부적인 실제 기능을 분리함을 통해서 개발을 보다 용이하게 혹은 배가 산으로 가는 일이 없도록 하는 역할을 하는 것이 interface가 갖는 의의이고 목적이다.




구형 노트북에 Linux로 Apache 웹 서버를 구축했다. 그리고 ipTIME의 DDNS로 도메인을 만들고 동영상 스트리밍 서버를 간단하게 만들었다.

그리고는 도메인으로 접속을 하니 잘 되었다. 

그런데 외부에 있는 사람에게 URL 주소를 주고 접속하게 했더니 .... 접속이 되지를 않았다.

말이 안되는 상황이 벌어졌다.


몰론 당연히 ipTIME에서 80 포트를 웹 서버로 사용하는 노트북으로 포트 포워딩을 했다.

결국 추적해 보니 사용하고 있는 통신사 LGU+에서 80 포트를 막아놓았다는 것을 알아냈다.

LGU+에 몇 차례에 걸쳐서 전화를 해 본 결과 실토를 받아냈다.


보안상의 이유 어쩌고 하는 말도 안되는 소리를 했고(80포트가 그렇게 보안상 막아야 한다면 기업 인터넷에서 왜 열어주는 것임?) 정책상 열어 줄수 없다는 최종적인 실토를 받아냈다.  통신사들의 갑질 아닌가 이게? 정상적으로 사용료를 지불하는데 어째서 80 포트를 사용 못해야한단 말인가?

이 과정에서 포트 개폐 유무를 체크해 주는 좋은 사이트를 발견했다.

아래 사이트는 포트 뿐만 아니라 IP 위치, Traceroute 등 다양한 유용한 기능들을 제공하는 쓸모가 많은 사이트이다.


http://www.whatsmyip.org/port-scanner/


결국 위의 문제는 세상 모든 사람들이 공통적으로 사용하는 80 포트를 사용하지 못하고 외부에서 들어오는 포트를 별다른 포트(예를들어 8181)로 만들고 그것을 내부에서는 80포트로 포트 포워딩 하는 작업을 해서 처리는 했지만 재미없는 일이다.




그래서 결국은 xxx.iptime.org:8181/xxx.php와 같은 방식으로 접속을 해야하는 수 밖에 없게 되었다.


포트포워딩 얘기가 나온김에 참고적으로 ipTIME 공유기의 DDNS를 이용해서 외부에서 puTTY를 이용해서 SSH로 접속이 가능할려면 SSH로 사용할 포트에 대해서도 역시 ipTIME 공유기의 관리자 페이지에서 포트포워딩해 주어야 된다.

FTP나 SFTP 등의 경우에 대해서도 역시 마찬가지 작업을 해 주어야 FileZilla나 puTTY를 통해 웹 서버에 접속이 가능하게 된다.

물론 Linux 서버단에서의 설정도 당연히 해 주어야 하는 건 그 또한 당연한 일이다.


짜증 나는 LGU+ 통신사로구나. 다른 통신사도 마찬가지인지 모르겠다. 80 포트 허용해 주는 통신사로 이동해야겠다.



안드로이드로 BLE(Bluetooth Low Energy) 개발을 하다보면 2대의 폰이 하나는 Peripheral(Slave) 모드로 다른 하나는 Central(Master) 모드로 동작하는 테스트 앱을 만들어야 할 때가 있다.

왜냐하면 Bluetooth Classic(혹은 Bluetooth Basic)과는 달리 BLE의 경우는 Peripheral 모드로 동작하는 기기가 없으면 Central에서 아무리 scan을 해 봐야 검색 자체가 되지 않기 때문이다.

참고로 스마트폰의 경우는 BLE로도 혹은 Bluetooth Classic으로 동작을 하는 '듀얼 모드'(혹은 Bluetooth Smart Ready)를 지원하기 때문에 이상의 작업 환경을 만드는 것이 가능하다.

또 참고로 BLE만을 지원하는 경우를 Bluetooth Smart 디바이스라고 하고 '싱글 모드'로 표현하기도 한다. 이 경우는 기존의 Bluetooth Classic 기기와는 호환되지 않는다.


여기서 BLE가 Peripheral 모드로 동작한다는 뜻은 다른 BLE 디바이스와 connection을 맺기 위해 advertising signal을 주기적으로 내보내는 역할을 하는 기기를 말하고 Central로 동작한다는 뜻은 다른 BLE가 내보내는 advertising signal를 주기적으로 스캔하는 디바이스를 말한다.

아무튼...


이렇게 Peripheral 모드로 동작하는 안드로이드 개발을 하다보면 아래 코드조각의 onStartFailure() 메소드에서 errorCode 1을 반환하는 경우를 만나게 된다.


        AdvertiseCallback advertisingCallback = new AdvertiseCallback(){

          @Override

          public void onStartSuccess(AdvertiseSettings settingsInEffect){

              Toast.makeText(getApplicationContext(), "Advertising 성공~ ", 1).show();

              super.onStartSuccess(settingsInEffect);

          }


          @Override

          public void onStartFailure(int errorCode){

              Log.e("BLE #######", "Advertising onStartFailure: "+errorCode); //여기서 errorCode가 1을 반환하는 에러


              //아래 Toast에서 errorCode가 1이 나오는데 API 문서를 보면

              //ADVERTISE_FAILED_DATA_TOO_LARGE 에러라고 설명이 되어 있다.

              //Failed to start advertising as the advertise data to be broadcasted is larger than 31 bytes.

              Toast.makeText(getApplicationContext(), "Advertising onStartFailure: "+errorCode, 1).show();

              super.onStartFailure(errorCode);

          }

        };


이 경우는 안드로이드 API 문서의 아래 설명과 같이 advertising의 데이터 길이가 31bytes를 넘어서 발생하는 문제이다.


"Failed to start advertising as the advertise data to be broadcasted is larger than 31 bytes."


BLE Peripheral 기기가 내보내는 advertising signal의 길이는 31byte로 한정되어 있는데 코드상에서 설정한 이 데이터의 크기가 31byte를 넘어서 발행한 경우이다.

아래 코드 조각이 advertising 데이터를 설정하는 코드이다.


        BluetoothLeAdvertiser advertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();


        AdvertiseSettings settings = new AdvertiseSettings.Builder()

                .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)

                .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)

                .setConnectable(false)

                .build();


        ParcelUuid pUuid = new ParcelUuid(UUID.fromString(getString(R.string.ble_uuid2)));


        AdvertiseData data = new AdvertiseData.Builder()

                .setIncludeDeviceName(true)

                .addServiceUuid(pUuid)

                .addServiceData(pUuid, "Data".getBytes(Charset.forName("UTF-8")))

                .build();


위의 AdvertiseData의 설정을 아래와 같이 수정해본다.


        AdvertiseData data = new AdvertiseData.Builder()

                .setIncludeDeviceName(true)

//                .addServiceUuid(pUuid)

//                .addServiceData(pUuid, "Data".getBytes(Charset.forName("UTF-8")))

                .build();


UUID와 Service Data를 advertising signal 데이터에 포함시키지 않도록 주석처리했다.

이제 문제가 해결되었을 것이고 다른 BLE에서 본 소스를 실행한 BLE를 정상적으로 잘 스캐닝 및 검색을 할수 있을 것이다.




블루투스 연동이나 BLE 연동 등의 작업을 하다보면 UUID 값을 필요로 하는 경우가 있다.

이때 UUID를 생성할수 있는 간단한 방법이 있다.

리눅스 시스템이 있거나(혹은 VirtualBox 등으로 리눅스 설치 등) 혹은 윈도우즈 환경에서도 UUID 값을 간단하게 생성할수 있는 방법이 있다.


리눅스의 경우 터미널 창에서 uuidgen 명령어를 실행하면 UUID 값을 생성해 준다. 반드시 root로 로그인하지 않아도 된다.


root@.....# uuidgen

f26e4cff-704a-47b5-9a25-77f7bc9ba54a


윈도우즈의 경우는 DOS 창(커맨더 창, 명령 프롬프트 창)을 띄워서 다음 명령을 수행하면 역시 UUID 값을 얻을수 있다.


Microsoft Windows [Version 10.0.16299.492]

(c) 2017 Microsoft Corporation. All rights reserved.


C:\Users\User>powershell -Command "[guid]::NewGuid().ToString()"

99924071-3058-4acd-bd95-978a77db9352


C:\Users\User>



+ Recent posts