이해하기 좋은 자료부터 공유. 

Guava’s Event Bus from Venkaiah Chowdary Koneru



여러 Event를 만들어 던지고, 또한 조건에 따라 EventListener를 이용하여 받을 때, 하나의 EventBus를 사용하면 코드가 간결해질 수 있다. 이런 IDEA로 만들어진 것이 Guava의 EventBus이다. 


하나의 EventBus를 생성 후, Listener interface를 상속하지 않은 pojo 클래스를 하나 생성하고 이벤트를 받을 메소드에 @Subscribe 를 추가하면 된다. 클래스에 @Subscribe 를 추가만 해도 Event는 전달되도록 되어 있다.

그리고 EventBus.post()의 인자 type에 따라 Event를 전달하는 방식이라, 객체 지향적으로 코드를 개발할 수 있다. 


import org.junit.Test;

import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;

public class GuavaTest {

class EventBusListener {
private boolean received;

@Subscribe
public void recieveMessage(String message) {
System.
out.println("String : " + message);
received = true;
}
                @Subscribe
public void getMessage(String message) {
System.
out.println("xxx : " + message);
received = true;
}

public boolean isReceived() {
return received;
}
}

class User {
int id;
public User(int id) {
this.id = id;
}
}

class UserEventBusListener {
private boolean received;

@Subscribe
public void recieveMessage(User user) {
System.
out.println("User : " + user.id);
received = true;
}
public boolean isReceived() {
return received;
}
}

@Test
public void test() {
EventBus
eventBus = new EventBus("test");

EventBusListener
eventBusListener = new EventBusListener();
UserEventBusListener
userEventBusListener = new UserEventBusListener();
eventBus.register(eventBusListener);
eventBus.register(userEventBusListener);

eventBus.post(new String("hello"));
eventBus.post(new User(1));
}
}



결과

String : hello

xxx : hello

User : 1


'general java' 카테고리의 다른 글

[play1] play deps --forceCopy  (0) 2015.05.06
maven mvn:deploy 시 401 에러  (0) 2015.04.27
[Guava] Monitor 예제  (0) 2015.04.21
[Guava] Futures 예제  (0) 2015.04.17
[Guava] Preconditions 예제  (0) 2015.04.16
Posted by '김용환'
,



Guava의 Monitor 클래스에 대한 API를 보면 기존의 locking에 대한 좋은 내용이 나온다.

http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/util/concurrent/Monitor.html


synchronized(notifyAll, wait)와 ReentrantLock(signal, Condition)에 대한 설명이 있다.


Guava의 Monitor 예제는 위에서 언급한 synchronized와 ReentrantLock개념을 잘 섞어놓았다. 이해하기 쉽고 직감적이다. 


자바 처음 공부할 때는 아래 구문을 만났을 때, 특이하다고 생각했다.

jvm 관점이야. 아래 while 문이 특이하다고 생각했다.


while(조건문) {

  awit();

}



Guava의 Monitor의 개념은 좀 명확한 개념이 있다.


monitor.enter()

try {

    do_something();

} finally {

  monitor.leave();

}



if (monitor.enterIf(guard)) {

  try {

       do_something();

  } finally {

      monitor.leave();

  }

} else {

   do_anotherthing_without_monitor();

}




예제



import java.util.List;
import java.util.concurrent.TimeUnit;

import org.junit.Test;

import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Monitor;

public class GuavaTest {
List<String>
list = Lists.newArrayList();
Monitor
monitor = new Monitor();
Monitor.Guard
capacityGuard = new Monitor.Guard(monitor) {
@Override
public boolean isSatisfied() {
return (list.size() < 1);
}
};

@Test
public void monitorTest() throws InterruptedException {
monitor.enterWhen(capacityGuard);
try {
list.add("Samuel");
System.
out.println("Samuel");
}
finally {
monitor.leave();
}

if (monitor.enterWhen(capacityGuard, 100, TimeUnit.MILLISECONDS)) {
try {
list.add("Jason");
System.
out.println("Jason");
}
finally {
monitor.leave();
}
}
}
}




결과

Samuel


'general java' 카테고리의 다른 글

maven mvn:deploy 시 401 에러  (0) 2015.04.27
[Guava] EventBus 예제  (0) 2015.04.23
[Guava] Futures 예제  (0) 2015.04.17
[Guava] Preconditions 예제  (0) 2015.04.16
[Guava] CacheBuilder, CachLoader 예제  (0) 2015.04.15
Posted by '김용환'
,



Guava Futures와 관련 API


http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/util/concurrent/ListeningExecutorService.html


http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/util/concurrent/Futures.html


