1. @Scheduled 어노테이션

특정 메소드에 @Scheduled 어노테이션을 선언하면 설정한 값에 따라 주기적으로 해당 메소드를 실행시킬 수 있습니다.

 

2. @Scheduled 사용 방법 

1) 스프링 스케줄링 기능 활성화

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class MyApplication {

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

}
@SpringBootApplication이 선언된 곳에 @EnableScheduling 어노테이션을 추가해 스프링 스케줄링 기능을 활성화시킵니다.

 

2) 스케줄 등록

import org.joda.time.LocalDateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class BatchScheduler {
    
    Logger logger = LoggerFactory.getLogger(this.getClass());
    
    //10초마다 실행
    @Scheduled(cron = "0/10 * * * * *")
    public void testSchedule() {
        
        logger.info("[MYTEST] test batch {}", LocalDateTime.now());
        
    }
    
}
BatchScheduler 클래스를 생성하고 컴포넌트로 등록합니다.
그리고 메소드 위에 @Scheduled 어노테이션을 선언하면 스케줄에 등록되고 설정한 시간마다 실행되게 됩니다.

 

@Scheduled 옵션

fixedDelay 이전 작업이 종료된 후 설정 시간만큼 기다린 후에 시작한다. (밀리세컨드)
@Scheduled(fixedDelay = 1000)
fixedRate 이전 작업이 종료되지 않아도 설정된 시간마다 시작한다. (밀리세컨드)
@Scheduled(fixedRate = 1000)
initialDelay 작업 시작 시, 설정된 시간만큼 기다린 후 시작한다. (밀리세컨드)
@Scheduled(fixedRate = 1000, initialDelay = 2000)
cron 원하는 시간대를 설정하여 작업을 실행한다.
@Scheduled(cron = "* * * * * *") 
(초(0-59), 분(0-59), 시간(0-23), 일(1-31), 월(1-12), 요일(1-7, 1:일, 7:토))

zone 시간대를 설정 한다. 미설정 시 로컬 시간대가 적용된다.
@Scheduled(cron = "* * * * * *", zone = "Asia/Seoul") 

 

3) cron 예제

cron 값에 특수문자를 추가해서 다양한 기간을 설정할 수 있습니다.

* : 모든 값 
? : 설정 없음
, : 배열 설정 ex) 1,3,5
- : 범위 설정 ex) 3-5
/ : 특정 값을 기준으로 특정값 마다 반복 

0 0 0 * * * : 매일 00시 00분 00초에 실행
0 0 2 * * MON : 월요일 새벽 2시에 실행
0/30 * * * * ? : 30초 마다 실행

 

 

의존성 주입을 이해하기 전에 의존성에 대해서 먼저 알아보겠습니다.

1. 의존성이란?

의존의 사전적 의미는 "스스로 하지 못하고 누군가에게 도움을 받음" 입니다.

그렇다면 의존성은 무엇일까요? 

의존성이란
어떤 객체가 생성되기 위해 다른 객체가 꼭 필요한 상태를 의미합니다.
의존성은 new 연산자를 통해 발생합니다. 의존성이 높아지면 결합도 또한 높아집니다. 

결합도가 높으면 해당 클래스를 수정할 경우 참조하고 있는 다른 클래스도 함께 이해해야합니다.  
왜냐하면 무작정 수정했다가는 사이드 이펙트가 발생할 수 있기 때문입니다.

그리고 결합도가 높은 클래스는 재사용하기 힘듭니다. 

 

1) 의존성 예제

public class Windows {

    public void booting() {
        System.out.println("Windows booting !");
    }
}

public class Computer {

    private Windows os;

    public Computer() {
        this.os = new Windows();
    }

    public void booting() {
        os.booting();
    }
}
Computer 클래스가 Windows 클래스를 참조하고 있는 상태입니다.  

만약 Mac, Linux 등 다양한 OS 객체를 전달 받으려면 소스를 수정해야합니다.
이는 코드의 유연성을 떨어트리고 중복 소스가 생기며 가독성 또한 떨어트리는 원인이 됩니다.

이러한 문제를 해결하기 위한 방법 중 하나는 바로 의존성 역전(Dependency Injection)을 이용하는 방법입니다.

 

2) 의존성 주입(Dependency Injection) 예제

public interface OS {
    public void booting();
}

