728x90

많은 참고 바란다! 

https://blakes-organization.gitbook.io/springboot2/

 

권장하는 프로젝트 폴더구조 - Springboot2@가이드북

application main class:Application.java,해당 class는 root packag 하위에 위치,일반적으로 해당 class에서 componet scan 설정을 한다.root package 하위에 위치함으로서 수동설정을 줄이면서Spring 필요한것들도 정

blakes-organization.gitbook.io

보기 불편해 하는 분?들을 위하여 gitboot 내용을 하나씩 티스토리에도 이관중이다. 다들 티스토리 나 네이버블로그에서 타 블로그 플랫폼으로 넘어간다고 하는데 난 뭐하려고 이런 뻘짓을 하는지...

'Springboot2.x 강좌' 카테고리의 다른 글

민감한 정보에 대한 암호화  (0) 2023.01.03
728x90

1.group_concat

예: name을 기준으로 그룹핑 시 ...

select name from `user`
group by name;
 
하지만 이름이 같은 놈들을 다시 묶고 싶을때. 그리고 " , " 로 구분하고 싶을때
select name,group_concat(code) from `user`
group by name;

2.char_length

예: 특정문자열 길이를 가져와서 다시 정렬하고 싶으면...

select * from brand where name like '%석삼%' 
order by char_length(name) asc limit 5;
 
name 으로 like 조회후 char_length 로 name 길이를 얻고 , 그걸 다시 오름차순으로...

3.locate

select * from brand where name like '%석삼%' 
order by char_length(name) asc, locate('석삼',name) asc limit 5,5;
 
4.replace
 
예: A를 B로 치환 
update brand set name=REPLACE(name,'A','B') 
where id=1;
 
 
앞뒤 공백을 없애기도 가능.
update brand set name=REPLACE(name,' ','') where name like ' %';
update brand set name=REPLACE(name,' ','') where name like '% ';
 

5.now

select now() from brand limit 1;
 
밀리세컨드를 리턴받고 싶을때.
select now(3) from brand limit 1;
 

6.insert into ... select

실무에서 가끔 데이터 insert가 필요하다 . 일반적으로 아래와 같이 하겠지?

INSERT INTO `brand`(`id`, `code`, `name`, `edit_date`) 
VALUES (5, '108', '석삼', '2022-12-21 19:42:21');
 
일반적인 소량의 데이터를 insert시는 문제 없지만 타 table에서 데이터를 읽어서 insert 하는 경우는 아래와 같이 작성한다.
INSERT INTO `brand`(`id`, `code`, `name`, `edit_date`) 
select null,code,name,now(3) from `order` where code in ('004','005');
 
이처럼 order 테이블의 데이터를 쉽게 brand 테이블에 넣을수 있다.
 

7.insert into ... ignore

예: 1000개의 데이터 입력 전에 name이 있을시 insert  안하고, 없을시에 만 데이터를 insert 하는 작업을 어떻게 수행할까?
INSERT INTO `brand`(`id`, `code`, `name`, `edit_date`) 
VALUES (123, '108', '석삼', now(3));
 
이렇게 하면? 당연이 안되겠지? 왜냐면 name , brand 모두 unique 로 설정되었기 때문(뭐 가설이니까 이해해주라! ^^).
그래서 많은 친구분들은 not exists 문법을 써서 이런 문제를 해결하겠지.
INSERT INTO `brand`(`id`, `code`, `name`, `edit_date`) 
select null,'108', '석삼',now(3) 
from dual where  not exists (select * from `brand` where name='석삼');
 
뭐 괜찮은 방법이긴 하다만 이것보다 더 쉽게 작성하는 문법이 있다. 즉 insert into ... ignore 를 사용하면 된다.
INSERT ignore INTO `brand`(`id`, `code`, `name`, `edit_date`) 
VALUES (123, '108', '석삼', now(3));
 
error도 없고 exception 을 무시하므로 sql 수행에 영향끼친 행수는 0개, 그리고 중복입력도 안되고 꿀이다! 

8.select ... for update

begin;
select * from `user` where id=1 
for update;
// .....
// 업무로직 처리
// .....
update `user` set score=score-1 where id=1;
commit;
 

