public class JavaProject {
// Java를 처음 접하면서 보게되는 코드
public static void main(String[] args) {
System.out.println("Hello World");
}
}
스프링의 근간이 되는 언어이다 스프링이 책이라고 하면 자바는 한글이다
스프링자체도 거의 대부분 자바로 만들어져 있고, 스프링은 자바뿐만아니라 코틀린,그루비로도 사용할 수 있다.
요즘은 자바에서 코틀린으로 넘어가는 추세인거 같기도하기때문에 코틀린에대해서도 공부를 해야겠다!
자바를 이용해서 어플리케이션을 쓰기위해 활용하는 프레임워크다(여러툴이있는 템플릿)
스프링은 갑자기 툭튀어나온게 아니다. 웹이 활성화되면서 상호간의 응답이 필요한 어플리케이션이 필요해졌고 서블릿이 나오기 시작했고 서블릿을 만들기위한 스펙이 J2EE라는 프레임워크나 스펙들이 나오기 시작했다(EJB). 다루기가 많이 어려웠다고 한다. 그렇게 때문에 스프링이라는것을 만들게 되었다.
자바, 서블릿, J2EE >>>>> 스프링 프레임워크
스프링은 라이브러리와 다르게 프레임워크이다. 프레임워크는 큰영역의 틀이라고 생각하면된다.
스프링프레임워크에는 이렇게 많은 기능들을 담고있는데 그 중에서 적합한 툴을 선택하여 사용것이 좋다.
스프링보다 한층 더 편리한 프레임워크라고 생각한다 내가 사용을 해보면서 dependency를 추가하거나 자바 버젼 세팅등등 start.spring.io에서 세팅을 해서 바로 띄울수있는 있다. 자동설정, 설정 표준화가 되어있어 손쉽게 접근할 수있고 원한다면 모두 마음대로 설정을 할 수있다는 장점이 있었다.
웹 어플리케이션 서버가 내장되어있고 기본적으로 톰켓이 내장되어있어서 따로 세팅을 해줄 필요가없는 장점이있었다.
스프링 프레임워크를 사용하면서 따라하기만 하면 누구나 만들수있다 하지만 프로젝트나 결과물을 만들면서 따라하기만 하면 이게 왜 이렇게 동작하는지 이해를 못할때가 있다고 생각한다.. 물론 나도 그렇고 그래서 스프링 프레임워크의 기술들 종류들을 알아보고 왜 이렇게 동작하는지에 대해서 알아볼려고한다
스프링을 공부하면서 매우 많이 나오는 핵심기술들이다.
내가 만든 클래스를 스프링이 직접 관리하여 애플리케이션을 동작하게 한다.
공통적인 코드를 프레임워크 레벨에서 지원해주는 방법
검증, 외부에서 받은 데이터를 객체로 담아내는 방법
스프링 내부에서 설정이 들어있는 파일들에 접근하는 동작원리
설정값들을 외부에서 주입을 받을 때 활용
짧은 표현식을 통해 필요한 데이터나 설정 값을 얻어올 수 있게 하는 특별한 형태의 표현식에 가까운 간편한 언어
스프링이라는 건 다양한 기능들을 계속 발전시켜가면서 제공하고 있기때문에 모든 기능에 대해서 다양한 가능성(다양한모듈)을 사용가능하다. 심지어 외부 모듈을 활용가능하다.
-> 이런 높은 자유도가 스프링을 어렵게 하는 요소이다.
이전 버전과의 강력한 호환성,API 디자인을 섬세하게 노력하기때문에 스프링 코드 자체가 하나의 좋은 참고 소스이므로 시간이 날때마다 스프링 내부 코드들을 까고 분석 해볼예정이다.
IoC나 DI는 레고 같은것이라고 강사 님이 말씀해주셨다 스프링은 바닥판에 깔려있고 우리는 그 위에 나의 어플리케이션을 만들어서 붙이면 된다라고 생각하면 된다고하셨다.
나도 처음에 Spring을 접했을 때는 IoC? DI가 뭐지? 에이 그냥 외워야지 했는데 막상 다시 공부를 하면서 천천히 읽어보니 Spring이 Class들을 관리를 하고 내가 관리할 필요가 없는 방법이구나 라고 느끼게되었다.
DI랑 IoC를 배울때 Bean이라는 용어가 나오는데 이에 대해서 알아보겠다!
행위들은 없고 데이터를 저장하기위한구조체로 자바 빈 규약이라는 것을 따르는 구조체
private 프로퍼티와 getter/setter로만 데이터를 접근한다.
인수(argument)가 없는 기본 생성자가 있음!
public class JavaBean {
private String id;
private Integer count;
public JavaBean(){}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
}
우리가 만든 A,B,C라는 클래스를 빈으로 등록을 하면 Spring에 있는 IoC컨테이너에 담겨진다.
ApplicationContext 인터페이스를 통해 제공되는 스프링 컨테이너는 Bean객체의 생성 및 Bean들의 조립(상호의존성 관리)를 담당함
과거에는 xml로 설정을 따로 관리하여 등록했다-> 나도 예전 강의를 보면서 xml에 빈등록하는 것을 본적이 있다 매우 불편하다고 느꼈는데
현재에는 annotation기반으로 Bean을 등록하며 예를 들면 @Bean, @Controller, @Service등이 있다.
@Controller, @Service도 빈으로 등록됨
빈은 default가 싱글톤이기 때문에 애플리케이션이 실행될때 생성이된다.
빈이 생성/변경/파괴할때 특정 작업을 해줘야한다. 이때 Bean LifeCycle callback을 이용한다
callback은 어떤이벤트발생시 호출되는 메소드 이다.
lifeCycle callback은 Bean을 생성하고 초기화하고 파괴하는 등 특정 시점에 호출되도록 정의된 함수이다.
관점지향..? 공통적인 부분을 Spring이 도와서 처리해준다.. 라고 강사님이 설명해주셨다.. 근데 아직은 잘모르겠지만 정리를 하면서 공부해보겠다.
AOP란 특정한 함수 호출전이나 호출후 뭔가 공통적인 처리가 필요하면 AOP를 사용한다
그 예중 로깅, 트랜잭션(스프링 MVC 프로젝트를 하다보면 @Transaction을 붙이게 되는데 내부적으로는 AOP가 대신 공통된 기술을 해준다) ,인증등이 있다.
여러 클래스나 기능에 걸쳐서 있는 관심사, 그것들을 모듈화함
AOP중에서 가장 많이 활용되는 부분은 @Transactional(트랜잭션관리) 기능
AOP에서 실제로 적용하는 기능(로깅,트랜잭션,인증 등)을 뜻함
프로그램이 진행을 하고 있을때 일련의 흐름들이 이어지고 있을때 모든 흐름에 AOP를 심을 수 없다. 특정 심을 수 있는 point들을 Join Point라고 함
즉, 모듈화된 특정 기능이 실행될 수 있는 연결 포인트
Join Point중에서 해당 Aspect를 적용할 대상을 뽑은 조건식이다.
Advice가 적용될 오브젝트
대상 오브젝트에 Aspect를 적용하는 경우 Advice를 덧붙이기 위해 하는 작업
주로 CGLIB(Code Generation Library, 실행 중에 실시간으로 코드를 생성하는 라이브러리) 프록시를 사용하여 프록싱 처리를 함
Advice를 비지니스 로직 코드에 삽입하는 것을 말함
AOP를 활용할때 주로 사용하는 라이브러리
AOP를 제대로 사용하기 위해 꼭 필요한 라이브러리
기본적으로 Spring boot에 포함이 되어있다.
기본적으로 제공되는 Spring AOP로는 다양한 기법(Pointcut 등)의 AOP를 사용할 수 없음
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
@Component // Component를 붙인 것은 해당 Aspect를 스프링의 Bean으로 등록해서 사용하기 위함
public class UsefulAspect {
}
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
@Component // Component를 붙인 것은 해당 Aspect를 스프링의 Bean으로 등록해서 사용하기 위함
public class UsefulAspect {
@Pointcut("execution(* transfer(..))") //- 포인트컷 표현식
private void anyOldTransfer() {}
}
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
@Component // Component를 붙인 것은 해당 Aspect를 스프링의 Bean으로 등록해서 사용하기 위함
public class UsefulAspect {
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {} //public 메서드 대상 포인트 컷
@Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {} // 특정 패키지 대상 포인트 컷
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {} // 위의 두 조건을 and(&&) 조건으로 결합한 포인트 컷
}
포인트컷을 활용하여 포인트컷의 전/후/주변에서 실행될 액션을 정의
dataAccessOperation()이라는 미리 정의된 포인트 컷의 바로 전에 doAccessCheck가 실행
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
dataAccessOperation()라는 미리 정의된 포인트컷에서 return이 발생된 후 실행
로깅, 알림을 보내는 것들이 필요할 수 있다
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
특정 서비스(여기서는 businessService()라는 포인트컷 전/후에 필요한 동작을 추가함)
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class AroundExample {
@Around("com.xyz.myapp.CommonPointcuts.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed(); //-ProceedingJoinPoint(우리가 잡아둔 포인트컷)의 메소드를 실행함
// stop stopwatch
return retVal;
}
}
유효성검증이라고하며 주로 사용자또는 서버의 요청(http request)내용에서 잘못된 내용이 있는 지 확인하는 단계를 뜻한다
스프링은 웹 레이어에 종속적이지 않은 방법으로 밸리데이션을 하려고 의도하고 있으며 주로 아래 두가지 방법을 활용하여 밸리데이션 진행(둘다 데이터 검증에 가까움)
비지니스 검증은 스프링이 도와줄수없는 레이어라고 하셨다
JavaBean 기반으로 간편하게 개별데이터를 검증이며 요즘에 가장 많이 활용되는 방법중하나로
JavaBean내에 어노테이션으로 검증방법을 명시한다
public class MemberCreationRequest {
@NotBlank(message="이름을 입력해주세요.")
@Size(max=64, message="이름의 최대 길이는 64자 입니다.")
private String name;
@Min(0, "나이는 0보다 커야 합니다.")
private int age;
@Email("이메일 형식이 잘못되었습니다.")
private int email;
// the usual getters and setters...
}
name, age,email이라는 세개의 property가 있다 그리고 각각 프로퍼티내에 검증 내용들을 어노테이션으로 명시해뒀다 이렇게 간단하게 에노테이션을 붙여두면 심플하게 Validation이 가능해진다.
위처럼 요청 dto에 어노테이션으로 명시 후 아래처럼 @Valid 어노테이션을 해당 @RequestBody에 달게 되면, Java Bean Validation을 수행한 후 문제가 없을 때만 메서드 내부로 진입이 된다.
@PostMapping(value = "/member")
public MemeberCreationResponse createMember(
@Valid @RequestBody final MemeberCreationRequest memeberCreationRequest) {
// member creation logics here...
}
검증중에 실패가 발쌩하면 MethodArgumentNotValidException이 발생
Person이라는 객체를 Javabean으로 만들어 두는 예시가 있다.
public class Person {
private String name;
private int age;
// the usual getters and setters...
}
public class PersonValidator implements Validator {
/**
* This Validator validates only Person instances
*/
public boolean supports(Class clazz) {
return Person.class.equals(clazz);
}
public void validate(Object obj, Errors e) {
ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
Person p = (Person) obj;
if (p.getAge() < 0) {
e.rejectValue("age", "negativevalue");
} else if (p.getAge() > 110) {
e.rejectValue("age", "too.darn.old");
}
}
}
Validator라는 인터페이스를 구현하여 해당 Person이라는 해당 인스턴스에만 활용되는 validator를 만든 예제이다
인터페이스의 두메서드는 아래와 같은 역할을한다
supports메서드: 이 validator가 동작할 조건을 정의하며 주로 class의 타입을 비교한다
validate메서드: 객체의 이름, 나이를 비교하는 validate를 진행한다
Javabean validation을 주로 사용한다고 강사님께서 말씀하심
장점: JavaBean Validation에 비해 조금더 복잡한 검증이 가능하다 예를 들어 두개의 데이터를 비교해서 검증한다던가
단점 : Validation을 수행하는 코드가 JavaBean Validation에 비해 찾기가 어렵다,
완전히 데이터만 검증하는 것이 아니기 때문에 일부 비즈니스적인 검증이 들어가는 경우가 있음
→ 이 경우 비즈니스 검증 로직이 여러 군데로 흩어지기 때문에 잘못된 검증(중복 검증, 다른 정책을 따르는 검증)을 수행할 가능성이 높아짐
사용자나 외부 서버의 요청 데이터를 특정 도메인 객체에 저장해서 우리 프로그램에 Request에 담아주는 것을 뜻한다. 이때 사용되는 내부원리에 대해서 간단히 알아보자
S(Source)라는 타입을 받아서 T(Target)이라는 타입으로 변환해주는 Interface임
package org.springframework.core.convert.converter;
public interface Converter<S, T> {
T convert(S source);
}
이 인터페이스는 강사님이 사용한 경험은 파라미터에 json형식의 문자열이 담겨오는 경우 해당 문자열을 곧바로 특정 dto를 담고싶을때 사용했다
// 요청
GET /user-info
x-auth-user : {"id":123, "name":"Paul"} //-json형식으로 id,name이 넘어온다
// 유저 객체
public class XAuthUser {
private int id;
private String name;
// the usual getters and setters...
}
@GetMapping("/user-info")
public UserInfoResponse getUserInfo(
@RequestHeader("x-auth-user") XAuthUser xAuthUser){
// get User Info logic here...
}
헤더에 json형식으로 담긴 문자열을 XAuthUser객체에 바로 담고싶은 경우 Converter를 사용하는 것이 좋다
Converter를 아래와 같이 Bean으로 등록하면된다
@Component //-Converter를 아래와 같이 Bean으로 등록하면된다
public class XAuthUserConverter implements Converter<String, XAuthUser> {
@Override
public XAuthUser convert(String source) {
return objectMapper.readValue(source, XAuthUser.class);
}
}
Json은 String형이고 이 값들을 XAuthUSer로 변환해준다
이와 비슷하게 PathParameter나 기타 특수한 경우 데이터를 특정 객체에 담고 싶은 경우
1. Converter를 만들어서 Spring에 Bean으로 등록
2. 스프링 내 ConversionService라는 내장된 서비스로 Converter 구현체 Bean들을 Converter리스트에 등록
3. 외부데이터가 들어오고 , Source Class Type-> Target Class Type이 Converter에 등록된 형식과 일치하면 해당 Converter가 동작하는 원리
이러한 과정을 통해 requestbody에 특정한 문자열(JSON)이 들어왔을때 Bean에 등록된 Converter가 우리의 requestDTO에 담기게 되는것이다.
특정 객체 와 String간의 변환을 담당(Converter의 일종이라고 생각할 수 있다)
Converter와 다른점은 response,request에도 활용을 하는 Converter이다
예시)
아래의 코드는 Date와 String간의 변환을 수행하는 Formatter이다
package org.springframework.format.datetime;
public final class DateFormatter implements Formatter<Date> {
public String print(Date date, Locale locale) {
return getDateFormat(locale).format(date);
}
public Date parse(String formatted, Locale locale) throws ParseException {
return getDateFormat(locale).parse(formatted);
}
// getDateFormat 등 일부 구현은 핵심에 집중하기 위해 생략...
}
Formatter도 Converter와 마찬가지로 Spring Bean으로 등록하면 자동으로 ConversionService에 등록시켜주기 때문에 필요(요청/응답 시 해당 데이터 타입이 있는 경우)에 따라 자동으로 동작하게 된다.
java.net.URL의 한계(classpath 내부 접근이나 상대경로 등)를 넘어서기 위해 스프링에서 추가로 구현
java.net.URL은 외부 url접근 기능이 주로있는것이다.
Spring의 내부 동작을 이해하기 위해서 필요한 부분이다.
public interface Resource extends InputStreamSource {
boolean exists();
boolean isReadable();
boolean isOpen();
boolean isFile();
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
ReadableByteChannel readableChannel() throws IOException;
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String relativePath) throws IOException;
String getFilename();
String getDescription();
}
java.net.URL을 래핑한 버전, prefix로 다양한 종류(ftp:, file:, http:, 등의 prefix로 접근유형 판단)의 Resource에 접근 가능하지만 기본적으로는 http(s)로 원격접근
스프링 프로젝트를 빌드한 결과물이 특정 폴더로 자동으로 들어가게된다 classpath(소스코드를 빌드한 결과(기본적으로 target/classes 폴더))하위의 리소스 접근시 사용한다
특정 파일경로를 딱찝어서 File을 다루기 위한 리소스 구현체
Servlet 어플리케이션 루트 하위 파일, InputStream, ByteArrayInput 스트림을 가져오기 위한 구현체
스프링 프로젝트 내 Resource(파일 등)에 접근할 때 사용하는 기능(인터페이스라고생각하면된다)
기본적으로 applicationContext에서 구현이 되어 있다.
프로젝트 내 파일(주로 classpath 하위 파일)에 접근할 일이 있을 경우 활용한다
대부분의 사전정의된 파일들은 자동으로 로딩되도록 되어 있으나, 추가로 필요한 파일이 있을 때 이 부분 활용 가능
applicationConterxt는 스프링의 핵심 기능들이 집약되어있는 모듈이다 즉 스프링의 뇌, 코어이다
@Service
public class ResourceService {
@Autowired
ApplicationContext ctx; //-@Autowired를 통해 ResourceService가 JavaBean에 등록이 될때 ApplicationContext를 ctx라는 property에 Injection(주입)을 하게된다
public void setResource() {
Resource myTemplate =
ctx.getResource("classpath:some/resource/path/myTemplate.txt");
// ctx.getResource("file:/some/resource/path/myTemplate.txt");
// ctx.getResource("http://myhost.com/resource/path/myTemplate.txt");
// use myTemplate...
}
}
Spring의 두뇌라고하는 ApplicaitonContext의 인터페이스에 가장 중요한 기능하나가 ResourcePatternResolver이다
스프링 ApplicationContext에서 ResourceLoader를 불러올 때 사용하는 Interface
위치 지정자 패턴("classpath:", "file:", "http:")에 따라 자동으로 Resouce 로더 구현체를 선택
public interface ApplicationContext extends EnvironmentCapable,
ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
// Spring ApplicationContext interface
}
applicationContext(스프링의 핵심설정)을 이루는 설정값을 가져오는 방법들
// let's create an applicationContext
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
ApplicationContext ctx =
new FileSystemXmlApplicationContext("conf/appContext.xml");
ApplicationContext ctx =
new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
// then you can use ctx as a Spring
Bear bear = (Bear) ctx.getBean("bear");
Expression Language(표현언어)는 짧고 간단한 문법을 통해 필요한 데이터나 설정 값을 얻어올 수 있게 하는 특별한 형태의 표현식에 가까운 간편한 언어(그래프 접근 등 가능)
SpEL은 그 중에서도 스프링 모든 영역에서 사용 가능한 언어형식임
주로 @Value라는 어노테이션에 설정값을 주입받는데 활용받는다
@Value("${config.value}")
이러한것들을 어떻게 가져오는 지 간단하게 알아보자
SpelParser는 ""안에 있는 문자열을 평가(evaluation)해서 결과값을 만들어낸다
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'");
String message = (String) exp.getValue(); // "Hello World"
Expression expWow = parser.parseExpression("'Hello World'.concat('!')"); //-스프링의 메서드(concat)까지도 쓸수있다.
String messageWow = (String) expWow.getValue(); // "Hello World!"
Expression expString =
parser.parseExpression("new String('hello world').toUpperCase()"); //-String 객체를 new로 생성해서 사용도 가능하다
String messageString = expString.getValue(String.class); // "HELLO WORLD"
'Hello World'는 문자열 리터럴이 되며,concat이라는 메서드도 호출할 수 있다
String 객체를 new로 생성해서 사용도 가능하다
이런식으로 Experssion Language안에 문자뿐만아니라 좀더 복잡한 메소드 호출, 그리고 객체생성해서도 사용가능하다라고 생각하면된다.
굳이 이렇게 한번더 복잡하게 할 이유가 없다 실무적으로 쓰는경우는 아래와같다
기본적으로 #{ } 방식으로 property를 설정
$표시를 사용했을경우 application.properties(또는 application.yml)의 값을 가져올 때는 ${ } 방식으로 가져올 수 있다.
@Component
public class SimpleComponent {
@Value("#{ 1+1 }")
int two; // 2
@Value("#{ 2 eq 2 }")
boolean isTrue; // true
@Value("${ server.hostname }")
String hostName; // www.server.com
@Value("#{ ${ server.hostname } eq 'www.server.com'}")
boolean isHostSame; // true
}
널 안정성을 높이는 방법이다
자바에서는 매번 아래의 코드처럼 Null체크를 해야한다
public void method(String request) {
if(request == null) return;
// normal process
System.out.println(request.toUpperCase());
}
Null Safety에서는 위와 같은 코드(보일러플레이트; 맨날 똑같은 코드가 반복적으로 생기는거)를 만들지 않으며 혹은 널체크하지 않아서 NPE(Null Pointer Exception)을 방지하는 방법이며 완벽한 방법은 아니지만 IDE에서 경고를 표시함으로 1차적인 문제를 방지하고 정확한 에러 위치를 확인할 수 있도록 도와준다
해당 값이나 함수 등이 Null이 아님을 나타내는 어노테이션
org.springframework.lang.NonNull 사용
메서드 파라미터에 붙이는 경우 : null이라는 데이터가 들어오는 것을 사전에 방지함 어떤 메서드에서 NPE이 발생햇는지 알수있다.
프로퍼티에 붙이는 경우는 null을 저장하는 경우 경고를 보여준다
메서드에 붙이는 경우 : null을 리턴하는 경우 경고, 응답값을 저장하거나 활용하는 쪽도 NonNull이라고 신뢰하고 사용
@NonNull과 반대로 해당 데이터가 null일수 있음을 명시
해당 어노테이션이 붙은 값을 사용하는 경우 null check를 항상 수행하도록 경고한다