스프링 부트와 의존주입 그리고 웹 기초

gdhi·2023년 11월 27일
post-thumbnail

  1. 어려워도 하던 것 처럼 다른 예시로 이해하든 구글링해서 이해하든 어떻게든 일단 이해부터 하자.
  2. Oracle, MyBatis는 잘 쓰지 않는다. DataBase와 연관 없이 애초에 프레임워크라서 설정에 SQL을 넣고 시작한다(Java에서 설정). 일반 Spring에서 MyBatis을 사용 하긴 한다.

❓Spring 공부방법

  1. 웹이 어떤 식으로 동작하는지 구조 파악 👉 숲을 보자
  2. 세부적으로 어떤게 동작하는지 알기 👉 나무를 보자









📖Spring Boot?

Spring 은 2가지 종류가 있다

  • Spring(일반) : 스프링 프레임워크(Spring Framework)는 자바 플랫폼을 위한 오픈 소스 애플리케이션 프레임워크로서 간단히 스프링(Spring)이라고도 한다. 동적인 웹 사이트를 개발하기 위한 여러 가지 서비스를 제공하고 있다. 대한민국 공공기관의 웹 서비스 개발 시 사용을 권장하고 있는 전자정부 표준프레임워크의 기반 기술로서 쓰이고 있다. 사용되는 기본 개념 링크

    프레임 워크

    사전적 의미로는 "소프트웨어 어플리케이션이나 솔루션의 개발을 수월하게 하기 위해 소프트웨어의 구체적 기능들에 해당하는 부분의 설계와 구현을 재사용 가능하도록 협업화된 형태로 제공하는 소프트웨어 환경" 이라고 정의가 되어있다.
    간단하게 생각해서 어플리케이션(=소프트웨어)을 편리하고 효율적으로 제작하기 위해, 뼈대가 되는 클래스들과 인터페이스로 구성된 일종의 기본 설계 틀이라고 생각하면 된다


  • Spring Boot(일반) : 빠른 Spring, 우리는 이걸로 배운다.



📌스프링 부트 특징

  1. 웹 서버 내장, Tomcat필요 없다.
  2. 라이브러리 관리를 위한 스프링 부트 스타터 제공
  3. Xml 설정을 사용하지 않는다
  4. 제어 역전 IOC
  5. 의존성 주입 DI
  6. 관점 지향 프로그래밍 AOP

❗ 초반이라 정확 하지 않을 수 있다. 나중에 따로 공부하며 수정해 나가자.









📖IntelliJ로 스프링 부트 시작하기


📌https://start.spring.io/

Kotlin
이전까지 Android 개발 언어는 JAVA로 많이 사용됐지만,
2017년 5월 Google I/O에서 Kotlin을 공식 언어로 채택한 후,
Kotlin으로 앱을 개발하는 기업들이 많아졌다.
실제 Kotlin은 2011년에 출시되었고, 2012년에 오픈소스 프로젝트로 공개되었다. 공식 언어는 2017년에 채택되었지만 출시는 2011년도. 위 사진 처럼 Spring에서도 쓰인다는 것

👉 안전하게 17버전으로

Gradle
Groovy 기반으로 한 BUILD TOOL

👉 웹 개발은 의존성이 필요

👉 Spring 웹 개발의 제일 기본의 의존성

👉 HelloWorld 하나로 합치자



📌IntelliJ


📍HelloWorld 프로젝트 열기


📍bulid.gradle 확인하기

plugins {
	id 'java' // 자바 연결
	id 'org.springframework.boot' version '3.2.0' // 스프링 부트 버전 3.2.0
	id 'io.spring.dependency-management' version '1.1.4' // 스프링 의존 관리 버전 1.1.4
}

group = 'com.study' // 회사명
version = '0.0.1-SNAPSHOT' // 건들지 말자

java {
	sourceCompatibility = '17' // 자바 17버전
}

repositories {
	mavenCentral() // Maven repository 사용
}

dependencies { // 의존성
	implementation 'org.springframework.boot:spring-boot-starter-web' // 실제 의존성
	testImplementation 'org.springframework.boot:spring-boot-starter-test' // 테스트 용 의존성
}

tasks.named('test') { // 클라우드 환경에서 테스트 중요하다..
	useJUnitPlatform() // 테스트에서 OK면 바로 올라간다. 테스트 설정 JUnit
}