9.on duplicate key update

예: 7번과 비슷한 상황 ,입력하기전 조회를 하고 해당 컬럼에 데이터가 있으면 pass, 없으면 insert 하는 상황이 이다.

병렬환경이 아니라면 위 문제7 과 동일하게 처리할수 있지만 병렬처리 상황에서는 데이터 중복 상황이 발생한다. 물론 중복데이터 입력 방지를 위한 여러가지 방법이 있다. unique index 를 추가한다던지...

이때 사용할수 있는게 바로 on duplicate key updat 이다.

insert 전에 PK 혹은 unique index 있는지를 판단하는데 , 없으면 insert 하고 있으면 update 한다. 

INSERT  INTO `brand`(`id`, `code`, `name`, `edit_date`) 
VALUES (123, '108', '석삼', now(3))
on duplicate key update name='석삼',edit_date=now(3);
 
이 쿼리로 쉽게 중복데이터도 없이 , 데이터를 갱신하는 좋은 방법이다.

병렬환경에서 on duplicate key update 사용시 dead lock 상황이 발생할수 있니 실제 상황에 맞게 주의하여 사용하여야 한다.

10.show create table

특정 테이블의 스키마 정보를 보고 싶을때 자주 사용하는 desc 한다.
desc `order`;
테블의 스키마명,사이즈,타입,null 허용여부, pk , defalut 등등 정보들.
하지만 index 관련 정보는 보이지 않는다. 이때 사용하는 명령;
show index from `order`;
하지만 보기 불편한다 ? 그러면 아래 명령을 사용해 보라;
show create table `order`;
 

11.create table ... select

임시테이블 order_2022121819 생성,order 테이블과 동일한 , 빈 테이블을 생성한다.
create table order_2022121819 like `order`;
 
order 데이터를 생성된 임시 테이블 order_2022121819 에 넣는다.
 
insert into order_2022121819 select * from `order`;
 
 
부분 테이블의 백업시 유용하게 사용한다. (내기준)
 

12.explain

뭐 이건 다 알겠지? 쿼리 분석시....

explain select * from `order` where code='002';
 

13.show processlist

쿼리수행후 너무 속도가 느리다(진중라면)  해당명령어로 진행과정을 살펴볼수 있다.

mysql> show processlist;
+---------+------+-----------+-------+---------+------+-------+------------------+
| Id      | User | Host      | db    | Command | Time | State | Info             |
+---------+------+-----------+-------+---------+------+-------+------------------+
| 2212724 | root | localhost | test  | Sleep   | 70   |       | NULL             |
| 2212286 | root | localhost | NULL  | Query   | 0    | NULL  | show processlist |
+---------+------+-----------+-------+---------+------+-------+------------------+
2 rows in set (0.00 sec)

프로세스를 찾았으니 죽여야지?

mysql> kill 2212724;
Query OK, 0 rows affected (0.00 sec)
mysql> show processlist;
+---------+------+-----------+-------+---------+------+-------+------------------+
| Id      | User | Host      | db    | Command | Time | State | Info             |
+---------+------+-----------+-------+---------+------+-------+------------------+
| 2212286 | root | localhost | NULL  | Query   | 0    | NULL  | show processlist |
+---------+------+-----------+-------+---------+------+-------+------------------+
2 rows in set (0.00 sec)
  1. Id    : Process id ,MySQL 이 관리하는 thread 번호
  2. User  : Thread에 접속하고 있는 MySQL 사용자
  3. Host  : 사용자가 접속하고 있는 호스트명 , IP 주소
  4. Command : Thread의 현재 command 상태 .
  5. Time  : Process가 현재 command상태에서 동작 시간
  6. State : Thread의 상태에 대해 사람이 읽을 수 있는 형태의 정보
    Info  : 현 실행되고 있는 SQL ."SHOW PROCESSLIST" 최대 100자까지 표시전부 표시하려면 "SHOW FULL PROCESSLIST"

14.mysqldump

mysqldump -h 172.0.0.1 -u root -proot dbname > backup.sql​
 

mysqldump -h{ip} -P{port} -u{user} -p{password} {parameter1},{parameter2}.... > {document}.sql

