spring과 netty

이번엔 spring을 이용해서 netty의 서버를 만들어보자

내용은 비슷하다.
기존에 했던거와 비슷하다. 스프링의 설정만 사용했다.
main부터 보자

  public static void main(String[] args) {
    try(AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class)){
      context.registerShutdownHook();
      NettyServer nettyServer = context.getBean(NettyServer.class);
      nettyServer.start();
    }
  }

다들 아시다 시피 AnnotationConfigApplicationContext 에 SpringConfig을 설정했다.
다음 으로 SpringConfig를 보자

@Configuration
@ComponentScan("me.wonwoo.spring")
@PropertySource("classpath:server.properties")
@Getter
public class SpringConfig {

  @Value("${tcp.port}")
  private int port;

  @Bean
  public InetSocketAddress tcpPort() {
    return new InetSocketAddress(port);
  }

  @Bean
  public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
    return new PropertySourcesPlaceholderConfigurer();
  }
}

이거 역시 스프링을 할 줄 아는 사람이라면 다 아는 내용이다.
젠장 딱히 할 얘기가 없네..
다음으로는 NettyServer를 보자 실제 netty의 서버를 올리는 곳이다.

@Component
public class NettyServer {

  @Value("${boss.thread.count}")
  private int bossCount;

  @Value("${worker.thread.count}")
  private int workerCount;

  @Autowired
  private InetSocketAddress port;

  private static final ServiceHandler SERVICE_HANDLER = new ServiceHandler();

  public void start() {
    EventLoopGroup bossGroup = new NioEventLoopGroup(bossCount);
    EventLoopGroup workerGroup = new NioEventLoopGroup(workerCount);

    try {
      ServerBootstrap b = new ServerBootstrap();
      b.group(bossGroup, workerGroup)
        .channel(NioServerSocketChannel.class)
        .childHandler(new ChannelInitializer<SocketChannel>() {
          @Override
          protected void initChannel(SocketChannel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
            pipeline.addLast(SERVICE_HANDLER);
          }
        });

      ChannelFuture channelFuture = b.bind(port).sync();
      channelFuture.channel().closeFuture().sync();
    } catch (InterruptedException e) {
      e.printStackTrace();
    } finally {
      bossGroup.shutdownGracefully();
      workerGroup.shutdownGracefully();
    }
  }
}

여기 역시 할얘기가 딱히 없다. NettyServer를 스프링의 빈으로 설정 했다.
그래야 Context에 저 아이가 있다.
다음으로는 handler를 보자

@ChannelHandler.Sharable
public class ServiceHandler extends ChannelInboundHandlerAdapter {

  Logger logger = LoggerFactory.getLogger(this.getClass());

  private final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

  @Override
  public void channelActive(ChannelHandlerContext ctx) throws Exception {
    channels.add(ctx.channel());
  }

  @Override
  public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    ByteBuf byteBuf = (ByteBuf) msg;
    logger.debug("message : {} ",byteBuf.toString(Charset.defaultCharset()));
    channels.writeAndFlush(msg);
  }
}

이제부터는 완전 netty다
우리가 이제껏 배워온 네티다.
한번 텔넷으로 접속을 해보자. 채팅을 할 수 있게 만들었다. 에코서버는 너무 식상하니까.
아마두 잘 돌아 갈 것으로 예상된다.
spring과 함께 API서버도 만들 수 있다.
혹은 어떤 사람이 Spring Boot에 내장 서버로 Netty를 구현한 것도 있다.
https://github.com/DanielThomas/spring-boot-starter-netty.git

이것으로 netty는 모두 마치겠다.
vertx도 해야되는데..
흠흠

spring swagger-ui

spring swagger-ui

swagger-ui는 테스트? 혹은 문서? 가 있는 UI를 제공 해준다.
한번 살펴 보자
maven에 다음과 같이 추가 하자

... 

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.4.0</version>
</dependency>

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.4.0</version>
</dependency>

...