📍gradle 폴더 확인하기


distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 👉 버전 정보, 변경 가능하다

📍src 폴더 확인하기

  • src : main(소스) - test(테스트 소스)
  • .gitignore : git 사용 제거 항목 설정
  • gradlew, gradlew.bat : 리눅스 web 빌더를 사용하기 위한 jar 생성시 사용
  • Help.md : 도움말
  • settings.gradle : gradle 설정

📍application.properties

❗ 절대 틀리면 안된다


📍HelloWorldApplication

👉 빌드, Springboot는 일반 Spring과 다르게 Main문이 있다.


👉 서버 실행이 잘 됐다

package com.study.HelloWorld;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication //어노테이션, 애너테이션 SpringBootApplication : 스프링 부트 어플리케이션 사용
public class HelloWorldApplication {
	// 메인문
	public static void main(String[] args) { // args는 옵션을 주는 것, 따로 공부하자
		// 스프링 어플리케이션 실행 메소드
		// 매개변수 : HelloWorldApplication.class
		SpringApplication.run(HelloWorldApplication.class, args);
	}
}









📖⭐의존 주입의 이해⭐

DI(Dependenct Injection) : Has관계
부모 관계는 아니지만 없으면 사용이 안된다. 예) 자동차의 엔진은 부모 자식 관계는 아니지만 엔진이 없으면 자동차가 굴러가지 않는다.

👉 Java 에서의 의존 주입, Spring 에선 이렇게 안한다.


👉 Spring 에서의 의존 주입, 제어의 역전(싱글톤) , 스프링 컨테이너 @로 사용. 객체 개념을 잘 알고 있어야 에러 등 쉽게 잡을 수 있다. 여러 가지로 참고할 자료들이 많다. 공부 필요


📌강한 결합 vs 약한 결합(자바를 알면 안봐도 된다)

  • 강한 결합 : 객체 간의 의존 관계에서 직접 객체를 생성하면 생성부터 메모리 관리를 위한 소멸까지 해당 객체의 라이프사이클을 개발자가 다 관리
  • 약한 결합 : 누군가가 생성한 객체를 주입받을 경우, 사용하기만 하면 된다.

📍이해를 위한 UnderstandDI 클래스

package DITest;

class Member{
    String name;
    String nickname;
    public Member(){

    }
}

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

    }
    public static void memberUser1(){
        // 강한 결합 : 직접 생성
        Member m1 = new Member();
    }

    // 약한 결합 : 생성된 것을 주입 받음 - 의존 주입(DI)
    public static void memberUser2(Member m){
        Member m2 = m;
    }
}



📌자바 코드로 DI 사용하기(자바를 알면 안봐도 된다)

📍com.study.springboot.bean 패키지 생성

Printer 인터페이스 클래스 생성

package com.study.springboot.bean;

public interface Printer {
    public void print(String message);
}



PrinterA 상속 클래스 생성

package com.study.springboot.bean;

public class PrinterA implements Printer {
    @Override
    public void print(String message){
        System.out.println("Printer A : " + message);
    }
}



PrinterB 상속 클래스 생성

package com.study.springboot.bean;

public class PrinterB implements Printer {
    @Override
    public void print(String message){
        System.out.println("Printer B : " + message);
    }
}



Member 클래스 생성

package com.study.springboot.bean;

public class Member {
    private String name;
    private String nickname;
    private Printer printer;
    public Member(){}

    public Member(String name, String nickname, Printer printer) {
        this.name = name;
        this.nickname = nickname;
        this.printer = printer;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public void setPrinter(Printer printer) {
        this.printer = printer;
    }
    
    public void print(){
        printer.print("Hello " + name + " : " + nickname);
    }
}



위 기능들을 사용하는 Config 클래스

package com.study.springboot.bean;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration // Spring에서 관리하는 어노테이션, 객체가 필요 없다
public class Config {
    // 빈(bean) : 스프링이 IoC 방식으로 관리하는 객체
    // 빈 팩토리(BeanFactory) : 스프링의 IoC를 담당하는 핵심 컨테이너
    // 어플리케이션 컨텍스트(ApplicationContext) : 빈 팩토리를 확장한 IoC 컨테이너