http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/util/concurrent/FutureCallback.html


Guava Futures는 Java Future에 비해서

Callback Listener를 사용할 수 있고, 성공/실패 여부를 받을 수 있다. 

(Mina나 Netty를 사용하는 장점을 알 수 있다.)




import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.junit.Test;

import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;

public class GuavaTest {

@Test
public void test_ExecutorService() {
System.
out.println("====java future===");
ExecutorService
executor = Executors.newFixedThreadPool(10);

Callable<String>
asyncTask = new Callable<String>() {
@Override
public String call() throws Exception {
Random
random = new Random();
int number = random.nextInt(5);
return "async : " + number;
}
};

Future<String>
future = executor.submit(asyncTask);

try {
String
result = future.get();
System.
out.println(result);
}
catch (ExecutionException e) {
e.printStackTrace();
}
catch (InterruptedException e) {
e.printStackTrace();
}

executor.shutdown();
}

@Test
public void test_guava_future() {
System.
out.println("===guava future===");

ListeningExecutorService
executor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));

Callable<String>
asyncTask = new Callable<String>() {
@Override
public String call() throws Exception {
Random
random = new Random();
int number = random.nextInt(5);
return "async : " + number;
}
};

ListenableFuture<String>
listenableFuture = executor.submit(asyncTask);

Futures.addCallback(
listenableFuture, new FutureCallback<String>() {
@Override
public void onSuccess(String result) {
System.
out.println("[success] : " + result);
}

@Override
public void onFailure(Throwable thrown) {
System.
out.println("[success] : " + thrown.getMessage());
}
});

try {
String
result = listenableFuture.get();
System.
out.println(result);
}
catch (ExecutionException e) {
e.printStackTrace();
}
catch (InterruptedException e) {
e.printStackTrace();
}

executor.shutdown();
}
}

결과

===guava future===

[success] : async : 1

async : 1

====java future===

async : 3



Futures 대신 ListenableFuture.addListener()를 활용할 수도 있다. 



@Test
public void test_guava_future_listener() {
System.
out.println("===guava future listener===");

ListeningExecutorService
executor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));

Callable<String>
asyncTask = new Callable<String>() {
@Override
public String call() throws Exception {
Random
random = new Random();
int number = random.nextInt(5);
return "async : " + number;
}
};

final ListenableFuture<String> listenableFuture = executor.submit(asyncTask);

listenableFuture.addListener(new Runnable() {
     
@Override
     
public void run() {
         
try {
             
final String contents = listenableFuture.get();
              System.
out.println(contents);
          }
catch (InterruptedException e) {
             
e.printStackTrace();
          }
catch (ExecutionException e) {
         
e.printStackTrace();
          }
      }
  },
executor);

executor.shutdown();

}



참조할만한 소스 

https://gist.github.com/bbejeck/1387892

'general java' 카테고리의 다른 글

[Guava] EventBus 예제  (0) 2015.04.23
[Guava] Monitor 예제  (0) 2015.04.21
[Guava] Preconditions 예제  (0) 2015.04.16
[Guava] CacheBuilder, CachLoader 예제  (0) 2015.04.15
[Guava] Collections2.filter  (0) 2015.04.14
Posted by '김용환'
,




Guava의 Preconditions는 java core의 assert() 보다 더 세련된 API이다. 

Spring 빈 생성 후 afterPropertiesSet() 에서 체크할 때 사용한다.


import org.apache.commons.lang3.StringUtils;
import org.junit.Before;
import org.junit.Test;

import com.google.common.base.Preconditions;
public class GuavaTest {

String
rootPath;

@Before
public void before() {
rootPath = "/root/service/";
}

@Test
public void simpleTest() {
Preconditions.checkNotNull(
rootPath, "rootPath should be configured");
Preconditions.checkArgument(!StringUtils.isEmpty(
rootPath), "rootPath is empty");
}
}


'general java' 카테고리의 다른 글

[Guava] Monitor 예제  (0) 2015.04.21
[Guava] Futures 예제  (0) 2015.04.17
[Guava] CacheBuilder, CachLoader 예제  (0) 2015.04.15
[Guava] Collections2.filter  (0) 2015.04.14
[Guava] Multimap 예제  (0) 2015.04.11
Posted by '김용환'
,


Guava의 CacheBuilder와 CacheLoader 예제이다. 




import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.apache.commons.collections.MapUtils;
import org.junit.Test;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;