뭐 너무 유명한? 명령어라 어느정도 개발?운영?서버 만져본 분이라면 다 아는 ...

이글의 타이틀과 약간 어긋난 놈이긴한데 일단 적어본다.

 

끝!

728x90

Springboot 으로 email 전송기능을 구현 하는 방법에는 다양하게 존재한다고 한다.

오늘은 내가 알고 있는 방법으로 글을 써볼까한다.

쓰레드 환경에서도 많은 사람한테 보낼수 있는 기능이라고 가져다가 자기 환경에 맞게 조금 수정하면 잘 써질듯하다.

controller 에서 responseEntity 하는 부분은 대충 써놔서 수정이 조금 필요한듯 싶다.

그럼 코드 go.go.go

pom.xml 에 추가 

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

application.yml 파일 작성

server:
  port: 8080

spring:
  mail:
    host: smtp.gmail.com
    username: your_google_account@gmail.com
    password: mxmiujqrxzgfabcd
    default-encoding: UTF-8
    properties:
      mail:
        smtp:
          auth: false
          starttls:
            enable: true
            required: true

 

멀티스레드 풀 관련 config 작성

@EnableAsync
@Configuration
public class ExecutorConfig extends AsyncConfigurerSupport {

    /**
     * 스레드풀 사이즈 설정
     **/
    private int corePoolSize = 4;
    /**
     * 스레드풀 초대 사이즈 설정
     **/
    private int maxPoolSize = 16;
    /**
     * 스레드풀 큐 사이즈
     **/
    private int queueCapacity = 10;
    /**
     * 스레드 이름 접두사
     */
    private String threadNamePrefix = "EmailAsyncExecutor-";

    @Bean("EmailAsync")
    public ThreadPoolTaskExecutor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setThreadNamePrefix(threadNamePrefix);


        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

        executor.setWaitForTasksToCompleteOnShutdown(true);

        executor.setAwaitTerminationSeconds(60);

        executor.initialize();

        return executor;
    }

}

Email 객체 생성

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Builder
public class EmailModel implements Serializable {

	/**
	 * 초기화
	 */
	private static final long serialVersionUID = 1404185636399251685L;

	/**
	 * 수신자
	 */
	private String email;
	/**
	 * 메일 제목
	 */
	private String subject;
	/**
	 * 메일 내용
	 */
	private String content;
	/**
	 * 첨부파일 위치
	 */
	private String attachFilePath;
	/**
	 * 메일 resource 위치
	 */
	private String resourcePath;
	/**
	 * 메일 resource 이름
	 */
	private String resourceName;

}

Email Service 작성

package com.example.demo.service.impl;

import com.example.demo.model.EmailModel;
import com.example.demo.service.MailService;
import lombok.RequiredArgsConstructor;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;


@Service
@RequiredArgsConstructor
public class MailServiceImpl implements MailService {


    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(MailServiceImpl.class);
    private final JavaMailSender mailSender;
    @Value("${spring.mail.username}")
    private String from;

    @Async("EmailAsync")
    @Override
    public Boolean sendSimpleMail(EmailModel emailModel) {

        try {
            SimpleMailMessage message = new SimpleMailMessage();
            message.setFrom(from);// 보내는 사람, 설정 하지 않으면 기본 값 .properties 값이 셋팅된다.
            message.setTo(emailModel.getEmail().split(";"));//수신자  1명 혹은 여러명일때 ";" 로 분리
            message.setSubject(emailModel.getSubject());// 제목
            message.setText(emailModel.getContent());// 본문

            mailSender.send(message);

            logger.info("단순 문자전자 메일전송 성공!");
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("단순 문자전자 메일전송 실패!");
            return false;
        }
        return true;
    }

    @Async("EmailAsync")
    @Override
    public Boolean sendHtmlMail(EmailModel emailModel) {

        String to = emailModel.getEmail();
        String subject = emailModel.getSubject();
        String content = emailModel.getContent();
        logger.info("HTML Template 메일전송 시작:{},{},{}", to, subject, content);
        //MimeMessage,MIME 프로토콜 사용
        MimeMessage message = mailSender.createMimeMessage();

        MimeMessageHelper helper;
        //MimeMessageHelper 더욱 많은 내용들을 설정 할수 있다.
        try {
            helper = new MimeMessageHelper(message, true);
            helper.setFrom(from);
            helper.setTo(to.split(";"));
            helper.setSubject(subject);
            helper.setText(content, true);// ture 일시 html 지원
            mailSender.send(message);
            logger.info("HTML Template 메일전송 성공!");
        } catch (MessagingException e) {
            logger.error("HTML Template 메일전송 실패!", e);
            return false;
        }
        return true;
    }