public class Windows implements OS {

    @Override
    public void booting() {
        System.out.println("Windows booting !");
    }
}

public class Linux implements OS {

    @Override
    public void booting() {
        System.out.println("Linux booting !");
    }

}

public class Computer {

    private OS os;
    //외부에서 객체를 주입받는다.
    public Computer(OS os) {
        this.os = os;
    }

    public void booting() {
        os.booting();
    }
}

public class Main {
    public static void main(String[] args) {

        OS windows = new Windows();
        Computer windowsComputer = new Computer(windows);

        OS linux = new Linux();
        Computer linuxComputer = new Computer(linux);
        
        windowsComputer.booting();
        linuxComputer.booting();
    }
}


단순히 생성자를 통해 외부에서 객체를 전달받았다고 의존성 주입이라고 하지 않습니다.
만약 Windows 객체를 생성자로 주입받았다면, 외부로 부터 객체를 주입받았지만, 결합도가 낮아졌다고 할 수 없습니다.
이전에 존재했던 문제를 똑같이 안고 가는 것 입니다.

제대로 된 의존성 주입을 하려면 추상화된 객체를 외부에서 주입 받아야 합니다.
Computer 클래스의 생성자는 외부에서 생성된 객체(OS 인터페이스를 구현한 객체)를 전달 받습니다. Windows 객체를 전달 받았을 때보다 결합도가 낮아 졌으며, OS가 새로 추가되더라도 Computer 클래스는 수정하지 않아도 됩니다.

 

2. 의존성 주입이란?

의존성 주입이란 추상화된 객체를 외부에서 주입받는 것을 의미합니다. 
(ex : OS 인터페이스를 구현한 Windows 또는 Linux)

의존성 주입은 생성자, setter, field 등 다양하게 구현할 수 있습니다.
의존성 주입의 장점

1) 테스트가 용이합니다.
2) 재사용성과 유연성이 높아집니다.
3) 코드의 중복이 없어지고, 단순해집니다.
4) 코드의 가독성이 좋아집니다.
5) 종속성과 결합도가 낮아집니다.

참조

https://engkimbs.tistory.com/602?category=767795
https://medium.com/@jang.wangsu/di-dependency-injection-%EC%9D%B4%EB%9E%80-1b12fdefec4f

Spring은 전달받은 파라미터를 매핑하는 여러가지 방법을 제공합니다.

1. @PathVariable

파라미터를 URL에 포함시켜 전달하는 방법입니다.

http://localhost/api/PathVariable/member/1
    @GetMapping("/PathVariable/{type}/{id}")
    @ResponseBody
    public String pathVariableTest(
            @PathVariable Integer id,
            @PathVariable String type
    ) {
        //type : member, id : 1
        return "success";
    }

추출할 url depth에 중괄호({})를 감싸서 매핑할 수 있습니다.
중괄호 안에 들어간 name과 @PathVariable을 선언한 변수명이 같아야합니다.
다르게 하고 싶다면 아래와 같이 작성해야합니다.

    @GetMapping("/PathVariable/{type}/{id}")
    @ResponseBody
    public String pathVariableTest(
            @PathVariable("id") Integer typeId,
            @PathVariable String type
    ) {
        //type : member, typeId : 1
        return "success";
    }

 

2. @RequestParam

key=value 형식으로 전달된 파라미터를 매핑하는 어노테이션입니다.
파라미터 전달 시 content-type은 application/x-www-form-urlencoded; charset=UTF-8;으로 해주어야합니다. 
Get 방식으로 전달 할 경우 url 맨 뒤에 ?key=value 형식으로 전달되고, Post 방식일 경우 Body를 통해 전달됩니다.

1) jquery ajax

//Jquery ajax
   $.ajax({
        url: "/api/RequestParam",
        type: "GET",
        data: {
            id: 1,
            name: "kim",
            description: "member",
            address: "seoul",
            active: true
        },
        contentType: "application/x-www-form-urlencoded; charset=UTF-8;"
    });

2) Test Controller

    @GetMapping("/RequestParam")
    @ResponseBody
    public String requestParam(
            @RequestParam(required = false, value = "id") Integer memberId,
            @RequestParam(required = false) String name,
            @RequestParam(required = false) String description,
            @RequestParam(required = false) String address,
            @RequestParam(required = false) boolean active
    ) {
        //처리..
        return "success";
    }