public class GuavaTest {
CacheLoader<String, String>
loader = new CacheLoader<String, String>() {
@Override
public String load(String key) {
return key.toUpperCase();
}
};

@Test
public void simpleTest() {
System.
out.println("--- simple ---");
LoadingCache<String, String>
cache = CacheBuilder.newBuilder().build(loader);

cache.getUnchecked("hello");
cache.getUnchecked("world");

MapUtils.debugPrint(System.
out, "map", cache.asMap());
System.
out.println(cache.size());
}

@Test
public void sizeLimitTest() {
System.
out.println("--- size limit test ---");
LoadingCache<String, String>
cache = CacheBuilder.newBuilder().maximumSize(3).build(loader);

cache.getUnchecked("a");
cache.getUnchecked("b");
cache.getUnchecked("c");
cache.getUnchecked("d");

MapUtils.debugPrint(System.
out, "map", cache.asMap());
System.
out.println(cache.size());
System.
out.println(cache.getIfPresent("a")); // a는 evit 되었다.
System.
out.println(cache.getIfPresent("d"));
}

@Test
public void timeLimitTest() throws InterruptedException {
System.
out.println("--- time limit test ---");
LoadingCache<String, String>
cache = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.SECONDS).build(loader);

cache.getUnchecked("Samuel");
System.
out.println(cache.getIfPresent("Samuel"));
Thread.sleep(800);
System.
out.println(cache.getIfPresent("Samuel")); // a는 evit되었다.
}

@Test
public void preloadTest() {
System.
out.println("-- preload test ---");
Map<String, String>
map = new HashMap<String, String>();
   
map.put("samuel", "SAMUEL");
   
map.put("jason", "JASON");
    LoadingCache<String, String>
cache = CacheBuilder.newBuilder().build(loader);
   
cache.putAll(map);
    MapUtils.debugPrint(System.
out, "map", cache.asMap());
}

@Test
public void removeNotiTest() {
System.
out.println("-- remove test ---");
RemovalListener<String, String>
listener = new RemovalListener<String, String>() {
       
@Override
       
public void onRemoval(RemovalNotification<String, String> n){
           
if (n.wasEvicted()) {
                String
cause = n.getCause().name();
                System.
out.println("[removed] cause : " + cause + ", key : " + n.getKey());
            }
        }
    };

    LoadingCache<String, String>
cache = CacheBuilder.newBuilder().maximumSize(2).removalListener(listener).build(loader);

   
cache.getUnchecked("samuel");
   
cache.getUnchecked("jason");
   
cache.getUnchecked("kalley");
}

@Test
public void etc() {
// LoadingCache<String, String> cache = CacheBuilder.newBuilder().refreshAfterWrite(1,TimeUnit.MINUTES).build(loader);
// LoadingCache<String, String> cache = CacheBuilder.newBuilder().weakKeys().build(loader);
// LoadingCache<String, String> cache = CacheBuilder.newBuilder().softKeys().build(loader);
}
}




결과


--- size limit test ---

map = 

{

    c = C java.lang.String

    b = B java.lang.String

    d = D java.lang.String

} com.google.common.cache.LocalCache

3

null

D

--- simple ---

map = 

{

    hello = HELLO java.lang.String

    world = WORLD java.lang.String

} com.google.common.cache.LocalCache

2

-- preload test ---

map = 

{

    jason = JASON java.lang.String

    samuel = SAMUEL java.lang.String

} com.google.common.cache.LocalCache

--- time limit test ---

SAMUEL

SAMUEL

-- remove test ---

[removed] cause : SIZE, key : samuel


'general java' 카테고리의 다른 글

[Guava] Futures 예제  (0) 2015.04.17
[Guava] Preconditions 예제  (0) 2015.04.16
[Guava] Collections2.filter  (0) 2015.04.14
[Guava] Multimap 예제  (0) 2015.04.11
[Guava] 자주 사용하는 Lists api  (0) 2015.04.10
Posted by '김용환'
,



Guava의 Collections2 클래스에서 많이 사용되는 Collections2.filter() 예제이다.

http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Collections2.html


직원들 중 퇴사자는 빼고 나머지만 보여주는 예제이다. 

예제


import java.util.List;

import javax.annotation.Nullable;

import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.junit.Test;

import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;

public class GuavaTest {

@Test
public void test() {
ImmutableList<Member>
list = ImmutableList.<Member>builder()
.add(
new Member("Samuel", 1, false))
.add(
new Member("Jason", 2, true))
.add(
new Member("Daniel", 3, false))
.build();

List<Member>
filteredList = Lists.newArrayList(Collections2.filter(list, new Predicate<Member>() {
@Override
public boolean apply(@Nullable Member member) {
return !member.isResigned;
}
}));

System.
out.println(filteredList);
}
}