    @Async("EmailAsync")
    @Override
    public Boolean sendAttachmentMail(EmailModel emailModel) {


        String to = emailModel.getEmail();
        String subject = emailModel.getSubject();
        String content = emailModel.getContent();
        String filePath = emailModel.getAttachFilePath();
        logger.info("첨부파일 메일전송 시작:{},{},{},{}", to, subject, content, filePath);
        MimeMessage message = mailSender.createMimeMessage();

        MimeMessageHelper helper;
        try {
            helper = new MimeMessageHelper(message, true); //true 파일 여러개라는 뜻
            helper.setFrom(from);
            helper.setTo(to.split(";"));
            helper.setSubject(subject);
            helper.setText(content, true);
            FileSystemResource file = new FileSystemResource(new File(filePath));
            String fileName = file.getFilename();
            helper.addAttachment(fileName, file);//添加附件,可多次调用该方法添加多个附件
            mailSender.send(message);
            logger.info("첨부파일 메일전송 성공!");
        } catch (MessagingException e) {
            logger.error("첨부파일 메일전송 실패!", e);
            return false;
        }
        return true;
    }

    @Async("EmailAsync")
    @Override
    public Boolean sendInlineResourceMail(EmailModel emailModel) {

        String to = emailModel.getEmail();
        String subject = emailModel.getSubject();
        String content = emailModel.getContent();
        String resourcePath = emailModel.getResourcePath();
        String resourceName = emailModel.getResourceName();
        logger.info("첨부파일 메일전송 시작:{},{},{},{},{}", to, subject, content, resourcePath, resourceName);
        MimeMessage message = mailSender.createMimeMessage();

        MimeMessageHelper helper;
        try {
            helper = new MimeMessageHelper(message, true);
            helper.setFrom(from);
            helper.setTo(to.split(";"));
            helper.setSubject(subject);
            helper.setText(content, true);
            // 절대경로를 읽는다
            FileSystemResource res = new FileSystemResource(new File(resourcePath));
            helper.addInline(resourceName, res);//파일여러개 일때  여러번 사용가능
            mailSender.send(message);
            logger.info("첨부파일 메일전송 성공!");
        } catch (MessagingException e) {
            logger.error("첨부파일 메일전송 실패!", e);
            return false;
        }
        return true;
    }

    @Async("EmailAsync")
    @Override
    public Boolean sendHtmlImageMail(EmailModel emailModel) {

        String to = emailModel.getEmail();
        String subject = emailModel.getSubject();
        String content = emailModel.getContent();
        String resourcePath = emailModel.getResourcePath();
        logger.info("첨부파일 메일전송 시작:{},{},{},{}", to, subject, content, resourcePath);
        MimeMessage message = mailSender.createMimeMessage();

        MimeMessageHelper helper;
        try {
            helper = new MimeMessageHelper(message, true);
            helper.setFrom(from);
            helper.setTo(to.split(";"));
            helper.setSubject(subject);
            helper.setText(content, true);

            helper.setText("<html><head></head><body><h1>hello world!</h1>" + "<img src=\"cid:aaa\"/></body></html>", true);
            FileSystemResource img = new FileSystemResource(new File(resourcePath));

            helper.addInline("aaa", img);

            mailSender.send(message);
            logger.info("첨부파일 메일전송 성공!");
            return true;

        } catch (MessagingException e) {
            logger.error("첨부파일 메일전송 실패!", e);
            return false;

        }
    }
}

서비스를 호출할 Controller 작성

@RestController
@RequiredArgsConstructor
@RequestMapping(value = "/mail")
public class MailController {


    private final MailService mailService;