    @Bean
    public Member member1(){ // 싱글톤으로 제공
        // Setter Injection (Setter 메소드를 이용한 의존성 주입)
        Member member1 = new Member();
        member1.setName("강동원");
        member1.setNickname("도사");
        member1.setPrinter(new PrinterA());

        return member1;
    }

    @Bean(name = "hello") // member1 이 있기 때문에 이름을 준 것
    public Member member2(){
        // COnstructor Injection (생성자를 이용한 의존성 주입)
        return new Member("전우치", "도사", new PrinterA());
    }

    @Bean
    public PrinterA printerA(){
        return new PrinterA();
    }

    @Bean
    public PrinterB printerB(){
        return new PrinterB();
    }
}



위 클래스들을 사용하는 DI 클래스

package com.study.springboot.bean;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

// @SpringBootApplication
public class DI {
    public static void main(String[] args) {
        //SpringApplication.run(DI.class, args);

        // 1. IoC 컨테이너 생성
        ApplicationContext context =
                new AnnotationConfigApplicationContext(Config.class);

        // 2. Member Bean 가져오기
        Member member1 = (Member) context.getBean("member1");
        member1.print();

        // 3. Member Bean 가져오기
        Member member2 = context.getBean("hello", Member.class);
        member2.print();

        // 4. PrinterB Bean 가져오기
        Printer printer = context.getBean("printerB", Printer.class);
        member1.setPrinter(printer);
        member1.print();
    }
}

❗ 한글 깨지면

-Xmx2048m
-Dfile.encoding=UTF-8
-Dconsole.encoding=UTF-8

👉 추가

👉 변경



두 객체가 같은지 비교(싱글톤인가?)해보자

package com.study.springboot.bean;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

// @SpringBootApplication
public class DI {
    public static void main(String[] args) {
        //SpringApplication.run(DI.class, args);

        // 1. IoC 컨테이너 생성
        ApplicationContext context =
                new AnnotationConfigApplicationContext(Config.class);

        // 2. Member Bean 가져오기
        Member member1 = (Member) context.getBean("member1");
        Member member3 = (Member) context.getBean("member1");
        member1.print();

        // 3. Member Bean 가져오기
        Member member2 = context.getBean("hello", Member.class);
        member2.print();

        // 4. PrinterB Bean 가져오기
        Printer printer = context.getBean("printerB", Printer.class);
        member1.setPrinter(printer);
        member1.print();

        // 5. 싱글톤인지 확인
        if(member1 == member2){
            System.out.println("1. 동일한 객체입니다.");
        }
        else {
            System.out.println("1. 서로 다른 객체입니다.");
        }

        if(member1 == member3){
            System.out.println("2. 동일한 객체입니다.");
        }
        else {
            System.out.println("2. 서로 다른 객체입니다.");
        }

    }
}


👉 1. member1 = member2 서로 다른 객체
2. member1 = member3 동일한 객체
싱글톤(Bean)이다. 하나의 객체를 돌려 쓰는 것(공중목욕탕)



📌🔥어노테이션으로 DI 사용하기🔥

🔥[Spring] Annotation 정리🔥


📍com.study.springboot.annotation 패키지 생성


Member 클래스

package com.study.springboot.annotation;

import com.study.springboot.annotation.Printer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component // 👉 @ComponentScan을 사용하여 @Bean을 사용하지 않아도 된다.
public class Member {
    @Value("강동원")
    private String name;
    @Value("도사")
    private String nickname;
    @Autowired // ⭐자동 연결, 엄청 많이 쓴다.⭐
    // 자동으로 연결 할 건데 컨테이너에 객체가 여러 개이면 이름(printerA)보고 찾는 것
    @Qualifier("printerA")
    private Printer printer;
    public Member(){}

