본문 바로가기

Dev/Spring Boot

[예제로 배우는 스프링 입문] Bean 생성 방법

Bean? IOC 컨테이너가 관리하는 객체

OwnerController ownerController=new OwnerController(); -> 이건 빈 객체가 아님
OwnerController bean=applicationContext.getBean(OwnerController.class); ->
이건 빈 객체가 맞다. ApplicationContext가 관리하는 것이므로

 

어떻게 스프링 컨테이너 안에다가 빈을 만들어줄까?

방법 1. Component Scanning

 어노티이션 프로세스 중에 스프링 ioc 컨테이너를 만들고 그안에 빈을 등록할 때 사용하는 여러가지 인터페이스가 있는데 그 인터페이스를 lifecycle call back이라고 부른다. 이것들 중에는 @component라는 어노테이션이 붙은 클래스들을 찾아서 그 클래스를 빈으로 등록하는 일을 하는 처리기가 등록되어 있다.

 우리는 지금 스프링 부트 프로젝트를 사용중이다. 그래서 @SpringBootApplication이라는 어노테이션이 있다. 이안에 @componentScan이라는 어노테이션이 있다. 이 어노테이션은 또다른 어노테이션이 달린 클래스가 어딨는지 어디부터 찾아보라고 알려준다. coponentScan 어노테이션이 붙어있는 곳에서 부터 모든 하위 패키지들에 있는 모든 클래스들을 찾아보게 된다.

 훑어보면서 @controller, @component, @repository,@service,@configuration, 직접 정의도 가능한데 모두 찾아 빈으로 등록해주는 것이 Component Scanning이다. 즉, 우리가 직접 OwnerController을 빈으로 등록하지 않더라도 스프링이 알아서 ioc 컨테이너가 만들어질 때, 스캔해서 어노테이션들을 보고 자동으로  빈으로 등록을 해주는 것이다.

즉, @componentScan은 어느 지점부터 찾을 것인지를 알려주는 것이고,

실제 찾게될 어노테이션은 @controller, @component 등이 있다.

 OwnerRepository.java는 좀 특이한 형태로 빈으로 등록이 된다. 스프링 데이터 jpa가 제공해주는 기능에 의해서 빈으로 등록이 된다.  이 경우에는 특정한 어노테이션이 없더라도 특정한 인터페이스를 상속 받은 경우에 이 인터페이스의 구현체를 내부적으로 만든다. 

OwnerRepository.java

SampleController.java

@controller을 추가하면 applicationContext에 컴포넌트 스캔이 되서 빈으로 등록된다.

SampleControllerTest.java

 

SampleController.java _저번 포스팅 코드 주석

package org.springframework.samples.petclinic.sample;

import org.springframework.stereotype.Controller;

// 백지연 추가
@Controller
public class SampleController {
    /*
    SampleRepository sampleRepository;
    public SampleController(SampleRepository sampleRepository){
        this.sampleRepository=sampleRepository;
    }
    public void doSomething(){
        sampleRepository.save();
    }

     */


}

 

주의 : @Test 어노테이션 import 다른거 하면 Runnable 오류

SampleControllerTest.java_저번 포스팅 코드 주석

package org.springframework.samples.petclinic.sample;
// 백지연 junit 추가
//import org.junit.jupiter.api.Test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;

// 백지연 : 어노테이션 추가. 스프링 부트 기반 테스트 작성코드
@RunWith(SpringRunner.class)
@SpringBootTest
public class SampleControllerTest {
    @Autowired
    ApplicationContext applicationContext;
    //ApplicationContext applicationContext;
    @Test
    public void testDI(){
        // 백지연 : applicationContext에 SampleController가 빈으로 등록되어 있는지 확인하는 테스트
        SampleController bean=applicationContext.getBean(SampleController.class);
        assertThat(bean).isNotNull();
    }
    /*
    public void testDoSomething(){
        SampleRepository sampleRepository=new SampleRepository();
        SampleController sampleController=new SampleController(sampleRepository);
        sampleController.doSomething();
    }
     */
}

 

 

방법 2. 직접 bean 어노테이션 등록

빈 설정 파일이 xml이냐 java 파일이냐에 따라 달라질 수 있지만, 최근 추세는 자바 설정 파일을 더 많이 쓰는 추세이다.

SampleConfig.java

SampleController.java

@controller 주석처리를 해도 직접 bean으로 등록해줬으므로 오류가 안난다.

 

getApplicationContext 말고도 Autowired를 통해서 꺼내쓸 수 있다.

OwnerController.java_주석처리