swagger 설정을 해주자

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket restApi() {
        // @formatter:off
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                .build()
                .useDefaultResponseMessages(false)
                .globalResponseMessage(RequestMethod.GET,
                        Arrays.asList(
                                new ResponseMessageBuilder()
                                        .code(500)
                                        .message("server error")
                                        .responseModel(
                                                new ModelRef("Error")
                                        ).build()
                        )
                );
        // @formatter:on
    }

    private ApiInfo apiInfo() {
        // @formatter:off
        return new ApiInfoBuilder()
                .title("Spring boot Swagger")
                .description("api list")
                .contact(new Contact("wonwoo", "wonwoo.ml", "test@test.com"))
                .version("1.0.0")
                .build();
        // @formatter:on
    }
}

별거 없다. 상태 코드 500은 전부 적용을 한다는 뜻이다.
그리고 제목, 내용, 연락처, 버전 등 입력 할 수 있다.

그런다음에 controller class에 다음과 같이 어노테이션을 추가 하자

...

@Api(description = "사용자 API")
public class AccountController {
....
}

...

속성으론 value, tags, hidden 등 여러가지가 있다.

그리고 해당 메소드에 다음과 같이 추가하자.

...

@ApiOperation(value = "사용자 리스트", notes = "사용자 리스트를 가져옵니다.")
@RequestMapping(value = "/accounts", method = RequestMethod.GET, headers = "Accept=application/json")
public List<Account> getAccounts() throws JsonProcessingException {
    List<Account> accounts = accountRepository.findAll();
    return accounts;
}
...

해당 API 의 제목 및 내용이다.
그리고 해당 프로퍼티 명을 지정해 줄 수 있다.

...

@ApiModelProperty(value = "사용자 아이디")
private Long id;

...

또한 감출수도 있다.

...

@ApiModelProperty(hidden = true)
private List<Ordered> ordered;

...

완료 되었다.
한번 UI 에들어 가서 확인해보자
http://localhost:8080/swagger-ui.html

spring data jpa 의 jsonfilter

spring data jpa 의 jsonfilter

이번엔 spring data jpa의 json 으로 보내기위한 방법을 한개더 포스팅 하겠다.
jsonfilter 라는 어노테이션을 사용하여 해보자

소스는 dto와 비슷하다.
다른 부분만 올려서 설명하겠다.
jsonfilter는 jackson 라이브러리의 어노테이션이다.
일단 entity에 jsonfilter를 추가하자

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonFilter("accountFilter")
public class Account {
    @Id
    @GeneratedValue
    @Column(name = "account_id")
    private Long id;

    @NotNull
    private String name;

    @OneToMany(mappedBy = "account")
    private List<Ordered> ordered;
}

기존 소스와 동일하지만 JsonFilter 어노테이션만 추가 하였다.
작업 할 것이 한개더 있다.

    @RequestMapping(value = "/accounts", method = RequestMethod.GET, headers = "Accept=application/json")
    public String getAccounts() throws JsonProcessingException {
        List<Account> accounts = accountRepository.findAll();
        ObjectMapper mapper = new ObjectMapper();
        FilterProvider filters = new SimpleFilterProvider().addFilter("accountFilter",
                SimpleBeanPropertyFilter.filterOutAllExcept("id", "name"));
        String accountsStr = mapper.setFilterProvider(filters).writeValueAsString(accounts);
        return accountsStr;
    }

    @RequestMapping(value = "/account/{id}", method = RequestMethod.GET, headers = "Accept=application/json")
    public String getAccount(@PathVariable Long id) throws IOException {
        Account account = accountRepository.findOne(id);
        ObjectMapper mapper = new ObjectMapper();
        FilterProvider filters = new SimpleFilterProvider().addFilter("accountFilter",
                SimpleBeanPropertyFilter.filterOutAllExcept("id", "name"));
        String accountStr = mapper.setFilterProvider(filters).writeValueAsString(account);
        return accountStr;
    }

ObjectMapper에 filter를 적용해야 된다. entity에 추가 했던 filter 값을 넣으면 된다.
SimpleBeanPropertyFilter.filterOutAllExcept 메소드는 추가할 property명만 적어주면 된다.
SimpleBeanPropertyFilter.serializeAllExcept 메소드는 제외 시킬 property명을 적어주면 된다.

어떤것이 좋을까 고민중
바로 다음에 @JsonView를 사용해서 해보자!