http://localhost:8080/api/RequestParam?id=1&name=kim&description=member&address=seoul&active=true

3) 모든 파라미터 한번에 받기

모든 파라미터를 한번에 받으려면 Map을 이용하면 됩니다.

    @GetMapping("/RequestParam")
    @ResponseBody
    public String requestParam(
            @RequestParam Map<String, Object> allParameters
    ) {
        //처리..
        return "success";
    }

 Map을 통해서 파라미터를 전달받으면 편하지만, 명확하게 어떤 파라미터를 전달받는지 모른다는 단점이 있습니다.

4) 배열 객체 넘기기

    $.ajax({
        url: "/api/RequestParam",
        type: "POST",
        data: {
            arr: [1, 2, 3]
        },
        contentType: "application/x-www-form-urlencoded; charset=UTF-8;"
    });
    @PostMapping("/RequestParam")
    @ResponseBody
    public String requestParam(
            @RequestParam(required = false, value = "arr[]") List<Integer> arr
    ) {
        return "success";
    }

5) 배열 String 넘기기

    $.ajax({
        url: "/api/RequestParam",
        type: "POST",
        data: {
            arr: "1,2,3"
        },
        contentType: "application/x-www-form-urlencoded; charset=UTF-8;"
    });
    @PostMapping("/RequestParam")
    @ResponseBody
    public String requestParam(
            // form의 input 배열을 받을 때도 아래와 같이 사용한다. ex) checkbox, multiselectbox
            @RequestParam(required = false, value = "arr") List<Integer> arr
    ) {
        return "success";
    }

3. @ModelAttribute

key=value 형식으로 전달된 파라미터를 매핑하는 어노테이션입니다.
파라미터 전달 시 content-type은 application/x-www-form-urlencoded; charset=UTF-8;으로 해주어야합니다. 
@RequestParam은 변수 한개씩 매핑하는 반면에 @ModelAttribute는 Object를 통해 한번에 받을 수 있습니다.
Object의 setter와 getter를 필수로 만들어주어야 합니다.

public class Member {

    private Integer id;
    private String name;
    private String description;
    private String address;
    private boolean active;
    private List<Integer> arr;
    
    //setter,getter 생략
    
}
    $.ajax({
        url: "/api/ModelAttribute",
        type: "GET",
        data: {
            id: 1,
            name: "kim",
            description: "super member",
            address: "seoul",
            active: true,
            arr: "1,2,3"
        ),
        contentType: "application/x-www-form-urlencoded; charset=UTF-8;"
    });
    @GetMapping("/ModelAttribute")
    @ResponseBody
    public Member ModelAtrribute(
            @ModelAttribute Member member
    ) {
        return member;
    }
localhost:8080/api/ModelAttribute?id=1&name=kim&description=member&address=seoul&active=true&arr=1%2C2%2C3

Member Object 여러개 전송하기

public class MemberArray {
    private List<Member> members;
    //getter,setter 생략
}
function modelAttribute() {
    var param = {};

    for (var index = 0; index < 5; index++) {
        param[`members[${index}].id`] = index;
        param[`members[${index}].name`] = "kim";
        param[`members[${index}].description`] = "description !";
        param[`members[${index}].address`] = "seoul";
        param[`members[${index}].active`] = true;
        param[`members[${index}].arr`] = "1,2,3,4";
    }

    $.ajax({
        url: "/api/ModelAttribute",
        type: "POST",
        data: param,
        dataType: 'json',
        contentType: "application/x-www-form-urlencoded; charset=UTF-8;"
    });
}
    @PostMapping("/ModelAttribute")
    @ResponseBody
    public Object ModelAtrributePost(
            @ModelAttribute MemberArray memberArray
    ) {
        return member;
    }

4. @RequestBody

Json이나 Xml로 전송된 파라미터를 매핑할 때 사용하는 어노테이션입니다.
content-type은 application/json; UTF-8;로 해주어야합니다.

    var json = {
        id: 1,
        name: "kim",
        description: "super member",
        address: "seoul",
        active: true,
        arr: [1, 2, 3]
    };

    $.ajax({
        url: "/api/RequestBody",
        type: "POST",
        data: JSON.stringify(json),
        dataType: 'json',
        contentType: "application/json; UTF-8;"
    });