    @PostMapping("/test")
    public ResponseEntity sendSimpleMail(String toEmail) {
        mailService.sendSimpleMail(EmailModel
                .builder()
                .email(toEmail)
                .subject("test")
                .content("일단 아무거나 테스트")
                .build());
        return ResponseEntity.ok().build();
    }


    @PostMapping("/simple")
    public ResponseEntity sendSimpleMail(@RequestBody EmailModel emailModel) {

        mailService.sendSimpleMail(emailModel);
        return ResponseEntity.ok().build();
    }


    @PostMapping("/html")
    public ResponseEntity sendHtmlMail(@RequestBody EmailModel emailModel) {

        mailService.sendHtmlMail(emailModel);
        return ResponseEntity.ok().build();
    }


    @PostMapping("/attach")
    public ResponseEntity sendAttachmentMail(@RequestBody EmailModel emailModel) {

        mailService.sendAttachmentMail(emailModel);
        return ResponseEntity.ok().build();
    }


    @PostMapping("/resource")
    public ResponseEntity sendInlineResourceMail(@RequestBody EmailModel emailModel) {

        mailService.sendInlineResourceMail(emailModel);
        return ResponseEntity.ok().build();
    }


    @PostMapping("/image")
    public ResponseEntity sendHtmlImageMail(@RequestBody EmailModel emailModel) {

        mailService.sendHtmlImageMail(emailModel);
        return ResponseEntity.ok().build();
    }


}

POSTMAN 으로 이메일 테스트 해볼게 

네이버에서 메일에서 확인

트러블슈팅

아래와 같이 에러가 나온다.

구글 보안 문제로 구글패스워드 아닌 앱 키를 발급받아서 적용해야 된다. 다른 편번으로 임시로 테스트하는 방법도 있지만 보안문제로 그냥 이대로 쓰는거 편하다.

setp1:

setp2:

mxmiujqrxzgfabcd

이런 유형의 문자열을 복사해서 yml 파일 설정 항목중 password 해당부분에 넣으면 된다.

 

게으른 당신을 위한 소스코드 https://github.com/luxury515/luxury515-springboot-email-thread

 

이상끝!

728x90

우리가 Spring하면 IOC(제어반전)와 AOP(절단면 지향 프로그래밍)가 가장 먼저 떠오를 수 있다.

스프링의 base 이고 훌륭한 디자인 덕분에 스프링이 많은 framework들 중에서도 남다른 인기를 장기간 이어 왔었다.

그 외에도 spring을 사용시 유연한 확장력장점 때문에 spring은 스펀지마냥 또다른 제3자 애플리케이션을 품?에 쉽게 끌어안을수 있는것이다. rocketmq, mybatis, redis 등등...

이번 글에서는  spring 에서 가장 많이 쓰이는 11가지 확장점에 대해 알아보자.


1. 커스텀 인터셉터(Interceptor)


SpringMVC interceptor 는 Spring interceptor에 비해 HttpServletRequest, HttpServletResponse 등 웹 객체 인스턴스를 가져올 수 있다. Spring mvc interceptor의  최상위 인터페이스는 HandlerInterceptor 이며 내무 함수는 아래와 같다.

  • preHandle 타겟 메서드가 실행되기 전에
  • postHandle 타겟 메서드를 실행한 후
  • afterCompletion 요청을 완료할 때 실행.

HandlerInterceptorAdapter 추상 클래스를 오버라이딩하여 구현 . 권한 인증, 로그, 통계구현 등 케이스에서 interceptor를 사용해볼만하다.

  • step1: HandlerInterceptorAdapter 정의 
public class AuthInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        String requestUrl = request.getRequestURI();
        if (checkAuth(requestUrl)) {
            return true;
        }

        return false;
    }

    private boolean checkAuth(String requestUrl) {
        System.out.println("권한인증");
        return true;
    }
}
  • step2:  정의된 Interceptor 를 빈에 등록
@Configuration
public class WebAuthConfig extends WebMvcConfigurerAdapter {
 
    @Bean
    public AuthInterceptor getAuthInterceptor() {
        return new AuthInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthInterceptor());
    }
}

2. 빈객체 얻는 3가지

BeanFactoryAware 사용하기 

@Service
public class PersonService implements BeanFactoryAware {
    private BeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    public void add() {
        Person person = (Person) beanFactory.getBean("person");
    }
}