    public Member(String name, String nickname, Printer printer) {
        this.name = name;
        this.nickname = nickname;
        this.printer = printer;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public void setPrinter(Printer printer) {
        this.printer = printer;
    }

    public void print(){
        printer.print("Hello " + name + " : " + nickname);
    }

}





PrinterA 클래스

package com.study.springboot.annotation;

import com.study.springboot.annotation.Printer;
import org.springframework.stereotype.Component;

@Component("printerA")
public class PrinterA implements Printer {
    @Override
    public void print(String message){
        System.out.println("Printer A : " + message);
    }
}



PrinterB 클래스

package com.study.springboot.annotation;

import com.study.springboot.annotation.Printer;
import org.springframework.stereotype.Component;

@Component("printerB")
public class PrinterB implements Printer {
    @Override
    public void print(String message){
        System.out.println("Printer B : " + message);
    }
}


⭐MyController 클래스⭐

package com.study.springboot.annotation;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class MyController {
    @Autowired
    Member member1; // New 생성자() 필요 없이 Spring 컨테이너 에서 Type에 따라 알아서 Bean을 주입 해준다
    @Autowired
    @Qualifier("printerB")
    Printer printer;
    @Autowired // New 생성자() 필요 없이 Spring 컨테이너 에서 Type에 따라 알아서 Bean을 주입 해준다
    Member member2;


    @RequestMapping("/") // http://localhost:8081/
    public @ResponseBody String root(){

        // 1.Member Bean 가져오기
        member1.print();

        // 2. PrinterB Bean 가져오기
        member1.setPrinter(printer);
        member1.print();

        // 3. 싱클톤인지 확인
        if (member1 == member2){
            System.out.println("동일한 객체입니다.");
        }
        else {
            System.out.println("서로 다른 객체입니다.");
        }

        return "Annotation 사용하기";

    }

}

DI 클래스

package com.study.springboot.annotation;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

@SpringBootApplication // @Configuration, @EnableAutoConfiguration, @ComponentScan 3가지를 하나의 애노테이션으로 합친 것
public class DI {
    public static void main(String[] args) {
        SpringApplication.run(DI.class, args);
    }
}

👉 실행









📖Web 기초


📌정적 리소스 사용하기

스프링 부트 프로젝트에서 js, css, image 등 정적인 요소를 사용
1. FreeMarker
2. Groovy
3. ⚡Thymleaf : View 파일 👉 HTML(동적으로 만들어 준다)
4. Velocity
5. ⚡JSP : 제공되는 스타터가 없어 Spring으로 추가⚡



📌예제 만들기 webBasic 프로젝트 생성

❗폴더 겹치는 거 하나로 합칠 것


📍index.html 생성

👉 staticsub, images 폴더까지 생성



index.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>


Hello World <br>
안녕하세요 <br>

<img src="images/21355235.jpg">


</body>
</html>



test.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>


        Hello(Sub)


</body>
</html>



Main의 WebBasicApplication

실행하여 웹 실행



📌JSP 사용하기


📍bulid.gradle

JSTL

jasper

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.2.0'
	id 'io.spring.dependency-management' version '1.1.4'
}

group = 'com.webBasic'
version = '0.0.1-SNAPSHOT'

java {
	sourceCompatibility = '17'
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	implementation 'javax.servlet:jstl:1.2'
	implementation 'org.apache.tomcat:tomcat-jasper:10.1.16'

}

tasks.named('test') {
	useJUnitPlatform()
}



📍Dynamic 폴더 만들기

직접 만들어야 한다..


📍application.properties

server.port=8081
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

📍test1.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<%
    out.println("Hello World");
%>
</body>
</html>

📍test2.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<%
    out.println("Hello World (sub)");
%>
</body>
</html>

📍MyController 클래스

package com.webBasic.webBasic;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class MyController {
    @RequestMapping("/")
    public @ResponseBody String root(){
        return "JSP in Gradle";
    }

    @RequestMapping("/test1")
    public String test1(){
        return "test1";
    }

    @RequestMapping("/test2")
    public String test2(){
        return "sub/test2";
    }
}


📍실행


❓🔥어떤 구조로 실행?🔥

  1. bulid.gradle
  • 'javax.servlet:jstl:1.2' : JSTL
  • 'org.apache.tomcat:tomcat-jasper:10.1.16' : JSP
    땡겨온다
  1. application.properties
server.port=8081
spring.mvc.view.prefix=/WEB-INF/views/ 👉 MVE View 사전경로를 /WEB-INF/views/ 
spring.mvc.view.suffix=.jsp
👉 확장자 명을 .jsp로

설정
3. WebBasicApplication.java
서버를 실행하고 @SpringBootApplication로 확인 👉 @Controller

@SpringBootApplication
public class WebBasicApplication {
  1. MyController
    1) @RequestMapping("/") : public @ResponseBody String root() 👉 바로 문자가 넘어간다.
    2) @RequestMapping("/test1") : public String test1() 👉 /WEB-INF/views/test1.jsp prefix, suffix 로 넘어간다
    3) @RequestMapping("/test2") : public String test2() 👉 /WEB-INF/views/sub/test2.jsp prefix, suffix 로 넘어간다



