✔️ 우리는 어떻게 하면 프로그램의 변경을 최소화 할까를 고민해야 한다.
➡️ 변경이 유리하게 만들기 위해 = OOP
class Car {}
class SportCar extends Car {}
class Truck extnds Car {}
오른쪽 예제의 메서드는 기능을 담당하는 메서드이고 car를 정의하는 식은 사용하는 코드이다. 저 메서드가 getCar()
의 SportCar()
가 바껴도 Car car = get Car();
에는 영향을 받지 않는다.
기능을 제공하는 코드는 하나만 있으면 되는데, 사용하는 코드(car 정의)는 여러개일 수가 있으며 여러 군데에서 사용된다. 그래서 왼쪽 식을 보면, 사용하는 코드를 계속 바꿔줘야 해서 번거롭다. 반대로 오른쪽 식은 별도의 메서드를 통해서 객체를 반환하게 바꾸면, 수많은 getCar()
를 넣은 car
는 고치지 않아도 된다.
Properties 클래스를 이용해서 config.txt
를 읽어온다. 그 내용이 있는 객체를 생성해서 반환한다.
Properties ➡️ key:values
: Properties를 사용하는 이유는 데이터를 가져오거나 저장할 때 편리하게 되어있다. (p.load
)
car = kr.ac.jipark09.Truck
engine = kr.ac.jipark09.TurboEngine
코드를 변경하지 않고 프로퍼티만 변경하면 된다.
: 컴파일을 다시 하지 않아도 되고 테스트도 하지 않아도 된다.
spring도 다 이런식으로 되어있다.
: 텍스트 파일대신 XML or Java 코드를 이용해서 정보를 제공하면 프레임이 정보를 이용해서 동작하게 되어있다. ➡️ 변경 포인트를 최대한 분리
❗️ 변하는 것과 변하지 않는 것 분리 ➡️ 지금하고 있는 것!
❗️ 관심사의 분리
❗️ 중복 코드 분리 = AOP
package kr.ac.jipark09.diCopy1;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.HashMap;
import java.util.Properties;
import java.util.Set;
class Car {}
class SportsCar extends Car {}
class Truck extends Car {}
class Engine {}
public class Main1 {
public static HashMap<String, Object> cmds;
public static void main(String[] args) throws Exception {
getMap();
System.out.println(cmds);
// Car car = (Car)getObject("car");
// Engine engine = (Engine) getObject("engine");
// System.out.println("car=" + car);
// System.out.println("engine=" + engine);
}
static Object getObject(String key) throws Exception {
// properties를 불러와서 value를 객체를 만든다.
Properties properties = new Properties();
properties.load(new FileReader("config.txt"));
Class clazz = Class.forName(properties.getProperty(key));
return clazz.newInstance();
}
static void getMap() throws Exception {
cmds = new HashMap<>();
Properties properties = new Properties();
properties.load(new FileReader("config.txt"));
Set<Object> keys = properties.keySet();
for(Object key : keys) {
String strKey = key.toString();
Class clazz = Class.forName(properties.getProperty(strKey));
cmds.put(strKey, clazz.newInstance());
}
}
}
[결과값]
{car=class kr.ac.jipark09.diCopy1.Truck, engine=class kr.ac.jipark09.diCopy1.Engine}
✔️ ApplicationContext = 객체 저장소
클래스 안에 Map을 만들어 준다.
getObject ➡️ getBean
: javaBean은 규칙을 지켜야 하는 제약이 있다.
직접 new SportsCar()
를 넣어주면 하드코딩이 되어버린다. 즉, 변하는 부분이 생기면 직접적으로 계속 바꿔줘야 된다.
➡️ Properties를 이용하여 유연하게 처리
✏️ AppContext()의 메서드 부분을 분리시킨다.
package kr.ac.jipark09.diCopy2;
import java.io.FileReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
class Car {}
class SportsCar extends Car {}
class Truck extends Car {}
class Engine {}
class AppContext {
Map map;
public AppContext() throws Exception {
Properties properties = new Properties();
properties.load(new FileReader("config.txt"));
map = new HashMap(properties);
for(Object key : map.keySet()) {
Class clazz = Class.forName((String) map.get(key));
map.put(key, clazz.newInstance());
}
}
public Object getBean(String key) {
return map.get(key);
}
}
public class Main2 {
public static void main(String[] args) throws Exception {
AppContext appContext = new AppContext();
Car car = (Car)appContext.getBean("car");
Engine engine = (Engine)appContext.getBean("engine");
System.out.println("car=" + car);
System.out.println("engine=" + engine);
}
}
[결과값]
car=kr.ac.jipark09.diCopy2.Truck@6574b225
engine=kr.ac.jipark09.diCopy2.Engine@2669b199
✔️ @Component 어노테이션을 이용해서 객체 저장소에 저장한 빈들을 지정
: guava 라이브러리
❗️ guava 라이브러리
: 자바 reflaction을 좀 더 쉽게 사용할 수 있도록 도와주는 라이브러리
classInfo.getSimpleName()
으로 클래스의 대문자를 소문자로 변환시켜준다.
➡️ key로 사용
config.txt 처럼 외부 파일을 이용해서 객체를 등록하기도 하지만 클래스 앞에 어노테이션을 이용해서 객체를 등록하는 방법도 있다.
: 외부 파일을 이용하면 새로운 객체를 등록할 때마다 고쳐야 되는데 여러사람이 편집할 때에는 쉽지않다. 그래서 공통으로 사용 할 객체를 적어놓고 어노테이션을 사용하면 편집하기 쉬워진다.
2가지 방법을 적절히 실행
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
<context:component-scan base-package="kr.ac.jipark09">
<context:exclude-filter type="regex" expression="kr.ac.jipark09.diCopy*.*"/> // 해당하는 패키지만 가져오기
</context:component-scan>
✏️ pom.xml에다 넣어주기
package kr.ac.jipark09.diCopy3;
import com.google.common.reflect.ClassPath;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.FileReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
@Component class Car {}
@Component class SportsCar extends Car {}
@Component class Truck extends Car {}
@Component class Engine {}
class AppContext {
Map map;
public AppContext() throws Exception {
map = new HashMap();
doComponentScan();
}
private void doComponentScan() throws Exception {
// 1. 패키지내의 클래스 목록을 가져온다.
// 2. 반복문으로 클래스를 하나씩 읽어와서 @Component가 붙어 있는지 확인
// 3. @Component가 붙어있으면 객체를 생성해서 map에 저장
ClassLoader classLoader = AppContext.class.getClassLoader();
ClassPath classPath = ClassPath.from(classLoader);
Set<ClassPath.ClassInfo> set = classPath.getTopLevelClasses("kr.ac.jipark09.diCopy3");
for(ClassPath.ClassInfo classInfo : set) {
Class clazz = classInfo.load();
Component component = (Component) clazz.getAnnotation(Component.class);
if(component != null) {
String id = StringUtils.uncapitalize(classInfo.getSimpleName());
map.put(id, clazz.newInstance());
}
}
}
public Object getBean(String key) {
return map.get(key);
}
}
public class Main3 {
public static void main(String[] args) throws Exception {
AppContext appContext = new AppContext();
Car car = (Car)appContext.getBean("car");
Engine engine = (Engine)appContext.getBean("engine");
System.out.println("car=" + car);
System.out.println("engine=" + engine);
}
}
car=kr.ac.jipark09.diCopy3.Car@74ad1f1f
engine=kr.ac.jipark09.diCopy3.Engine@6a1aab78
car=kr.ac.jipark09.diCopy3.Car@491cc5c9
engine=null
instanceof
를 통해 Car 타입이 맞는지 아닌지 알아 볼 수 있다. // ByName으로 찾기
public Object getBean(String key) {
return map.get(key);
}
// ByType으로 찾기
public Object getBean(Class clazz) {
for (Object obj : map.values()) {
// obj가 클래스의 객체거나 자손이면 true 반환
if(clazz.isInstance(obj)) {
return obj;
}
}
return null;
}
// 결과 보기
public class Main3 {
public static void main(String[] args) throws Exception {
AppContext appContext = new AppContext();
Car car = (Car)appContext.getBean("car");
Car car1 = (Car)appContext.getBean(Car.class);
Engine engine = (Engine)appContext.getBean("engine");
System.out.println("car=" + car);
System.out.println("car1=" + car1);
System.out.println("engine=" + engine);
}
}
✔️ 스프링이 관리하는 빈(Bean)을 자동으로 주입해주는 어노테이션1
✔️ By Type으로 값을 찾아서 연결
✏️ @Autowired 사용하기
instanceof
로 찾음package kr.ac.jipark09.diCopy4;
import com.google.common.reflect.ClassPath;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@Component class Car {
@Autowired Engine engine;
@Autowired Door door;
@Override
public String toString() {
return "Car{" +
"engine=" + engine +
", door=" + door +
'}';
}
}
@Component class SportsCar extends Car {}
@Component class Truck extends Car {}
@Component class Engine {}
@Component class Door {}
class AppContext {
Map map;
public AppContext() throws Exception {
map = new HashMap();
doComponentScan();
doAutowired();
}
private void doAutowired() {
// map에 저장된 객체의 iv중에 @Autowired가 붙어 있으면
// map에서 iv의 타입에 맞는 객체를 찾아서 연결(객체의 주소를 iv에 저장)
try {
for(Object bean : map.values()) {
for(Field field : bean.getClass().getDeclaredFields()) {
if(field.getAnnotation(Autowired.class) != null) {
field.set(bean, getBean(field.getType())); // car.engine = obj; 타입을 찾아서 연결
}
}
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private void doComponentScan() throws Exception {
// 1. 패키지내의 클래스 목록을 가져온다.
// 2. 반복문으로 클래스를 하나씩 읽어와서 @Component가 붙어 있는지 확인
// 3. @Component가 붙어있으면 객체를 생성해서 map에 저장
ClassLoader classLoader = AppContext.class.getClassLoader();
ClassPath classPath = ClassPath.from(classLoader);
Set<ClassPath.ClassInfo> set = classPath.getTopLevelClasses("kr.ac.jipark09.diCopy4");
for(ClassPath.ClassInfo classInfo : set) {
Class clazz = classInfo.load();
Component component = (Component) clazz.getAnnotation(Component.class);
if(component != null) {
String id = StringUtils.uncapitalize(classInfo.getSimpleName());
map.put(id, clazz.newInstance());
}
}
}
// ByName으로 찾기
public Object getBean(String key) {
return map.get(key);
}
// ByType으로 찾기
public Object getBean(Class clazz) {
for (Object obj : map.values()) {
// obj가 클래스의 객체거나 자손이면 true 반환해서 obj에 대입
if(clazz.isInstance(obj)) {
return obj;
}
}
return null;
}
}
public class Main4 {
public static void main(String[] args) throws Exception {
AppContext appContext = new AppContext();
Car car = (Car)appContext.getBean("car");
Door door = (Door) appContext.getBean(Door.class);
Engine engine = (Engine)appContext.getBean("engine");
// 수동으로 객체 연결
// car.engine = engine;
// car.door = door;
System.out.println("car=" + car);
System.out.println("engine=" + engine);
System.out.println("door=" + door);
}
}
✔️ 스프링이 관리하는 빈(Bean)을 자동으로 주입해주는 어노테이션 2
✔️ By Name으로 값을 찾아서 연결
ByName으로 key를 뒤져서 해당 타입에 맞는 값을 찾는다.
@Resource 이름을 우리가 직접 지정해 줄 수도 있다.
직접 지정하기 전에는 앞글자를 대문자에서 소문자로 바꾼 name을 사용한다.
package kr.ac.jipark09.diCopy4;
import com.google.common.reflect.ClassPath;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@Component class Car {
@Resource
Engine engine;
@Autowired Door door;
@Override
public String toString() {
return "Car{" +
"engine=" + engine +
", door=" + door +
'}';
}
}
@Component class SportsCar extends Car {}
@Component class Truck extends Car {}
@Component class Engine {}
@Component class Door {}
class AppContext {
Map map;
public AppContext() throws Exception {
map = new HashMap();
doComponentScan();
doResource();
}
private void doResource() {
// map에 저장된 객체의 iv중에 @Resource가 붙어 있으면
// map에서 iv의 이름에 맞는 객체를 찾아서 연결(객체의 주소를 iv에 저장)
try {
for(Object bean : map.values()) {
for(Field field : bean.getClass().getDeclaredFields()) {
if(field.getAnnotation(Resource.class) != null) {
field.set(bean, getBean(field.getName())); // car.engine = obj; 타입을 찾아서 연결
}
}
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private void doComponentScan() throws Exception {
// 1. 패키지내의 클래스 목록을 가져온다.
// 2. 반복문으로 클래스를 하나씩 읽어와서 @Component가 붙어 있는지 확인
// 3. @Component가 붙어있으면 객체를 생성해서 map에 저장
ClassLoader classLoader = AppContext.class.getClassLoader();
ClassPath classPath = ClassPath.from(classLoader);
Set<ClassPath.ClassInfo> set = classPath.getTopLevelClasses("kr.ac.jipark09.diCopy4");
for(ClassPath.ClassInfo classInfo : set) {
Class clazz = classInfo.load();
Component component = (Component) clazz.getAnnotation(Component.class);
if(component != null) {
String id = StringUtils.uncapitalize(classInfo.getSimpleName());
map.put(id, clazz.newInstance());
}
}
}
// ByName으로 찾기
public Object getBean(String key) {
return map.get(key);
}
// ByType으로 찾기
public Object getBean(Class clazz) {
for (Object obj : map.values()) {
// obj가 클래스의 객체거나 자손이면 true 반환해서 obj에 대입
if(clazz.isInstance(obj)) {
return obj;
}
}
return null;
}
}
public class Main4 {
public static void main(String[] args) throws Exception {
AppContext appContext = new AppContext();
Car car = (Car)appContext.getBean("car");
Door door = (Door) appContext.getBean(Door.class);
Engine engine = (Engine)appContext.getBean("engine");
// 수동으로 객체 연결
// car.engine = engine;
// car.door = door;
System.out.println("car=" + car);
System.out.println("engine=" + engine);
System.out.println("door=" + door);
}
}
Reference
: https://fastcampus.co.kr/dev_academy_nks