일시적으로 사용된다는 것은 재사용이 되지 않거나 그럴 필요가 없다는 것인데 이건 확장성이 좋지 않다는 걸 의미합니다.
OOP 측면에서는 확장성 있게 만드는 걸 지향하지만 오히려 설계를 해치는 경우가 있습니다.
이러한 경우에 익명 클래스를 많이 씁니다.
프로그램 한번만 사용되어야하는 객체의 경우 [ UI 이벤트 처리, 스레드 객체 등... ]
밑에는 UI 이벤트 처리에 대한 예제이다 사실상 어거지로 만든 거고 클래스로도 처리가 가능할 거 같은데 설계 부분에 솔직히 이해는 가지만 크게 와닿진 않는다
Button를 생성해서 인터페이스를 즉각 구현해서 익명 객체(매개변수)를 구현한 예제이다.
RingListener
package org.example;
public interface RingListener {
public void ring ();
}
Button
package org.example;
public class Button {
public void ring(RingListener listener){
listener.ring();
}
}
Main
package org.example;
public class Main {
public static void main(String[] args) {
Button button = new Button();
button.ring(new RingListener() {
@Override
public void ring() {
System.out.println("1회용 ring ring");
}
}); // 1회용 ring ring
}
}
비즈니스 로직이 제각각이며 재사용성이 없어서 매번 클래스를 생성해야하는 경우 [ 아래와 예제와 같이 버튼이 여러개인데 각각 다른 역할의 응답을 하는 경우 버튼이 많아질수록 기하급수적으로 늘어난다]
Button
package org.example;
public class Button {
void turnOn () {
System.out.println("버튼을 킵니다.");
}
}
Anonymous
package org.example;
public class Anonymous {
/** 객체 생성해 재정의하기 **/
Button button = new Button(){
String name = "방 스위치";
// 익명 클래스 안에서만 살아있기 때문에 사용 불가
void turnOff(){
System.out.println("Down 버튼을 눌러 " + name + "를 끕니다");
}
// 재정의를 했기 때문에 사용 가능
@Override
void turnOn(){
System.out.println("Up 버튼을 눌러 " + name + "를 킵니다");
}
};
/** 클래스안 메소드에서 객체 생성해 재정의하기 [1번과 동일하다] **/
void method1(){
Button button1 = new Button(){
String name = "정답 스위치";
// 익명 클래스 안에서만 살아있기 때문에 사용 불가
void turnOff(){
System.out.println("X 버튼을 눌러 " + name + "를 끕니다");
}
// 재정의를 했기 때문에 사용 가능
@Override
void turnOn(){
System.out.println("O 버튼을 눌러 " + name + "를 킵니다");
}
};
button1.turnOn();
}
/** 매개변수에 익명함수 사용해보기 **/
void method2(Button button){
button.turnOn();
}
}
Main Thread
public class Main {
public static void main(String[] args) {
Anonymous button = new Anonymous();
// 1번 방법으로 호출
button.button.turnOn(); // 위 버튼을 눌러 방 스위치를 킵니다
// 2번 방법으로 호출
button.method1(); // O 버튼을 눌러 정답 스위치를 킵니다
// 3번 방법으로 호출
// 2번은 Anonymous에서 상세 내용을 작성한 거고 3번 방법은 그걸 밖에서 내가 정의해서 사용하는 차이가 있다.
// 즉, 어디서 상세 내용을 재정의하냐에 따라 다름
button.method2(new Button(){
String name = "엘레베이터";
// 익명 클래스 안에서만 살아있기 때문에 사용 불가
void turnOff(){
System.out.println("엑스 버튼을 눌러 " + name + "를 닫습니다.");
}
// 재정의를 했기 때문에 사용 가능
@Override
void turnOn(){
System.out.println("동그라미 버튼을 눌러 " + name + "를 엽니다.");
}
}); // 동그라미 버튼을 눌러 엘레베이터를 엽니다.
}
}
참고로 익명클래스의 경우 Override가 필요하기 때문에 Interface나 Abstract같은 추상 클래스로부터 구현하는 경우가 많습니다.
📝함수형 인터페이스
함수형 인터페이스란 1 개의 추상 메소드를 갖는 인터페이스로 여러 개의 디폴트 메서드가 있더라도 추상 메서드가 오직 하나면 함수형 인터페이스라고 한다 (default method 또는 static method 는 여러 개 존재해도 상관 없다)
매번 함수형 인터페이스를 직접 만들어서 사용하는 건 번거로운 일로서 그래서 Java 에서는 기본적으로 많이 사용되는 함수형 인터페이스를 제공한다 기본적으로 제공되는 것만 사용해도 충분하기 때문에 직접 만드는 경우는 거의 없다.
예제 코드
@FunctionalInterface // 함수형 인터페이스 조건에 맞는지 검사
interface CustomInterface<T> {
T myCall(); // abstract method 오직 하나!!
// default method 는 존재해도 상관없음
default void printDefault() {
System.out.println("Hello Default");
}
// static method 는 존재해도 상관없음
static void printStatic() {
System.out.println("Hello Static");
}
}
함수형 인터페이스 장점
같은 Input에 같은 Ouput을 제공하고 변경 개념이 아니라 복사되고 복사 된 것을 함수를 거쳐 결과를 내기 때문에 멀티스레드 공유자원에 안전하다
코드를 간결하고 가독성 있게 작성할 수 있다.
함수형 인터페이스 단점
익명 함수의 재사용이 불가능
재귀함수의 구현이 어렵다.
재사용이 불가능하므로 비슷한 함수 구현이 잦을 수 있다.
디버깅이 어렵다.
Java에서 제공하는 많이 쓰는 함수형 인터페이스
Runnable → 파라미터 없고 리턴값도 없는 경우 / 추상메소드 = run()
Consumer<T> → 파라미터 있고 리턴값 없는 경우 / 추상메소드 = accept(T t)
Supplier<T> → 파라미터 없고 리턴값 있는 경우 / 추상메소드 = get()
Function<T,R>→ 파라미터 있고 리턴값 있는 경우 / 추상메소드 = apply(T t) // T파라미터, R 반환값
Operator<T> → 파라미터 있고 리턴값 있는 경우 / 연산용도, 임의의 타입 전달하고 임의의 타입 반환
Predicate<T> → 파라미터 있고 리턴값(boolean) 있는 경우 / 추상 메소드 = test(T t)
Runnable
public class Main {
public static void main(String[] args) {
Runnable runnable = () -> System.out.println("Runnable run");
runnable.run(); // Runnable run
}
}
Consumer
public class Main {
public static void main(String[] args) {
Consumer<String> con = text -> System.out.println("parameter : " + text);
con.accept("Consumer Testing!!");
/** Consumer Chaining **/
Consumer<String> cutFruit = fruit -> System.out.println(fruit + "를 깍습니다." ); // Apple를 깍습니다.
Consumer<String> eatFruit = fruit -> System.out.println(fruit + "를 먹습니다."); // Apple를 먹습니다.
cutFruit.andThen(eatFruit).accept("Apple");
}
}
Consumer 파생형
BiConsumer => 파라미터를 2개 받아서 처리
DoubleConsumer ==> double타입으로 파라미터를 받아서 처리
IntConsumer ==> int타입으로 파라미터를 받아서 처리
LongConsumer ==> long타입으로 파라미터를 받아서 처리
ObjDoubleConsumer ==> 두개의 파라미터중에 하나는 obj 다른하나는 double
ObjIntConsumer ==> 두개의 파라미터중에 하나는 obj 다른하나는 int
ObjLongConsumer ==> 두개의 파라미터중에 하나는 obj 다른하나는 long
public class Main {
public static void main(String[] args) {
BiConsumer<String, Integer> bi = (text, num) -> System.out.println("파리미터 2개 -> " + text + " + " + num);
bi.accept("프로그래밍!!", 1234); // 파리미터 2개 -> 프로그래밍!! + 1234
}
}
Supplier
public class Main {
public static void main(String[] args) {
Supplier<String> supplier = () -> "supplier has return but no params";
System.out.println(supplier.get()); // supplier has return but no params
}
}
Supplier 파생형
BooleanSupplier : boolean 타입으로 반환
DoubleSupplier : double 타입으로 반환
IntSupplier : int 타입으로 반환
LongSupplier : long 타입으로 반환
Operator
public class Main {
public static void main(String[] args) {
BinaryOperator<Integer> binaryOperator = (num1, num2) -> num1 + num2;
int result = binaryOperator.apply(5, 10);
System.out.println(result); // 15
}
}
Operator라는 인터페이스는 없다
BinaryOperator<T> : BiFunction<T,U,R> 상속 → 두개의 파라미터 전달하고 반환 ( 모두 데이터타입 동일 )
UnaryOperator<T> : Function<T,R> 상속 → 하나의 파라미터 전달하고 반환 ( 모두 데이터타입 동일 )
Operator 파생형
DoubleBinaryOperator : 두개의 파라미터 double 전달하고 double 반환 ( 모두 데이터타입 동일 )
DoubleUnaryOperator : 하나의 파라미터 double 전달하고 double반환
IntBinaryOperator : 두개의 파라미터 int 전달하고 int 반환 ( 모두 데이터타입 동일 )
IntUnaryOperator : 하나의 파라미터 int 전달하고 int반환
LongBinaryOperator : 두개의 파라미터 long 전달하고 long 반환 ( 모두 데이터타입 동일 )
LongUnaryOperator : 하나의 파라미터 long 전달하고 long반환
Predicate
package org.example;
import java.util.function.*;
public class Main {
public static void main(String[] args) {
Predicate<String> isNameString = str -> str.equals("name");
System.out.println("is String Name? test() -> " + isNameString.test("age"));
// is String Name? test() -> false
System.out.println("is String Name? test() -> " + isNameString.test("name"));
// is String Name? test() -> true
}
}
📝 람다식 [익명 객체]
람다식의 경우 간단한 메소드를 한줄로 표현하게끔 해줍니다.
생략해서 표현하려면 여러개의 함수가 있으면 특정이 안 되기 때문에 하나의 메소드만 존재하는 인터페이스나 추상클래스에 사용됩니다.
람다는 익명 객체이다 (익명 함수는 함수가 독자적으로 존재해야하지만 Java는 클래스에 종속적이기 때문에 익명 객체라는 말이 맞다)
람다식 축약 예제 따라해보기
class Calculator {
int sum(int x, int y){
return x + y;
} // 람다식 : (x,y) -> x+y;
void print(String name, int i){
System.out.println(name + " = " + i);
} // 람다식 : (name,i) -> System.out.println(name + " = " + i);
int square(int x){
return x * x;
} // 람다식 : x -> x * x
int roll() {
return (int) (Math.random() * 6);
} // 람다식 : () -> (int) (Math.random() * 6);
}
// 여러 줄일 경우
(name, i) -> {
String msg = "Name: " + name;
System.out.println(msg);
System.out.println("Index: " + i);
}
함수반환 타입을 생략할 수 있다.
함수명을 생략할 수 있다.
인자 타입을 생략할 수 있다.
함수내용인 중괄호 내용이 한줄이면 중괄호는 생략이 가능하다
함수형 인터페이스 + 람다식 이용해보기
package org.example;
public class Main {
public static void main(String[] args) {
/** 익명 객체 잘못된 사용 방법 **/
Object object = new Object() {
int max(int a, int b){
return a > b ? a : b;
}
}; // Object에 max라는게 없기 때문에 object.max(3,5); 사용 불가
/** 함수형 인터페이스 **/
// MyFunction myFunction = new MyFunction() {
// @Override
// public int max(int a, int b) {
// return a > b ? a: b;
// }
// };
/** 함수형 인터페이스 + 람다식 **/
MyFunction myFunction = (a, b) -> a > b ? a : b;
// 함수형 인터페이스 자체가 추상 메서드가 하나밖에 없기 때문에
// 매개변수 및 리턴값에 뭐가 나올지 알고 있어서 생략이 가능하다
System.out.println(myFunction.max(3,5)); // 5
// Integer.parseInt 말고도 Integer는 다양한 함수가 있지만 람다가 가능한 것은
// 해당 함수를 왼쪽에 메소드가 하나인 Interface에 넣기 때문에 가능하다
Function<String, Integer> parser = Integer::parseInt;
}
interface MyFunction {
public abstract int max(int a, int b);
}
}
람다식하고 함수형 인터페이스는 짝궁이라고 생각하면 된다.
📝 익명함수, 람다식 특징 (AtomicInteger)
public class LambdaStackExample {
public static Runnable createRunnable() {
int n = 10;
// ❌ 만약 n++을 허용한다면?
return () -> {
// n++; // 컴파일 에러 발생 (Local variable must be final or effectively final)
System.out.println("n = " + n);
};
}
public static void main(String[] args) {
Runnable r = createRunnable();
r.run();
}
}
일반적으로 로컬 변수(원시값 or 참조값)은 메서드 스택 프레임안에 생성됩니다.
메서드가 끝나면 변수는 사라지지만 만약 사라진 메서드 안에 로컬 변수를 계속 수정할 수 있게 되면 사라진 메모리에 접근하기 때문에 위험이 있습니다.
해당 위험상황은 아래와 같은 경우가 있습니다.
스레드 작업에 많이 쓰이게 되는데 쓰레드에서 계속 밖에 있는 변수를 수정하게 되면 점점 이상해질 가능성이 있습니다.
위와 같은 이유로 아예 읽기 전용인 final로만 만듭니다. (변수캡처 = 복사본)
int같은 건 스택에 올라가기 때문에 위험이 있지만 Heap에 저장되는 건 사용할 수 있습니다.
계속적인 참조가 있기 때문에 메소드가 종료되어도 남아있습니다.
AtomicInteger 등 지원하는 클래스를 이용해 수정하거나 값을 읽거나 할 수 있습니다.
@Getter // Getter 생성
@Setter // Setter 생성
@RequiredArgsConstructor // 생성자 DI
@Data // Getter + Setter 생성
@AllArgsConstructor // 생성자 생성
@NoArgsConstructor // 기본생성자
@ToString(of = {"id","password"})
public class Account {
// private final String id; // DB에서 값을 받아와 수정할 일이 없을 때는 final 로 쓰는게 trend
// private final int password; // DB에서 값을 받아와 수정할 일이 없을 때는 final 로 쓰는게 trend
private String id;
private int password;
@Builder // 내가 원하는 생성자 파라미터 지정 가능
public Account(String id, int password) {
this.id = id;
this.password = password;
}
}
public class ApiExceptionV2Controller {
...
@ResponseStatus(HttpStatus.BAD_REQUEST) // 해당 Status 설정 없으면 200(정상)이 나가기 때문에 따로 설정 필요
@ExceptionHandler(IllegalArgumentException.class) // 해당 컨트롤러에서만 IllegalArgumentException 발생 시 작동
public ErrorResult illegalExHandler(IllegalArgumentException e){
log.error("[exception] ex",e);
return new ErrorResult("BAD",e.getMessage());
/**
* {
* "code": "BAD",
* "message": "잘못된 입력 값"
* }
*/
}
@GetMapping("/api2/members/{id}")
public MemberDto getMember(@PathVariable("id") String id) {
if (id.equals("ex")) {
throw new RuntimeException("잘못된 사용자");
}
if (id.equals("bad")) {
throw new IllegalArgumentException("잘못된 입력 값");
}
if (id.equals("user-ex")) {
throw new UserException("사용자 오류");
}
return new MemberDto(id, "hello " + id);
}
}
현 컨트롤러 내에서 지정한 에러 발생시 핸들링할 수 있는 역할을 한다
📝@Transactional
@Transactional
public void updateSomething(MembmerDto membmerDto){
// ... 업데이트
// 실패!! -> RollBack
}
📝@TestConfiguration
@TestConfiguration
static class TestConfig {
private final DataSource dataSource;
public TestConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
MemberRepositoryV3 memberRepositoryV3() {
return new MemberRepositoryV3(dataSource);
}
@Bean
MemberServiceV3_3 memberServiceV3_3() {
return new MemberServiceV3_3(memberRepositoryV3());
}
}
@RestController
@RequestMapping("/mapping/users")
public class MappingClassController {
/**
* GET /mapping/users
*/
@GetMapping
public String users() {
return "get users";
}
/**
* POST /mapping/users
*/
@PostMapping
public String addUser() {
return "post user";
}
/**
* GET /mapping/users/{userId}
*/
@GetMapping("/{userId}")
public String findUser(@PathVariable String userId) {
return "get userId=" + userId;
}
/**
* PATCH /mapping/users/{userId}
*/
@PatchMapping("/{userId}")
public String updateUser(@PathVariable String userId) {
return "update userId=" + userId;
}
}
일반적으로 공통적인 URL을 클래스 위에 선언해 사용한다
@RequestMapping(value = "/modelAndView.do" method = RequestMethod.GET)
public ModelAndView returnModelAndView(HttpServletResponse response,
HttpServletRequest request) {
ModelAndView modelAndView = new ModelAndView();
// WEB-INF/views/가 기본 경로
modelAndView.setViewName("request"); // request.jsp 페이지 노출 WEB-INF/views/request.jsp
modelAndView.addObject("name","James"); // index.jsp에 name : James 값을 전달
return modelAndView;
}
리턴 종류 - ModelAndView
노출시킬 jsp 페이지와 값을 보낼 수 있습니다.
@RequestMapping(value = "/string.do")
public String returnString(HttpServletResponse response,
HttpServletRequest request) {
return "request"; // request.jsp 페이지 노출
}
@RequestMapping(value = "/model.do", method = RequestMethod.GET)
public void body4(@ModelAttribute BodyVO bodyVO,
HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
// 호출 URI : model.do?name=lee&age=20
// @ModelAttribute 사용시에는 해당 클래스의 객체를 생성하고 값을 set 해준다.
// (default로 @ModelAttribute 안 적어도 @ModelAttribute 적은 거 처럼 동작)
// 객체를 받는 경우는 무조건 쿼리스트링으로만 동작함 (body 인식 못 함)
System.out.println(">>> " + bodyVO.getName()); // >>> lee
System.out.println(">>> " + bodyVO.getAge()); // >>> 20
}
사용시 해당 클래스의 객체를 생성하고 값을 set 해준다.(default로 @ModelAttribute 안 적어도 @ModelAttribute 적은 거 처럼 동작)
객체를 받는 경우는 무조건 쿼리스트링으로만 동작함 (body 내용은 자동 DI 못 함)
📝 @ResponseBody,@RequestBody
@RequestMapping(value = "/urlencoded.do", method = RequestMethod.POST)
public void urlencoded(String code,String name) {
// 호출 Url : urlencoded.do?name=Lee
// Content-Type : application/x-www-form-urlencoded
// Body : key → code, Value → ABC
System.out.println(">>> " + code); // ABC
System.out.println(">>> " + name); // Lee
// 일반적으로 POST 방식에 쿼리스트링을 붙히지 않지만 붙혀도 인식이 가능함
// x-www-form-urlencoded 의 경우 쿼리스트링처럼 code=ABC 인식을 한다.
}
/** application/x-www-form-urlencoded **/
@RequestMapping(value = "/urlencodedToObject.do", method = RequestMethod.POST)
public void urlencodedToObject(BodyVO bodyVO) {
// 호출 Url : urlencoded.do?name=Lee
// Content-Type : application/x-www-form-urlencoded
// Body : key → code, Value → ABC
System.out.println(">>> " + bodyVO.getName()); // ABC
System.out.println(">>> " + bodyVO.getAge()); // ABC
}
/** application/x-www-form-urlencoded의 GET의 경우 따로 RequestBody가 필요없다. **/
/** text **/
@RequestMapping(value = "/text.do", method = RequestMethod.POST)
public void text(@RequestBody String bodyContents) {
// 호출 Url : text.do
// body : this is text
// Content-Type : text 또는 text/html
System.out.println(">>> " + bodyContents); // this is text
}
/** application/json **/
@RequestMapping(value = "/json.do", method = RequestMethod.POST)
public void json(@RequestBody String bodyContents) {
// 호출 Url : json.do
// body : {"content" : "this is json"}
// Content-Type : application/json
System.out.println(">>> " + bodyContents); // {"content" : "this is json"}
}
/** application/json (Object Injection) **/
@RequestMapping(value = "/jsonToObject.do", method = RequestMethod.POST)
public @ResponseBody HashMap<String, Object> jsonToObject(@RequestBody BodyVO bodyVO) {
/** BodyVO (HashMap도 또한 Object이기 떄문에 동일하게 적용 된다. **/
// String name
// String age → int 형으로 값을 보내면 못 자동 Injection이 안 된다. "age" : 20 (X)
/** 참고로 BodyVO에 default값이 설정되어 있어도 ReuqestBody에 넘어온 값이 없다면(body에 json을 안 보내면) null로 덮어씌워진다. **/
/** 필요사항에서는 데이터를 까서 검증하는 작업을 직접해야할 거 같다. **/
// 호출 Url : jsonToObject.do
// body : {"name" : "lee", "age":"20"}
// Content-Type : application/json
System.out.println(">>> " + bodyVO.getName()); // lee
System.out.println(">>> " + bodyVO.getAge()); // 20
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("bodyVO", bodyVO);
return map;
}
/** responseBody란 처리한 결과를 클라이언트에게 다시 돌려줘서 읽을 수 있게한다. (즉 return 값이 있어야한다.) **/
// 미사용시 → 404 페이지를 찾을 수 없음
// 사용시 → {"bodyVO" : {"name" : "lee", "age":"20"}}
/**
* jsonToObject.do을 사용하기 위해서는 하기 라이브러리가 필요 (body 내용을 Object에 Injeciton)
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.3</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.13</version>
</dependency>
**/
package com.lsj.study.controller;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.lsj.study.util.MosaicCaptcha;
@Controller
public class CaptchaController {
/** ------------ Mosaic CaptCha Page ------------ **/
@GetMapping(value = "/captcha/mosaic")
public String captchaMosaic(HttpServletRequest reques) {
/** Referer 필요시 사용 **/
// String referer = request.getHeader("Referer");
//
// if (referer.equals(rightReferer)) return "captcha/captcha_mosaic";
// else return "captcha/wrong_page";
return "captcha/captcha_mosaic";
}
/** ------------ Mosaic CaptCha Multiple Choice (객관식 그림) ------------ **/
@GetMapping(value = "/captcha/mosaic/multiple/choice")
public void getQuestionList(HttpServletResponse response, HttpSession session) throws IOException {
MosaicCaptcha mosaicCaptcha = new MosaicCaptcha();
/** 객관식 번호 가져오기 **/
BufferedImage choiceNum1 = mosaicCaptcha.getMosaicQuestionImage(session);
BufferedImage choiceNum2 = mosaicCaptcha.getMosaicQuestionImage(session);
BufferedImage choiceNum3 = mosaicCaptcha.getMosaicQuestionImage(session);
BufferedImage choiceNum4 = mosaicCaptcha.getMosaicAnswerImage(session);
/** 화면 준비 **/
OutputStream out = response.getOutputStream();
/** 객관식 정보 담기 **/
Map<String, BufferedImage> multipleChoiceMap = new HashMap<String, BufferedImage>();
multipleChoiceMap.put("wrong1", choiceNum1);
multipleChoiceMap.put("wrong2", choiceNum2);
multipleChoiceMap.put("wrong3", choiceNum3);
multipleChoiceMap.put("answer", choiceNum4);
/** 객관식 번호 섞기 **/
List<String> keys = new ArrayList(multipleChoiceMap.keySet());
Collections.shuffle(keys);
/** 객관식 번호 부여하기 **/
BufferedImage questionNum1 = mosaicCaptcha.drawNumberOnMultipleChoiceImage("1");
BufferedImage questionNum2 = mosaicCaptcha.drawNumberOnMultipleChoiceImage("2");
BufferedImage questionNum3 = mosaicCaptcha.drawNumberOnMultipleChoiceImage("3");
BufferedImage questionNum4 = mosaicCaptcha.drawNumberOnMultipleChoiceImage("4");
/** key로 정답을 찾아 session 영역에 저장 **/
int answerNum = keys.indexOf("answer");
session.setAttribute("mosaicAnswer", answerNum + 1);
System.out.println("Mosaic-answer : " + (answerNum + 1));
/** 객관식 리스트 합쳐 하나의 이미지로 재구성 **/
try {
/** 기본 바탕화면 구성 **/
BufferedImage mergedImage = new BufferedImage(550, 100, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = (Graphics2D) mergedImage.getGraphics();
graphics.setPaint(new Color(255, 255, 255));
graphics.fillRect(0, 0, mergedImage.getWidth(), mergedImage.getHeight());
/** 객관식 리스트 **/
graphics.drawImage(multipleChoiceMap.get(keys.get(0)), 0, 0, null);
graphics.drawImage(multipleChoiceMap.get(keys.get(1)), 150, 0, null);
graphics.drawImage(multipleChoiceMap.get(keys.get(2)), 300, 0, null);
graphics.drawImage(multipleChoiceMap.get(keys.get(3)), 450, 0, null);
/** 객관식 리스트의 번호 부여 **/
graphics.drawImage(questionNum1, 10, 0, null);
graphics.drawImage(questionNum2, 160, 0, null);
graphics.drawImage(questionNum3, 310, 0, null);
graphics.drawImage(questionNum4, 460, 0, null);
/** 화면에 출력 **/
response.setContentType("image/png");
ImageIO.write(mergedImage, "png", out);
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
/** ------------ Mosaic CaptCha Question (문제) ------------ **/
@GetMapping(value = "/captcha/mosaic/question")
public void getMosaicQuestion(HttpServletResponse response, HttpSession session)
throws IOException {
MosaicCaptcha mosaic = new MosaicCaptcha();
/** 랜덤 이미지 받기 **/
String fileImagePath = mosaic.getRandomMosaicImage(session);
/** 자를 부분 랜덤 XY 좌표 설정 **/
mosaic.setCutXYPoint(session);
File file = new File(fileImagePath); // 리사이즈할 파일 정보
int resizedWidth = 500; // 리사이즈할 가로 길이
int resizedHeight = 500; // 리사이즈할 세로 길이
/** 리사이징 **/
BufferedImage blackBoxImage = mosaic.resize(file, resizedWidth, resizedHeight);
/** 문제 낼 곳에 블랙박스 생성 **/
int blackBoxWidth = 100;
int blackBoxHeight = 100;
mosaic.drawBlackBox(blackBoxImage, blackBoxWidth, blackBoxHeight, session);
/** 화면에 뿌릴 준비 **/
OutputStream out = response.getOutputStream();
/** 화면에 뿌리기 **/
response.setContentType("image/png");
ImageIO.write(blackBoxImage, "png", out);
}
/** ------------ Mosaic CaptCha Confirm (정답 확인) ------------ **/
@GetMapping(value = "/captcha/mosaic/answer")
public @ResponseBody Map<Object, Object> confirmCaptChaMosaic(HttpServletRequest request, HttpSession session) {
Map<Object, Object> result = new HashMap<>();
/** 정답 가져오기 **/
session = request.getSession();
String answer = String.valueOf((int) session.getAttribute("mosaicAnswer")); // 정답
String userAnswer = request.getParameter("userAnswer"); // 유저 정답
System.out.println("Mosaic-userAnswer : " + userAnswer);
/** 정답 확인 **/
if (userAnswer.equals(answer)) result.put("response", "right");
else result.put("response", "wrong");
return result;
}
}
Controller
package com.lsj.study.util;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpSession;
import org.springframework.core.io.ClassPathResource;
import com.lsj.study.vo.ImageVo;
public class MosaicCaptcha {
/**
* @param session : 사용자 session
* @return 랜덤 모자이크에 사용할 이미지 가져오기
*/
public String getRandomMosaicImage(HttpSession session) {
int randomImage = (int) (Math.random() * 6) + 1;
String filePath = "";
switch (randomImage) {
case 1:
filePath = new ClassPathResource("image/captcha/mosaic/mosaic_1.png").getPath();
break;
case 2:
filePath = new ClassPathResource("image/captcha/mosaic/mosaic_2.png").getPath();
break;
case 3:
filePath = new ClassPathResource("image/captcha/mosaic/mosaic_3.png").getPath();
break;
case 4:
filePath = new ClassPathResource("image/captcha/mosaic/mosaic_4.png").getPath();
break;
case 5:
filePath = new ClassPathResource("image/captcha/mosaic/mosaic_5.png").getPath();
break;
case 6:
filePath = new ClassPathResource("image/captcha/mosaic/mosaic_6.png").getPath();
break;
}
URL resource = getClass().getClassLoader().getResource(filePath);
filePath = resource.getFile();
session.setAttribute("questionImagePath", filePath);
session.setAttribute("imageNumber", randomImage);
System.out.println("QuestionfilePath : " + filePath);
return filePath;
}
/**
* @param session : 사용자 session
* @return 잘못된 모자이크 이미지 만들기
*/
public String getWrongRandomMosaicImage(HttpSession session) {
int questionImage = 1;
if(session.getAttribute("imageNumber") != null)
questionImage = (int) session.getAttribute("imageNumber");
int randomImage = (int) (Math.random() * 6) + 1;
while (true) {
if (questionImage != randomImage)
break;
else
randomImage = (int) (Math.random() * 6) + 1;
}
String filePath = "";
switch (randomImage) {
case 1:
filePath = new ClassPathResource("image/captcha/mosaic/mosaic_1.png").getPath();
break;
case 2:
filePath = new ClassPathResource("image/captcha/mosaic/mosaic_2.png").getPath();
break;
case 3:
filePath = new ClassPathResource("image/captcha/mosaic/mosaic_3.png").getPath();
break;
case 4:
filePath = new ClassPathResource("image/captcha/mosaic/mosaic_4.png").getPath();
break;
case 5:
filePath = new ClassPathResource("image/captcha/mosaic/mosaic_5.png").getPath();
break;
case 6:
filePath = new ClassPathResource("image/captcha/mosaic/mosaic_6.png").getPath();
break;
}
URL resource = getClass().getClassLoader().getResource(filePath);
filePath = resource.getFile();
System.out.println("wrongFilePath : " + filePath);
return filePath;
}
/**
* @param file : 리사이즈할 파일
* @param width : 리사이즈할 넓이
* @param height : 리사이즈할 높이
* @return 리사이즈된 이미지
* @throws IOException
*/
public BufferedImage resize(File file, int width, int height) throws IOException {
/** 이미지 가져오기 **/
InputStream inputStream = new FileInputStream(file);
/** 받은 이미지 읽기 **/
BufferedImage inputImage = ImageIO.read(inputStream);
/** 문제 이미지의 자를 부분의 크기와 위치 범위가 정해져있기 때문에 동일한 크기로 리사이징 **/
BufferedImage outputImage = new BufferedImage(width, height, inputImage.getType());
Graphics2D graphics2D = outputImage.createGraphics();
graphics2D.drawImage(inputImage, 0, 0, width, height, null); // 그리기
graphics2D.dispose(); // 자원해제
return outputImage;
}
/**
* @param session : 사용자 session
* @why 모자이크로 만들 자를 이미지 X, Y 좌표를 설정
*/
public void setCutXYPoint(HttpSession session) {
int cutXPoint;
int cutYPoint;
/** session이 안 잡힌 경우 예외처리 **/
if (session.getAttribute("XPoint") == null || session.getAttribute("YPoint") == null) {
cutXPoint = (int) (Math.random() * 400);
cutYPoint = (int) (Math.random() * 400);
session.setAttribute("XPoint", cutXPoint);
session.setAttribute("YPoint", cutYPoint);
/** session 영역이 존재하는 경우 **/
} else {
cutXPoint = (int) (Math.random() * 400);
cutYPoint = (int) (Math.random() * 400);
session.setAttribute("XPoint", cutXPoint);
session.setAttribute("YPoint", cutYPoint);
}
}
/**
* @param blackBoxImage : 블랙박스 이미지
* @param blackBoxWidth : 블랙박스 넓이
* @param blackBoxHeight : 블랙박스 높이
* @param session : 사용자 session
* @why 자른 모자이크를 검은색 사각형 박스로 덮어씌우기
*/
public void drawBlackBox(BufferedImage blackBoxImage, int blackBoxWidth, int blackBoxHeight, HttpSession session) {
int cutXPoint = (int) session.getAttribute("XPoint"); // 왼쪽 상단 X좌표
int cutYPoint = (int) session.getAttribute("YPoint"); // 왼쪽 상단 Y좌표
/** 블랙박스 그리기 **/
Graphics graphics = blackBoxImage.getGraphics();
graphics.setColor(Color.BLACK);
graphics.fillRect(cutXPoint, cutYPoint, blackBoxWidth, blackBoxHeight); // 채우기 사각형
// graphics.drawRect(cutXPoint, cutYPoint, blackBoxWidth, blackBoxHeight); // 테두리 사각형
graphics.dispose();
}
/**
*
* @param numberToDraw : 자른 객곽신 이미지 위에 그릴 숫자
* @return 자른 객관식 이미지 위에 객관식 번호 기입한 이미지
*/
public BufferedImage drawNumberOnMultipleChoiceImage(String numberToDraw) {
/** 이미지화할 글자 설정 **/
int imageWidth = 550; // 문제 이미지 넓이
int imageHeight = 100; // 문제 이미지 높이
Color color = new Color(255, 0, 0); // 글자 색
Font font = new Font("TimeRoman", Font.PLAIN, 35); // 이미지 글체
ImageVo stringImage = new ImageVo(imageWidth, imageHeight, color, font);
/** 문자를 그릴 종이 만들기 **/
BufferedImage image = getWhitePaper(stringImage);
/** 문제 받아오기 **/
String question = numberToDraw;
/** 글자 이미지가 생성될 위치 선정 **/
Graphics2D graphics2 = image.createGraphics(); // Graphics2D 와 BufferedImage는 연동 된 느낌?
/** 글자색과 글자체 받아오기 **/
graphics2.setColor(stringImage.getImageColor());
graphics2.setFont(stringImage.getFont());
/** 해당 내용으로 그리기 작업 **/
graphics2.drawString(question, 0, 40);
graphics2.dispose();
return image;
}
/**
* @param session : 사용자 session
* @return 문제에서 자를 이미지 가져오기
*/
public BufferedImage getMosaicQuestionImage(HttpSession session) throws IOException {
MosaicCaptcha mosaic = new MosaicCaptcha();
/** 랜덤 이미지 받기 **/
String fileImagePath = mosaic.getWrongRandomMosaicImage(session);
/** 리사이즈할 파일 정보 받기 **/
File file = new File(fileImagePath);
int resizedWidth = 500; // 리사이즈할 가로 길이
int resizedHeight = 500; // 리사이즈할 세로 길이
/** 리사이징 **/
BufferedImage resizedImage = mosaic.resize(file, resizedWidth, resizedHeight);
/** 문제 이미지의 자를 부분의 크기와 위치 설정 **/
int cutWidth = 100; // 자를 넓이
int cutHeight = 100; // 자를 높이
int cutXPoint = (int) (Math.random() * 400); // 랜덤 X포인트 (자를 왼쪽 상단 위치)
int cutYPoint = (int) (Math.random() * 400); // 랜덤 Y포인트 (자를 왼쪽 상단 위치)
BufferedImage cutImage = resizedImage.getSubimage(cutXPoint, cutYPoint, cutWidth, cutHeight); // 잘린 이미지
return cutImage;
}
/**
* @param session : 사용자 session
* @return 잘린 정답 이미지
*/
public BufferedImage getMosaicAnswerImage(HttpSession session) throws IOException {
MosaicCaptcha mosaic = new MosaicCaptcha();
/** 문제 이미지 경로 **/
String fileImagePath = (String) session.getAttribute("questionImagePath");
File file = new File(fileImagePath); // 리사이즈할 파일 정보 받기
int resizedWidth = 500; // 리사이즈할 가로 길이
int resizedHeight = 500; // 리사이즈할 세로 길이
/** 리사이징 **/
BufferedImage resizedImage = mosaic.resize(file, resizedWidth, resizedHeight);
/** 문제 이미지의 자를 부분의 크기와 위치 설정 **/
int cutWidth = 100; // 자를 넓이
int cutHeight = 100; // 자를 높이
int cutXPoint = (int) session.getAttribute("XPoint"); // 왼쪽 상단 X좌표
int cutYPoint = (int) session.getAttribute("YPoint"); // 왼쪽 상단 Y좌표
/** 잘린 이미지 **/
BufferedImage cutImage = resizedImage.getSubimage(cutXPoint, cutYPoint, cutWidth, cutHeight);
return cutImage;
}
/**
* @param customImage : 그림을 그릴 종이 설정
* @return 그림을 그릴 종이
*/
public BufferedImage getWhitePaper(ImageVo customImage) {
// 글자를 그릴 종이 크기 설정
int width = customImage.getImageWidth();
int height = customImage.getImageHeight();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
return image;
}
}
Util
package com.lsj.study.vo;
import java.awt.Color;
import java.awt.Font;
public class ImageVo {
private int imageWidth;
private int imageHeight;
private Color imageColor;
private Font font;
public ImageVo(int imageWidth, int imageHeight, Color imageColor, Font font) {
this.imageWidth = imageWidth;
this.imageHeight = imageHeight;
this.imageColor = imageColor;
this.font = font;
}
public int getImageWidth() {
return imageWidth;
}
public void setImageWidth(int imageWidth) {
this.imageWidth = imageWidth;
}
public int getImageHeight() {
return imageHeight;
}
public void setImageHeight(int imageHeight) {
this.imageHeight = imageHeight;
}
public Color getImageColor() {
return imageColor;
}
public void setImageColor(Color imageColor) {
this.imageColor = imageColor;
}
public Font getFont() {
return font;
}
public void setFont(Font font) {
this.font = font;
}
}
package com.lsj.study.controller;
import java.awt.Color;
import java.awt.Font;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.lsj.study.Util.NumberCaptcha;
import com.lsj.study.Vo.ImageVo;
@Controller
public class CaptchaController {
/** ------------ Number CaptCha Page ------------ **/
@GetMapping(value = "/captcha/number")
public String captchaNumber(HttpServletRequest request) {
/** Referer 필요시 사용 **/
// String referer = request.getHeader("Referer");
//
// if (referer.equals(rightReferer)) return "captcha/captcha_number";
// else return "captcha/wrong_page";
return "captcha/captcha_number";
}
/** ------------ Number CaptCha Question (문제)------------ **/
@GetMapping(value = "/captcha/number/question")
public void getNumberCaptcha(HttpServletResponse response, HttpSession session)
throws IOException {
NumberCaptcha numberCaptcha = new NumberCaptcha();
OutputStream out = response.getOutputStream(); // 현재 화면에 뿌릴 outputStream
/** 이미지화할 글자 설정 **/
int imageWidth = 1000; // 문제 이미지 넓이
int imageHeight = 500; // 문제 이미지 높이
Color color = new Color(255, 0, 0); // 글자 색
Font font = new Font("TimeRoman", Font.PLAIN, 100); // 이미지 글체
ImageVo stringImage = new ImageVo(imageWidth, imageHeight, color, font);
/** 문자를 그릴 종이 만들기 **/
BufferedImage image = numberCaptcha.getWhitePaper(stringImage);
/** 문제 받아오기 **/
String question = numberCaptcha.getQuestion(session);
/** 종이(image)에 문제(questino)을 그리기 **/
numberCaptcha.draw(image, stringImage, question);
/** 이미지화된 글자의 content-type 설정 **/
response.setContentType("image/png");
/** 만든 이미지를 out(화면)에 png로 생성 **/
ImageIO.write(image, "png", out);
}
/** ------------ Number CaptCha Confrim (정답 확인) ------------ **/
@GetMapping(value = "/captcha/number/answer")
public @ResponseBody Map<Object, Object> confirmNumberCaptcha(HttpServletRequest request, HttpSession session) {
Map<Object, Object> result = new HashMap<>();
/** 정답 가져오기 **/
session = request.getSession();
String answer = (String) session.getAttribute("answer"); // 정답
String userAnswer = request.getParameter("userAnswer"); // 유저 정답
/** 정답 확인 **/
if (userAnswer.equals(answer)) result.put("response", "right");
else result.put("response", "wrong");
return result;
}
}
Controller
package com.lsj.study.Util;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.servlet.http.HttpSession;
import com.lsj.study.Vo.ImageVo;
public class NumberCaptcha {
/**
* @param customImage : 글자 그릴 종이 설정
* @return : 글자 그릴 종이
*/
public BufferedImage getWhitePaper(ImageVo customImage) {
// 글자를 그릴 종이 크기 설정
int width = customImage.getImageWidth();
int height = customImage.getImageHeight();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
return image;
}
/**
* @param session : 사용자 session
* @return : 랜덤 숫자 더하기 문제 생성
*/
public String getQuestion(HttpSession session) {
// 1번째 2번째 랜덤 숫자
int firstNum = (int) (Math.random() * 10 + 1);
int secondNum = (int) (Math.random() * 10 + 1);
// session에 답 저장
int answer = firstNum + secondNum;
session.setAttribute("answer", String.valueOf(answer));
System.out.println("Number-answer : " + answer);
// 문제 만들기
String question = String.valueOf(firstNum) + " + " + String.valueOf(secondNum) + " = ?";
return question;
}
/**
* @param image : 그림 그릴 종이
* @param imageVo : 그림 그릴 종이 설정
* @param question : 그림에 그릴 문제
*/
public void draw(BufferedImage image, ImageVo imageVo, String question) {
// 글자 이미지가 생성될 위치 선정
int left = 240;
int top = 300;
// Graphics2D 와 BufferedImage는 연동 된 느낌?
Graphics2D graphics = image.createGraphics();
// 글자색과 글자체 받아오기
graphics.setColor(imageVo.getImageColor());
graphics.setFont(imageVo.getFont());
// 해당 내용으로 그리기 작업
graphics.drawString(question, left, top);
graphics.dispose();
}
}
Util
package com.lsj.study.Vo;
import java.awt.Color;
import java.awt.Font;
public class ImageVo {
private int imageWidth;
private int imageHeight;
private Color imageColor;
private Font font;
public ImageVo(int imageWidth, int imageHeight, Color imageColor, Font font) {
this.imageWidth = imageWidth;
this.imageHeight = imageHeight;
this.imageColor = imageColor;
this.font = font;
}
public int getImageWidth() {
return imageWidth;
}
public void setImageWidth(int imageWidth) {
this.imageWidth = imageWidth;
}
public int getImageHeight() {
return imageHeight;
}
public void setImageHeight(int imageHeight) {
this.imageHeight = imageHeight;
}
public Color getImageColor() {
return imageColor;
}
public void setImageColor(Color imageColor) {
this.imageColor = imageColor;
}
public Font getFont() {
return font;
}
public void setFont(Font font) {
this.font = font;
}
}
package com.lsj.study.controller;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.lsj.study.Util.ColorCaptcha;
@Controller
public class CaptchaController {
/** ------------ Color CaptCha Page ------------ **/
@GetMapping(value = "/captcha/color")
public String captchaColor(HttpServletRequest request) {
/** Referer 필요시 사용 **/
//String referer = request.getHeader("Referer");
//if (referer.equals(rightReferer)) return "captcha/captcha_color";
//else return "captcha/wrong_page";
return "captcha/captcha_color";
}
/** ------------ Color CaptCha Question (문제의 공) ------------ **/
@GetMapping(value = "/captcha/color/question")
public void colorQuestion(HttpServletResponse response, HttpSession session) throws IOException {
/** 기존에 남아있는 정답 번호 삭제 **/
session.removeAttribute("colorAnswer");
ColorCaptcha colorCaptcha = new ColorCaptcha();
BufferedImage questionImage = colorCaptcha.drawQuestionColorBall(600, 200, session);
OutputStream out = response.getOutputStream(); // response outputStream
/** 이미지화된 글자의 content-type 설정 **/
response.setContentType("image/png");
/** 만든 이미지를 out(화면)에 png로 생성 **/
ImageIO.write(questionImage, "png", out);
}
/** ------------ Color CaptCha Multiple Choice (선택할 공) ------------ **/
@GetMapping(value = "/captcha/color/multiple/choice")
public void getMultipleChoice(HttpServletResponse response,HttpSession session) throws IOException {
ColorCaptcha colorCaptcha = new ColorCaptcha();
BufferedImage wrongImage1 = colorCaptcha.drawRandomColorBall(100, 100, session);
BufferedImage wrongImage2 = colorCaptcha.drawRandomColorBall(100, 100, session);
BufferedImage wrongImage3 = colorCaptcha.drawRandomColorBall(100, 100, session);
BufferedImage answerImage = colorCaptcha.drawAnswerColorBall(100, 100, session);
OutputStream out = response.getOutputStream(); // 현재 화면에 뿌릴 outputStream
/** 객관식 정보 담기 **/
Map<String, BufferedImage> multipleChoiceMap = new HashMap<String, BufferedImage>();
multipleChoiceMap.put("wrong1", wrongImage1);
multipleChoiceMap.put("wrong2", wrongImage2);
multipleChoiceMap.put("wrong3", wrongImage3);
multipleChoiceMap.put("answer", answerImage);
/** 객관식 번호 섞기 **/
List<String> keys = new ArrayList(multipleChoiceMap.keySet());
Collections.shuffle(keys);
/** 객관식 번호 부여하기 **/
BufferedImage questionNum1 = colorCaptcha.drawNumberOnColorBall("1");
BufferedImage questionNum2 = colorCaptcha.drawNumberOnColorBall("2");
BufferedImage questionNum3 = colorCaptcha.drawNumberOnColorBall("3");
BufferedImage questionNum4 = colorCaptcha.drawNumberOnColorBall("4");
/** key로 정답을 찾아 session 영역에 저장 **/
int answerNum = keys.indexOf("answer");
session.setAttribute("colorAnswer", answerNum + 1);
System.out.println("Color-Answer : " + (answerNum + 1) );
/** 객관식 리스트 합쳐 하나의 이미지로 재구성 **/
try {
/** 기본 바탕화면 구성 **/
BufferedImage mergedImage = new BufferedImage(600, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = (Graphics2D) mergedImage.getGraphics();
graphics.setPaint(new Color(255, 255, 255));
graphics.fillRect(0, 0, mergedImage.getWidth(), mergedImage.getHeight());
/** 객관식 리스트 **/
graphics.drawImage(multipleChoiceMap.get(keys.get(0)), 50, 0, null);
graphics.drawImage(multipleChoiceMap.get(keys.get(1)), 200, 0, null);
graphics.drawImage(multipleChoiceMap.get(keys.get(2)), 350, 0, null);
graphics.drawImage(multipleChoiceMap.get(keys.get(3)), 500, 0, null);
/** 객관식 리스트의 번호 부여 **/
graphics.drawImage(questionNum1, 70, 0, null);
graphics.drawImage(questionNum2, 220, 0, null);
graphics.drawImage(questionNum3, 370, 0, null);
graphics.drawImage(questionNum4, 520, 0, null);
/** 화면에 출력 **/
response.setContentType("image/png");
ImageIO.write(mergedImage, "png", out);
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
/** ------------ Color CaptCha Confirm ------------ **/
@GetMapping(value = "/captcha/color/answer")
public @ResponseBody Map<Object, Object> confirmCaptChaColor
(HttpServletRequest request,HttpSession session) {
Map<Object, Object> result = new HashMap<>();
/** 정답 가져오기 **/
session = request.getSession();
String answer = String.valueOf((int) session.getAttribute("colorAnswer")); // 정답
String userAnswer = request.getParameter("userAnswer"); // 유저 정답
/** 정답 확인 **/
if (userAnswer.equals(answer)) result.put("response", "right");
else result.put("response", "wrong");
return result;
}
}
Controller
package com.lsj.study.Util;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.servlet.http.HttpSession;
import com.lsj.study.Vo.ImageVo;
public class ColorCaptcha {
/**
* @param : width - 공 넓이
* height - 공 높이
* session - 사용자 Session
* @return : 문제로 낼 색칠된 공 이미지
*/
public BufferedImage drawQuestionColorBall(int width, int height, HttpSession session) {
session.removeAttribute("colorR");
session.removeAttribute("colorG");
session.removeAttribute("colorB");
BufferedImage questionImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = (Graphics2D) questionImage.getGraphics();
int colorR = (int) (Math.random() * 255);
int colorG = (int) (Math.random() * 255);
int colorB = (int) (Math.random() * 255);
session.setAttribute("colorR", colorR);
session.setAttribute("colorG", colorG);
session.setAttribute("colorB", colorB);
graphics.setColor(new Color(colorR, colorG, colorB));
int xPoint = 250;
int yPoint = 25;
int ovalWidth = 150;
int ovalHeight = 150;
graphics.fillOval(xPoint, yPoint, ovalWidth, ovalHeight); // 채우기 원
// graphics.drawOval(xPoint, yPoint, ovalWidth, ovalHeight); // 테두리 원
graphics.dispose();
return questionImage;
}
/**
*
* @param : width - 공 넓이
* height - 공 높이
* session - 사용자 Session
* @return : 랜덤으로 색칠된 오답인 공 이미지
*/
public BufferedImage drawRandomColorBall(int width, int height, HttpSession session) {
int answerR = 120;
if(session.getAttribute("colorR") != null)
answerR = (int) session.getAttribute("colorR");
BufferedImage questionImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = (Graphics2D) questionImage.getGraphics();
/** 정답과 중복 안되게 만들기 **/
int colorR;
if(answerR > 200) colorR = (int) (Math.random() * 150);
else if (answerR > 150) colorR = (int) (Math.random() * 100);
else if (answerR > 100) colorR = (int) (Math.random() * 100) + 50;
else if (answerR > 50) colorR = (int) (Math.random() * 100) + 100;
else colorR = (int) (Math.random() * 150) + 100;
int colorG = (int) (Math.random() * 255);
int colorB = (int) (Math.random() * 255);
graphics.setPaint(new Color(255, 255, 255));
graphics.setColor(new Color(colorR, colorG, colorB));
int xPoint = 0;
int yPoint = 0;
int ovalWidth = 100;
int ovalHeight = 100;
graphics.fillOval(xPoint, yPoint, ovalWidth, ovalHeight);
graphics.dispose();
return questionImage;
}
/**
* @param : width - 공 넓이
* height - 공 높이
* session - 사용자 Session
* @return : 정답인 색공 설정
*/
public BufferedImage drawAnswerColorBall(int width, int height, HttpSession session) {
int answerR = (int) session.getAttribute("colorR");
int answerG = (int) session.getAttribute("colorG");
int answerB = (int) session.getAttribute("colorB");
System.out.println("answerR : " + answerR);
System.out.println("answerG : " + answerG);
System.out.println("answerB : " + answerB);
BufferedImage questionImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = (Graphics2D) questionImage.getGraphics();
graphics.setPaint(new Color(255, 255, 255));
graphics.setColor(new Color(answerR, answerG, answerB));
int xPoint = 0;
int yPoint = 0;
int ovalWidth = 100;
int ovalHeight = 100;
graphics.fillOval(xPoint, yPoint, ovalWidth, ovalHeight);
graphics.dispose();
return questionImage;
}
/**
* @param : NumberToDraw - 공에 그릴 숫자
* @return : 오답 및 정답 색깔 공에 객관식 선택 번호 그리기
*/
public BufferedImage drawNumberOnColorBall(String NumberToDraw) {
/** 이미지화할 글자 설정 **/
int imageWidth = 550; // 문제 이미지 넓이
int imageHeight = 100; // 문제 이미지 높이
Color color = new Color(0, 0, 0); // 글자 색
Font font = new Font("TimeRoman", Font.PLAIN, 25); // 이미지 글체
ImageVo stringImage = new ImageVo(imageWidth, imageHeight, color, font);
/** 문자를 그릴 종이 만들기 **/
BufferedImage image = getWhitePaper(stringImage);
/** 문제 받아오기 **/
String question = NumberToDraw;
/** 글자 이미지가 생성될 위치 선정 **/
Graphics2D graphics2 = image.createGraphics(); // Graphics2D 와 BufferedImage는 연동 된 느낌?
/** 글자색과 글자체 받아오기 **/
graphics2.setColor(stringImage.getImageColor());
graphics2.setFont(stringImage.getFont());
/** 해당 내용으로 그리기 작업 **/
graphics2.drawString(question, 0, 40);
graphics2.dispose();
return image;
}
/**
* @param customImage : 그림 그릴 종이 크기 설정
* @return : 그림 그릴 종이
*/
public BufferedImage getWhitePaper(ImageVo customImage) {
// 글자를 그릴 종이 크기 설정
int width = customImage.getImageWidth();
int height = customImage.getImageHeight();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
return image;
}
}
Color Ball Class
package com.lsj.study.Vo;
import java.awt.Color;
import java.awt.Font;
public class ImageVo {
private int imageWidth;
private int imageHeight;
private Color imageColor;
private Font font;
public ImageVo(int imageWidth, int imageHeight, Color imageColor, Font font) {
this.imageWidth = imageWidth;
this.imageHeight = imageHeight;
this.imageColor = imageColor;
this.font = font;
}
public int getImageWidth() {
return imageWidth;
}
public void setImageWidth(int imageWidth) {
this.imageWidth = imageWidth;
}
public int getImageHeight() {
return imageHeight;
}
public void setImageHeight(int imageHeight) {
this.imageHeight = imageHeight;
}
public Color getImageColor() {
return imageColor;
}
public void setImageColor(Color imageColor) {
this.imageColor = imageColor;
}
public Font getFont() {
return font;
}
public void setFont(Font font) {
this.font = font;
}
}