ApplicationContextAwar 사용하기 

@Service
public class PersonService2 implements ApplicationContextAware {
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public void add() {
        Person person = (Person) applicationContext.getBean("person");
    }
}

ApplicationListener 사용하기

@Service
public class PersonService3 implements ApplicationListener<ContextRefreshedEvent> {
    private ApplicationContext applicationContext;
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        applicationContext = event.getApplicationContext();
    }

    public void add() {
        Person person = (Person) applicationContext.getBean("person");
    }
}

3. global Exception 처리


아래의 심플한 코드가 있다.

@RequestMapping("/test")
@RestController
public class TestController {

    @GetMapping("/add")
    public String add() {
        int a = 10 / 0;
        return "성공";
    }
}

성공여부에 따른 분기처리를 아래와같이 자주 한다.

@GetMapping("/add")
public String add() {
    String result = "성공";
    try {
        int a = 10 / 0;
    } catch (Exception e) {
        result = "data exception!";
    }
    return result;
}

 

 

뭐 이거 하나만 처리한다고 치면 괜찮은 코드인듯 싶다. 하지만 controller가 수십개, 수백게 되면 이런 처리를 일일해주기는에는 너무 큰 노가다 일뿐다. Spring에서 기막한 어노테이션을 지원한다. 바로 RestControllerAdvice 이다. 이름만 봐도 어느정도 예측가능하다. 아래 코드를 보자.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public String handleException(Exception e) {
        if (e instanceof ArithmeticException) {
            return "data Exception!";
        }
        if (e instanceof Exception) {
            return "Service Intenerl Error!";
        }
        retur nnull;
    }
}

처음 이 기능을 보고 1주일 변비가 뻥뚤린 기분이였다.

 

4. 클래스 유형 전환

Spring 은 3가지 타입전환방식이 있다.

Converter<S,T>:S 를 T로 전환.

ConverterFactory<S, R>:S를 R의 자식클래스로 전환

GenericConverter:정교한 Converter구현이 필요한 경우, GenericConverter 사용한다.

step1: 샘플 domain 생성

@Data
public class User {

    private Long id;
    private String name;
    private Date registerDate;
}

step2: 구현

public class DateConverter implements Converter<String, Date> {

    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Override
    public Date convert(String source) {
        if (source != null && !"".equals(source)) {
            try {
                simpleDateFormat.parse(source);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

step3: 정의된 클래스 전화객체를 빈에 등록한다.

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new DateConverter());
    }
}

step4: 사용해보기 

@RequestMapping("/user")
@RestController
public class UserController {

    @RequestMapping("/save")
    public String save(@RequestBody User user) {
        return "success";
    }
}

5. Config import 하기

5.1 일반클래스

public class A {
}

@Import(A.class)
@Configuration
public class TestConfiguration {
}

@Import 로 A 클래스를 주입하면 , spring은 자동으로 A클래스에 대하여 객체를 생성할것이고. 따라서 @Autowired 를 이용하여 주입하면 된다.

@Autowired
private A a;

어 ? @Bean 하지 않고도 객체 생성 가능하네...

5.2 config 클래스

해당 import 방식은 제일 복잡하다. 왜냐면 @Configuration 주입은 기타 다른 주입방식을 겹쳐서 사용하는걸 지원하기 때문이다. 아래처럼 말이다.

public class A {
}

public class B {
}

@Import(B.class)
@Configuration
public class AConfiguration {

    @Bean
    public A a() {
        return new A();
    }
}

@Import(AConfiguration.class)
@Configuration
public class TestConfiguration {
}

5.3 ImportSelector

해당방식은 ImportSelector 인터페이스를 구현해야 한다.

public class AImportSelector implements ImportSelector {

private static final String CLASS_NAME = "com.sue.cache.service.test13.A";
    
 public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{CLASS_NAME};
    }
}

@Import(AImportSelector.class)
@Configuration
public class TestConfiguration {
}

이 방식의 장점은 리턴이 배열로 되어 있어 여러개를 동시에 import 할수 있다는것.

5.4 ImportBeanDefinitionRegistrar

해당방식은 ImportBeanDefinitionRegistrar 인터페이스를 구현한다.