JSON.stringify() 를 이용해 객체를 JsonString으로 변환합니다.

    @PostMapping("/RequestBody")
    @ResponseBody
    public Member RequestBody(
            @RequestBody Member member
    ) {
        return member;
    }
{"id":1,"name":"kim","description":"super member","address":"seoul","active":true,"arr":[1,2,3]}

Member Json Object 여러개 전송하기

    var json = {
        id: 1,
        name: "kim",
        description: "super member",
        address: "seoul",
        active: true,
        arr: [1, 2, 3]
    };
    
    var jsons = [json, json, json];

    $.ajax({
        url: "/api/RequestBodyList",
        type: "POST",
        data: JSON.stringify(jsons),
        dataType: 'json',
        contentType: "application/json; UTF-8;"
    });
    @PostMapping("/RequestBodyList")
    @ResponseBody
    public Object RequestBodyListPost(
            @RequestBody List<Member> members
    ) {
        return members;
    }
[{"id":1,"name":"kim","description":"super member","address":"seoul","active":true,"arr":[1,2,3]},{"id":1,"name":"kim","description":"super member","address":"seoul","active":true,"arr":[1,2,3]},
{"id":1,"name":"kim","description":"super member","address":"seoul","active":true,"arr":[1,2,3]}]

본 예제는 jquery를 이용해 구현하였습니다.

1. fileUpload.html

<html>
...

    <input type="file" id="files" multiple/>
    <button id="saveBtn" type="button">저장</button>

...
</html>

2. fileUpload.js

$(document).ready(function () {

    $("#saveBtn").click(function () {
    
        var formData = new FormData();
        //formData란 <form> 태그를 객체화한 것을 의미한다.
        //formData.append(key, value)를 이용해 데이터를 삽입할 수 있다.
        //form 태그 내에 input 태그를 사용한 것과 동일하다.
        
        var files = $("#files")[0].files; //선택한 파일리스트를 가져온다.

        for (var index = 0; index < files.length; index++) {
            formData.append(`files[${index}].file`, files[index]);
            formData.append(`files[${index}].id`, index + 1);
        }
        //formData의 file key값을 files[0,1..].file 형식으로 지정한다.

        $.ajax({
            type: "POST",
            data: formData,
            dataType: 'json',
            processData: false,
            contentType: false,
            success: function (data) {
                alert("completed!");
            },
            error : function(){
                alert("failed! ")
            }
        });
    });
});

processData가 false인 경우 formData를 QueryString으로 변환하지 않는다.
contentType이 false인 경우 contentType이 아래와 같이 설정된다.
multipart/form-data; boundary=----WebKitFormBoundaryzS65BXVvgmggfL5A

@Controller
class FileUploadController{
    @GetMapping(value = "/fileUpload")
    public String fileUploadPage() {
        return "page/fileUpload";
    }

    @ResponseBody
    @PostMapping(value = "/fileUpload")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void fileUpload(FileFormTO fileFormTO) {

        fileFormTO.getFiles().forEach(fileData -> {
            logger.info("{} {}", fileData, fileData.getFile().getOriginalFilename());
        });
        //file save Logic
    }
}

3. FileTO

public class FileTO {

    private Integer id;
    private boolean deleted = false;
    private MultipartFile file;
    
    
    //setter, getter, toString 생략

}

파일 객체를 정의한다.

4. FileFormTO

public class FileFormTO {

    private List<FileTO> files;

	//setter, getter 생략
}

form데이터를 객체 단위로 전달받으려면 위와 같이 한번 더 객체로 감싸야한다. 

5. FileUploadController

@Controller
class FileUploadController{
    @GetMapping(value = "/fileUpload")
    public String fileUplaodPage() {
        return "page/fileUpload";
    }

    @ResponseBody
    @PostMapping(value = "/fileUpload")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void fileUpload(FileFormTO fileFormTO) {

        fileFormTO.getFiles().forEach(fileData -> {
            logger.info("{} {}", fileData, fileData.getFile().getOriginalFilename());
        });
    }
}

6. 테스트

이미지 3개 등록

 

파일의 다양한 정보를 하나의 객체로 묶어 서버에 전달할 때, 위 방법을 사용하면 매우 유용하다. 

+ Recent posts