getCar()는 그대로
Map이나 외부 파일을 이용한다면, 읽어올 파일에서의 변경이 실행하는 코드에서 나타나지 않기 때문에 변경에 유리하다
변하는 것과 변하지 않는 것을 분리함
config.txt 파일을 읽어서 객체를 저장하는 코드는 변경되지 않는다
스프링에서는 XML이나 JAVA로 비슷한 설정을 할 수 있다
txt 파일에 저장된 값은 key=value 형태
car=com.fastcampus.ch3.diCopy1.SportsCar
car 객체를 이 경로에 있는 SportsCar 클래스 객체로 하겠다는 의미
class Car{}
class SportsCar extends Car{}
class Truck extends Car {}
public class Main1 {
public static void main(String[] args) throws Exception {
Car car = getCar();
//3. config.txt에서 car로 정의된 key의 value를 읽어와서 반환한다
System.out.println("car = " + car);
}
static Car getCar() throws Exception{
//Properties는 string-string으로 저장
Properties p = new Properties();
p.load(new FileReader("config.txt"));
Class clazz = Class.forName(p.getProperty("car"));
//1. p에서 car가 key인 value를 반환하고
//이를 클래스 이름으로 하는 클래스를 반환
//clazz는 사용하고자 하는 클래스가 되는 것
return (Car)clazz.newInstance();
//2. 클래스의 객체를 하나 생성하는 것, Object 형으로 반환되기 때문에 형변환 해야함
}
}
txt 파일에서 여러 객체를 가져오도록 변경
class Car{}
class SportsCar extends Car{}
class Truck extends Car {}
class Engine{} //1. 엔진 클래스 추가
public class Main1 {
public static void main(String[] args) throws Exception {
Car car = (Car)getObject("car");
//3. Object로 반환받기 때문에 메서드를 사용하는 쪽에서 형변환
Engine engine = (Engine)getObject("engine");
//4. key에 따라 여러 개의 객체를 불러올 수 있음
System.out.println("car = " + car);
System.out.println("engine = " + engine);
}
static Object getObject(String key) throws Exception{
//2. 이제는 car와 engine 둘을 가져와야하기 때문에
//Object로 반환받는 getObject 메서드 생성
//key를 매개변수로 입력받아 어떤 클래스를 가져올지 알려줌
Properties p = new Properties();
p.load(new FileReader("config.txt"));
Class clazz = Class.forName(p.getProperty(key));
return clazz.newInstance();
}
}
class AppContext{
Map map;//객체 저장소
AppContext() {
map = new HashMap();
try {
Properties p = new Properties();
p.load(new FileReader("config.txt"));
map = new HashMap(p);
//properties에 저장된 내용(String-String)을 map에 저장
for (Object key : map.keySet()){ //map에서 key를 하나씩 가져와서
Class clazz = Class.forName((String)map.get(key));
// key에 해당하는 value를 String으로 꺼내와서 class 객체로 만든다
map.put(key, clazz.newInstance());
// 클래스 객체를 다시 map에 key와 value로 저장한다
// 기존 String이었던 Value가 Class로 바뀜
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Object getBean(String key){
return map.get(key); //key에 해당하는 value를 map에서 꺼낸다
}
}
public class Main2 {
public static void main(String[] args) throws Exception {
AppContext ac = new AppContext();
Car car = (Car)ac.getBean("car");
Engine engine = (Engine)ac.getBean("engine");
System.out.println("car = " + car);
System.out.println("engine = " + engine);
}
}
클래스 앞에 @Component 애너테이션을 붙이고,
아래와 같이 수정하면 해당 클래스에서 애너테이션이 붙은 것들을 자동으로 등록해준다
패키지에 있는 모든 클래스를 읽어서 Set에 저장하고
반복문을 돌며 @Component가 붙은 클래스를 찾아서
붙었으면 클래스의 객체를 생성해서 map에 저장하는 과정
클래스 이름을 key로 하기 때문에, 클래스 명을 대문자로 시작하는 것에서 소문자로 바꿔서 key로 한다
@Component class Car{}
@Component class SportsCar extends Car{}
@Component class Truck extends Car {}
@Component class Engine{}
class AppContext{
Map map;//객체 저장소
AppContext() {
map = new HashMap();
doComponentScan();
}
private void doComponentScan() {
try {
//1. 패키지 내의 클래스 목록을 가져온다
//2. 반복문으로 클래스를 하나씩 읽어와서 @Component 애너테이션이 붙어있는지 확인
//3. 붙어있으면 객체를 생성해서 map에 저장
ClassLoader classLoader = AppContext.class.getClassLoader();
ClassPath classPath = ClassPath.from(classLoader);
Set<ClassPath.ClassInfo> set = classPath.getTopLevelClasses("com.fastcampus.ch3.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());
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Object getBean(String key){
return map.get(key);
}
}
public class Main3 {
public static void main(String[] args) throws Exception {
AppContext ac = new AppContext();
Car car = (Car)ac.getBean("car");
Engine engine = (Engine)ac.getBean("engine");
System.out.println("car = " + car);
System.out.println("engine = " + engine);
}
}
Object getBean(String key){ //이름으로 찾아서 객체 반환
return map.get(key);
}
Car car = (Car)ac.getBean("car");
Object getBean(Class clazz) { //클래스를 받아서
for(Object obj : map.values()){ //맵의 값을 읽으면서
if (clazz.isInstance(obj)) //값이 클래스의 인스턴스면 반환
return obj;
}
return null;
}
Car car2 = (Car)ac.getBean(Car.class);
car.engine = engine ..
car.door = door ..
//객체 저장소에서 객체 가져오고
Door door = (Door)ac.getBean(Door.class);
Engine engine = (Engine)ac.getBean("engine");
// 수동으로 객체를 연결
car.engine = engine;
car.door = door;
private void doAutowired() {
//map에 저장된 객체의 iv 중에 @Autowired가 붙어있으면
//map에서 iv 타입에 맞는 객체를 찾아서 연결(객체의 주소를 iv에 저장)
try {
for(Object bean : map.values()){ //객체 저장소를 하나씩 살피면서
for (Field fld : bean.getClass().getDeclaredFields()){ //갖고있는 필드(iv) 중에 @Autowired 붙은게 있는지 확인
if (fld.getAnnotation(Autowired.class)!=null) // 애노테이션이 붙어있으면
fld.set(bean, getBean(fld.getType())); // 타입으로 객체 찾아서 (getType()) 그 필드에 set하기
}
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private void doResource() {
//map에 저장된 객체의 iv 중에 @Resource가 붙어있으면
//map에서 iv 이름에 맞는 객체를 찾아서 연결(객체의 주소를 iv에 저장)
try {
for(Object bean : map.values()){ //객체 저장소를 하나씩 살피면서
for (Field fld : bean.getClass().getDeclaredFields()){ //갖고있는 필드(iv) 중에 @Resource 붙은게 있는지 확인
if (fld.getAnnotation(Resource.class)!=null) // 애노테이션이 붙어있으면
fld.set(bean, getBean(fld.getName())); // 이름으로 객체 찾아서 (getName()) 그 필드에 set하기
}
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
기존에는 config.txt로 사용
config.xml로 변경
config.xml에 클래스들 정보를 설정해준 것!
ApplicationContext ac = new GenericXmlApplicationContext("config.xml"); //저장소에 맵 형태로 만들어짐
//그 저장소에서 가져다가 사용 by Name!
Car car = (Car) ac.getBean("car");
//타입으로 하고 싶다면
Car car2 = (Car) ac.getBean(Car.class);
// 이름과 타입 정보를 동시에 주면 형변환 안해도 된다
Car car3 = ac.getBean("car", Car.class);
Engine engine = (Engine) ac.getBean("engine");
Door door = (Door) ac.getBean("door");
System.out.println("car = " + car);
System.out.println("engine = " + engine);
System.out.println("door = " + door);
bean으로 등록된 객체들은 모두 싱글톤으로 생성되어 관리되지만 getBean할 때마다 새로운 객체가 생성되게 하고 싶다면 config.xml 에서 scope="prototype"을 설정하면 됨
싱글톤은 기본값으로 생략되어있음<bean id="car" class="com.fastcampus.ch3.Car" scope="prototype"/> <bean id="engine" class="com.fastcampus.ch3.Engine" scope="singleton"/>
car.setColor("red");
car.setEngine(engine);
car.setOil(100);
car.setDoor(new Door[]{ac.getBean("door", Door.class), ac.getBean("door", Door.class)});
<bean id="car" class="com.fastcampus.ch3.Car">
<property name="color" value="red"/>
<property name="oil" value="100"/>
<property name="engine" value="engine"/>
<property name="door">
<array value-type="com.fastcampus.ch3.Door">
<ref bean="door"/>
<ref bean="door"/>
</array>
</property>
</bean>
<bean id="car" class="com.fastcampus.ch3.Car">
<constructor-arg name="color" value="red"/>
<constructor-arg name="oil" value="100"/>
<constructor-arg name="engine" value="engine"/>
<constructor-arg name="door">
<array value-type="com.fastcampus.ch3.Door">
<ref bean="door"/>
<ref bean="door"/>
</array>
</constructor-arg>
</bean>
<context:component-scan base-package="com.fastcampus.ch3">
<context:exclude-filter type="regex" expression="com.fastcampus.ch3.diCopy*.*"/>
</context:component-scan>
@Value("red") String color;
@Value("100") int oil;
@Autowired Engine engine;
@Autowired Door[] door;
- 만약 상속받은 클래스가 여러개고, 각각이 빈에 등록되어 있으면서 부모 클래스 타입으로 getBean 하면 오류 발생! 이름으로 찾아야함
- @Autowired도 내부적으로 타입으로 찾아서 연결하는 건데 이건 가능하다! 왜?
타입으로 먼저 검색을 하고, 여러개일 경우 이름으로 찾는다! 그래서 오류가 발생하지 않는다- 타입이 여러개인데 이름이 같은 객체가 없을 경우에는 에러 발생!
- @Autowired와 함께 @Qualifier("사용할객체이름")을 명시해줌 (by Type)
- 이는 @Resource(name="사용할객체이름")과 똑같다 (by Name)
이름이 없음면 에러 발생!