public class AImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(A.class);
        registry.registerBeanDefinition("a", rootBeanDefinition);
    }
}

@Import(AImportBeanDefinitionRegistrar.class)
@Configuration
public class TestConfiguration {
}

registerBeanDefinitions 에서 BeanDefinitionRegistry 의 등록객체를 얻을수 있다. 때문에 BeanDefinition 수동으로 생성 & 등록을 조작할수 있다.


6. 프로젝트 실행시

일반적으로 우리는 그냥 기본 실행을 하지만 필요시 해당 프로젝트의 설정대로 시작해야 하는 경도 있다 . 예를 들ㅇ면 시스템설정,로컬캐쉬 , 초기화 등 설정을 해야 하는 경우도 있다. Spring은 CommandLineRunner 과 ApplicationRunner 를 제공한다.

@Component
public class TestRunner implements ApplicationRunner {

    @Autowired
    private LoadDataService loadDataService;

    public void run(ApplicationArguments args) throws Exception {
        loadDataService.load();
    }
}

ApplicationRunner 를 구현하여 run() 메서드를 재정의 하면 된다.

만일 프로젝트에 ApplicationRunner 가 여러개 면 @Order(n) 나 @Priority 로 순서를 지정할수 있다.


7.BeanDefinition

Spring IOC 에서 bean을 초기화 하기전에 BeanDefinitionReader가  bean 들의 정보를 읽는다. 이때 BeanDefinition 객체에 저장하고 이것을 이용해 bean 을 생성하게 된다.

그럼 BeanDefinition 의 속성를 수정하려면??

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(User.class);
        beanDefinitionBuilder.addPropertyValue("id", 123);
        beanDefinitionBuilder.addPropertyValue("name", "hi this kim");
        defaultListableBeanFactory.registerBeanDefinition("user", beanDefinitionBuilder.getBeanDefinition());
    }
}

postProcessBeanFactory 메서드에서 BeanDefinition 관련 객체를 얻을수 있고 그걸 수정하면 된다.

8. Bean 초기화

가끔 Bean 들이 초기화 되고 난후 나의 로직을 끼워넣고 싶다 .이럴때 사용하는것이 바로 BeanPostProcessor 이다.

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof User) {
            ((User) bean).setUserName("hi this kim!");
        }
        return bean;
    }
}
  • postProcessBeforeInitialization 초기화 전 호출 됨.
  • postProcessAfterInitialization  초기화 후 호출 됨.

9. 초기화 방법

9.1 @PostConstruct 사용하기

@Service
public class AService {
    @PostConstruct
    public void init() {
        System.out.println("===초기화===");
    }
}

9.2 InitializingBean 인터페이스 구현

@Service
public class BService implements InitializingBean {

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("===초기화===");
    }
}

10. 컨테이너 닫기

DisposableBean 인터페이스를 구현하고 destroy()를 재정의 한다.

@Service
public class DService implements InitializingBean, DisposableBean {
 
    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean destroy");
    }
 
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean afterPropertiesSet");
    }
}

11. 사용자 정의 도메인

동일 스레드의 spring 컨테이너에서 생성도니 빈은 하나! 로 하고 싶을 경우??

step1: scope 인터페이스 구현 

public class ThreadLocalScope implements Scope {
    private static final ThreadLocal THREAD_LOCAL_SCOPE = new ThreadLocal();

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        Object value = THREAD_LOCAL_SCOPE.get();
        if (value != null) {
            return value;
        }

        Object object = objectFactory.getObject();
        THREAD_LOCAL_SCOPE.set(object);
        return object;
    }

    @Override
    public Object remove(String name) {
        THREAD_LOCAL_SCOPE.remove();
        return null;
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
    }

    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }

    @Override
    public String getConversationId() {
        return null;
    }
}

step2: 새로 정의한 scope를 spring 컨테이너에 등록

@Component
public class ThreadLocalBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        beanFactory.registerScope("threadLocalScope", new ThreadLocalScope());
    }
}

setp3: 새로정의한 scope 사용

@Scope("threadLocalScope")
@Service
public class CService {
    public void add() {
    }
}

 

끝!

+ Recent posts