class Member {
String
name;
int id;
boolean isResigned;

public Member(String name, int id, boolean isResigned) {
this.name = name;
this.id = id;
this.isResigned = isResigned;
}

@Override
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
}




결과

[com.google.Member@74ee003d[name=Samuel,id=1,isResigned=false], com.google.Member@1d4455b3[name=Daniel,id=3,isResigned=false]]


Posted by '김용환'
,


api 문서

http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/Multimap.html


코드 라인을 줄여줄 수 있는 map중의 하나이다.  Multimap은 ListMultimap과 SetMultimap 뿐 아니라, 다양한 형태의 Multimap을 지원한다. 


Multimap은 키의 값을 Collection으로 저장하고 리턴한다. 따라서, key 충돌은 Collection인 value에 추가된다.



예제코드 #1)

import java.util.Collection;

import org.junit.Test;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;

public class GuavaTest {

@Test
public void test1() {
// Map<String,List<String>> map = new HashMap<String,List<String>>();
Multimap<String, String>
family = ArrayListMultimap.create();
family.put("Father Kimchy", "1st Son-Kimchy");
family.put("Father Kimchy", "1st Daughter-Kimchy");
family.put("Father Jason", "1st Son-Jason");

Collection<String>
child = family.get("Father Kimchy");
System.
out.println(child);

child = family.get("Father Jason");

System.out.println(child);

  }
}




결과

[1st Son-Kimchy, 1st Daughter-Kimchy]

[1st Son-Jason]




예제 코드 #2


   Multimap<Object, String> family = ArrayListMultimap.create();

  family.put(Lists.newArrayList(1), "1");

 

  System.out.println(family.get(Lists.newArrayList(1)));



결과는 


[1]

Posted by '김용환'
,



api 문서

http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/Lists.html



Lists에서 자주 사용하는 것은 생성할 때이다. 


Lists.newArrayList();

Lists.newLinkedList();

Lists.newArrayListWithExpectedSize(person.size());

List<byte[]> list = Lists.newArrayList(Bytes.toBytes("request"), Bytes.toBytes("init"));




Lists.partition()으로 partition(또는 paging) 처리를 할 수 있다. 
그리고, List.transform()으로 다른 타입을 갖는 List로 만들 수 있다. 

예제


import java.util.Arrays;
import java.util.List;

import org.junit.Test;

import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;

public class GuavaTest {

@Test
public void test1() {
List<Integer>
lists = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
List<List<Integer>>
pages = Lists.partition(lists, 3);
System.
out.println(pages);
}

@Test
 
public void test2() {
      ImmutableList<String>
numberString = new ImmutableList.Builder<String>()
                .add(
"11111")
                .add(
"22222")
                .add(
"33333")
                .add(
"44444")
                .build();

List<Integer>
result = Lists.transform(numberString, new Function<String, Integer>() {
@Override
public Integer apply(String value) {
return Integer.parseInt(value);
}
});

System.
out.println(result);
  }
}




결과


[[1, 2, 3], [4, 5, 6], [7]]

[11111, 22222, 33333, 44444]

Posted by '김용환'
,


ImmutableMap, ImmutableSet, ImmutableList 에서 자주 쓰는 예제이다. of() 또는 build()를 이용해서 만든다. 


예제

import java.util.List;
import java.util.Set;

import org.apache.commons.collections.MapUtils;
import org.junit.Test;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;

public class GuavaTest {

@Test
public void test() {
// ImmutableMap 예제
ImmutableMap<Integer, Integer>
mapOfIntegerAndInteger = ImmutableMap.of(20000024, 2, 20000026, 3, 20000052, 1, 20000059, 2, 20000063, 3);
MapUtils.debugPrint(System.
out, "test", mapOfIntegerAndInteger);

ImmutableMap<String, Integer>
mapOfStringAndInteger = ImmutableMap.of("Samuel", 2, "Jason", 3, "Kalley", 1, "Bono", 2, "Kelly", 3);
MapUtils.debugPrint(System.
out, "test1", mapOfStringAndInteger);

//너무 많은 map을 만들 것 같으면, builder를 사용한다.
ImmutableMap<String,String>
mapByBuilder = ImmutableMap.<String, String>builder()
    .put(
"key1", "value1")
    .put(
"key2", "value2")
    .build();
MapUtils.debugPrint(System.
out, "test2", mapByBuilder);

// ImmutableList 예제
List<String>
list = ImmutableList.of("Samuel", "Jason", "Kalley");
System.
out.println(list);

List<String>
listByBuilder = ImmutableList.<String>builder()
.add(
"Samuel")
.add(
"Hally")
.add(
"Brian")
.build();
System.
out.println(listByBuilder);

// ImmutableSet 예제
Set<String>
set = ImmutableSet.of("Samuel", "Jason", "Samuel");
System.
out.println(set);

Set<String>
setByBuilder = ImmutableSet.<String>builder()
.add(
"Samuel")
.add(
"Samuel")
.build();
System.
out.println(setByBuilder);
}

}