/*
 * Copyright 2012-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.samples.petclinic.owner;

import org.springframework.context.ApplicationContext;
import org.springframework.samples.petclinic.visit.VisitRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.validation.Valid;
import java.util.Collection;
import java.util.Map;

/**
 * @author Juergen Hoeller
 * @author Ken Krebs
 * @author Arjen Poutsma
 * @author Michael Isvy
 */
@Controller
class OwnerController {

	private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm";
	private final OwnerRepository owners;
	private VisitRepository visits;

	// 백지연 : 테스트를 눈에 띄게 하기 위해 getBean()// 백지연 : 빈 강의에서 autowired강의중 주석처리
    // private final ApplicationContext applicationContext;

    // 백지연 : application 인자추가 // 백지연 : 빈 강의에서 autowired강의중 주석처리
	public OwnerController(OwnerRepository clinicService, VisitRepository visits) {
		this.owners = clinicService;
		this.visits = visits;
		//this.applicationContext=applicationContext;
	}
	// 백지연 : 빈 강의에서 autowired강의중 주석처리
    /*
	// 백지연 : 매핑 추가
    @GetMapping("/bean")
    @ResponseBody
    public String bean(){
	    return "bean: "+applicationContext.getBean(OwnerRepository.class)
            +"\n"+ "owners: "+owners;
    }
    */

	@InitBinder
	public void setAllowedFields(WebDataBinder dataBinder) {
		dataBinder.setDisallowedFields("id");
	}

	@GetMapping("/owners/new")
	public String initCreationForm(Map<String, Object> model) {
		Owner owner = new Owner();
		model.put("owner", owner);
		return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
	}

	@PostMapping("/owners/new")
	public String processCreationForm(@Valid Owner owner, BindingResult result) {
		if (result.hasErrors()) {
			return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
		}
		else {
			this.owners.save(owner);
			return "redirect:/owners/" + owner.getId();
		}
	}

	@GetMapping("/owners/find")
	public String initFindForm(Map<String, Object> model) {
		model.put("owner", new Owner());
		return "owners/findOwners";
	}

	@GetMapping("/owners")
	public String processFindForm(Owner owner, BindingResult result, Map<String, Object> model) {

		// allow parameterless GET request for /owners to return all records
		// 백지연: getLastName(), setLastName()을 First로 수정
		if (owner.getFirstName() == null) {
			owner.setFirstName(""); // empty string signifies broadest possible search
		}

		// find owners by first name
		// 백지연: 주석 first로 수정
		// 백지연: 레파지토리에서 실제 검색을 해오는 부분 findByFirstName
		// this.owners.findByLastName->first로 수정
		Collection<Owner> results = this.owners.findByFirstName(owner.getFirstName());
		if (results.isEmpty()) {
			// no owners found
			result.rejectValue("lastName", "notFound", "not found");
			return "owners/findOwners";
		}
		else if (results.size() == 1) {
			// 1 owner found
			owner = results.iterator().next();
			return "redirect:/owners/" + owner.getId();
		}
		else {
			// multiple owners found
			model.put("selections", results);
			return "owners/ownersList";
		}
	}

	@GetMapping("/owners/{ownerId}/edit")
	public String initUpdateOwnerForm(@PathVariable("ownerId") int ownerId, Model model) {
		Owner owner = this.owners.findById(ownerId);
		model.addAttribute(owner);
		return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
	}

	@PostMapping("/owners/{ownerId}/edit")
	public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result,
			@PathVariable("ownerId") int ownerId) {
		if (result.hasErrors()) {
			return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
		}
		else {
			owner.setId(ownerId);
			this.owners.save(owner);
			return "redirect:/owners/{ownerId}";
		}
	}

	/**
	 * Custom handler for displaying an owner.
	 * @param ownerId the ID of the owner to display
	 * @return a ModelMap with the model attributes for the view
	 */
	@GetMapping("/owners/{ownerId}")
	public ModelAndView showOwner(@PathVariable("ownerId") int ownerId) {
		ModelAndView mav = new ModelAndView("owners/ownerDetails");
		Owner owner = this.owners.findById(ownerId);
		for (Pet pet : owner.getPets()) {
			pet.setVisitsInternal(visits.findByPetId(pet.getId()));
		}
		mav.addObject(owner);
		return mav;
	}

}

 처리 후 @autowired로 owner을 꺼내온다.

getApplicationContext(빈)처럼 직접 꺼내 오는 것보다는 스프링 ioc 컨테이너가 제공하는 디펜던시 인젝션(주입)하는 방법(ex. @autowired)을 많이 쓴다. 자세히 어떤 방법인지는 다음 포스팅에 쓰겠다.