📌⭐모델 사용하기 Model 프로젝트 생성⭐

❗폴더 겹치는 거 하나로 합칠 것


📍build.gradle

jstl api
Jakarta Servlet api
Jakarta Standard Tag Library

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.2.0'
	id 'io.spring.dependency-management' version '1.1.4'
}

group = 'com.Model'
version = '0.0.1-SNAPSHOT'

java {
	sourceCompatibility = '17'
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	implementation 'jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:3.0.0'
	compileOnly 'jakarta.servlet:jakarta.servlet-api:6.0.0'
	implementation 'org.glassfish.web:jakarta.servlet.jsp.jstl:3.0.1'
	implementation 'org.apache.tomcat:tomcat-jasper:10.1.16'
}

tasks.named('test') {
	useJUnitPlatform()
}



📍application.properties

server.port=8081
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp



📍webapp 폴더 생성



📍MyController 클래스

package com.Model.Model;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import java.util.ArrayList;
import java.util.List;

@Controller
public class MyController {
    @RequestMapping("/")
    public @ResponseBody String root(){
        return "Model & View";
    }

    @RequestMapping("/test1")
    public String test1(Model model){
        // Model 객체를 이용해서, view로 Data 전달
        // 데이터만 설정이 가능
        model.addAttribute("name", "홍길동");

        return "test1";
    }

    @RequestMapping("/mv")
    public ModelAndView test2(){
        // 데이터와 뷰를 동시에 설정이 가능
        ModelAndView mv = new ModelAndView();

        List<String> list = new ArrayList<>();

        list.add("test1");
        list.add("test2");
        list.add("test3");

        mv.addObject("lists", list);    // jstl로 호출
        mv.addObject("ObjectTest", "테스트입니다."); // jstl로 호출
        mv.addObject("name", "홍길동"); // jstl로 호출
        mv.setViewName("view/myView");

        return mv;
    }
}



📍test1.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
  pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <%
    out.println("Model : Hello World");
    %>
    <br>
    당신의 이름은 ${name} 입니다.
</body>
</html>



📍myView.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
  pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset<="UTF-8">
    <title>Document</title>
</head>
<body>
    <%
    out.println("Model(sub) : Hello World");
    %>
    <br>
    ${ObjectTest}
    <br>
    ${lists}
    <br>
    <br>
    <c:forEach var="mylist" items="${lists}">
        ${mylist} <br>
    </c:forEach>
    <br>
    <br>
    당신의 이름은 ${name}입니다.
</body>
</html>

📍실행 결과

📍⭐어떤 구조로 실행? Model 객체!!!⭐

1~3 은 위와 동일
4. MyController
1) test1의 동작

 @RequestMapping("/test1")
    public String test1(Model model){
        // Model 객체를 이용해서, view로 Data 전달
        // 데이터만 설정이 가능
        model.addAttribute("name", "홍길동");
        return "test1";
    }

❓메소드 + 메개변수??
Model(UI Model, MVC Model과 다르다) : Spring에서 제공되는 클래스
url 호출 될 때 매개변수로 사용해도 에러 ❌
👉 MVC에서 ControllerView 로 정보를 보낼 때 객체에 정보를 담아 보낸다.⭐

2) test2의 동작

@RequestMapping("/mv")
    public ModelAndView test2(){
        // 데이터와 뷰를 동시에 설정이 가능
        ModelAndView mv = new ModelAndView();
        List<String> list = new ArrayList<>();
        list.add("test1");
        list.add("test2");
        list.add("test3");
        mv.addObject("lists", list);    // jstl로 호출
        mv.addObject("ObjectTest", "테스트입니다."); // jstl로 호출
        mv.addObject("name", "홍길동"); // jstl로 호출
        mv.setViewName("view/myView");
        return mv;
    }

ModelAndView : 데이터 모델과 화면 출력 View를 한 번에 하겠다.
⭐결국 ModelModelAndViewrequest.setAttribute()와 비슷한 역할이지만 한 단계 더 나아갔다.
보통 Model을 많이 쓴다.⭐

0개의 댓글