결과

test = 

{

    20000024 = 2 java.lang.Integer

    20000026 = 3 java.lang.Integer

    20000052 = 1 java.lang.Integer

    20000059 = 2 java.lang.Integer

    20000063 = 3 java.lang.Integer

} com.google.common.collect.RegularImmutableMap

test1 = 

{

    Samuel = 2 java.lang.Integer

    Jason = 3 java.lang.Integer

    Kalley = 1 java.lang.Integer

    Bono = 2 java.lang.Integer

    Kelly = 3 java.lang.Integer

} com.google.common.collect.RegularImmutableMap

test2 = 

{

    key1 = value1 java.lang.String

    key2 = value2 java.lang.String

} com.google.common.collect.RegularImmutableMap

[Samuel, Jason, Kalley]

[Samuel, Hally, Brian]

[Samuel, Jason]

[Samuel]



'general java' 카테고리의 다른 글

[Guava] Multimap 예제  (0) 2015.04.11
[Guava] 자주 사용하는 Lists api  (0) 2015.04.10
[Guava] Iterables.transform 과 removeIf 예제  (0) 2015.04.10
[Guava] Joiner와 Splitter 예제  (0) 2015.04.09
[Guava] 자주 사용하는 Maps api  (0) 2015.04.09
Posted by '김용환'
,


Guava의 Iterable 중, Iterable.transform()이 많이 사용된다.


http://google-collections.googlecode.com/svn/trunk/javadoc/com/google/common/collect/Iterables.html



List<Member>의 name을 List<String>을 얻는 방법이다. 


예제

import java.util.List;


import org.junit.Test;

import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;

public class IterableTest {

  @Test
  public void test() {

ImmutableList<Member>
member = new ImmutableList.Builder<Member>()
        .add(
new Member("Samuel", 1))
        .add(
new Member("Chacha", 2))
        .add(
new Member("Jason", 3))
        .add(
new Member("Kalley", 4))
        .build();

         List<String>
result = Lists.transform(member, new Function<Member, String>() {
             
@Override
public String apply(Member person) {
                 
return person.name;
             }
         });

          System.
out.println(result);
  }
}


class Member {
  String
name;
 
int id;
 
public Member (String name, int id) {
 
this.name = name;
 
this.id = id;
  }
}


결과

[Samuel, Chacha, Jason, Kalley]



두번째로 잘 사용하는 메소드는 Iterables.removeIf()으로, 자주 사용하는 편이다. collection에서 특정 조건(predicate)에 걸리면 삭제하도록 한다. 


import java.util.List;

import org.junit.Test;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

public class IterableTest {

@Test
public void test() {
List<String>
numbers = Lists.newArrayList();
numbers.add("1111");
numbers.add("2222");
numbers.add("3333");
numbers.add("0000");

// 0으로 나타나는 String은 제외.
boolean result = Iterables.removeIf(numbers, new Predicate<String>() {
@Override
public boolean apply(String input) {
int inputNumber = Integer.parseInt(input);
if (inputNumber == 0) {
return true;
}
return false;
}

});

    System.
out.println(result);
    System.
out.println(numbers);
}
}


결과

true

[1111, 2222, 3333]




참고로, transform() 메소드를 포함하는 Guava 클래스는 Lists와 Collections이다. 특이하게 Sets는 없다. (만들어질 때부터 의도와 관련된 정책적인 판단이 있다. https://github.com/google/guava/issues/219) ImmutableSet.copyof()를 쓰도록 권고하고 있다.


다음 예제는 Lists.transform()을 이용하여 <String> 타입을 List<Integer>로 변환하는 예제이다.




import java.util.List;

import org.junit.Test;

import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;

public class IterableTest {

@Test
public void test() {
    ImmutableList<String>
numberString = new ImmutableList.Builder<String>()
                .add(
"11111")
                .add(
"22222")
                .add(
"33333")
                .add(
"44444")
                .build();

            List<Integer>
result = Lists.transform(numberString,
                   new Function<String, Integer>() {
                          
@Override
  
public Integer apply(String value) {
                                
return Integer.parseInt(value);
                          }
            });

            System.
out.println(result);
}
}



결과

[11111, 22222, 33333, 44444]







Posted by '김용환'
,