Spring Boot / 개념, Bean등록, DI설정, External Config, Profile

Cheol·2023년 8월 18일
1
post-thumbnail

😁Spring Boot??

Spring에서의 복잡하고 방대산 설정에 대해 부담을 느껴 Spring Boot가 만들어졌다.

SpringBoot는 경쟁관계에 있는 다른 프레임워크처럼 커맨드 도구를 제공하고, tomcat이나 jetty 같은 내장 서버를 통해 복잡한 설정과 실행을 간소화했다.

==> Spring을 처음 사용하는 개발자도 애플리케이션 관련 설정을 쉽게 처리하고 관리함으로써 개발에 더 집중할 수 있게 되었다.


Spring Boot 장점❗

  • 라이브러리 관리 자동화
    • Maven기능에 더해 스타터(Starter)라는 것을 이용하여 특정 기능에 필요한 라이브러리 의존성을 더욱 간단히 처리할 수 있다.
      또한 라이브러리 버전도 자동으로 관리된다.

  • 설정의 자동화
    • 프로젝트에 추가된 라이브러리를 기반으로, 실행에 필요한 환경을 자동으로 설정해준다.

  • 테스트 환경과 내장 tomcat
    • Junit을 비롯한 테스트 관련 라이브러리들이 기본적으로 내장되어 있으며, tomcat서버를 내장하고 있어서 단지 main메서드를 실행하는 방식으로 서버를 구동하기 때문에 빠르게 실행 결과를 확인할 수 있다.

  • 독립적으로 실행 가능한 JAR.
    • 애플리케이션을 개발하고 테스트까지 마쳤으면
      실제 운영 서버에 배포하기 위해서는 packaing을 해야한다.
      SpringBoot에서는 독립적으로 실행 가능한 애플리케이션을 빠르게 개발하는 것을 목표로 하기 때문에 웹 애플리케이션도 WAR가 아닌 JAR파일로 패키징하여 사용할 수 있다.

  • 정리

    -- 단독 실행이 가능한 스프링 어플리케이션 생성
    -- 내장형 Tomcat, Jetty 또는 Undertow를 지원(war 배포시 불필요)
    -- 기본으로 설정되어 있는 starter 컴포넌트들을 이용하여 쉽게 환경설정(dependency, build 등) 가능
    -- Library 자동 인식을 통한 환경 구성 지원
    -- 상용화 수준의 통계(metrics), 상태 점검(health check)및 외부 설정 제공
    -- 설정을 위한 XML코드 대신에 propertiesyaml 파일 사용



Spring과의 차이점:
1. pom.xml의 dependency 관리 추가가 더 편하다.
2. application.properties, yml이용 프로젝트 구동환경 설정

jar: jar파일로 묶어 tomcat없이 웹 서비스가 가능하다.
war: jar파일을 war파일로 묶어 tomcat을 사용해야 한다.

Spring Boot Project를 생성할 때 Version 설정에서
ver.2.xx는 jdk 1.8 지원.
ver.3.xx은 jdk 17지원.


  • com.example의 하위폴더에 파일을 만들어야 @ComponentScan이 인식한다.

  • @configuration: bean생성하는 xml을 읽어올 수 있는 기능을 한다.


커스텀 배너 설정

src/main/resources에 커스텀할 파일을 넣어준다.

applcation.properties 설정

spring.banner.location=banner.txt
spring.banner.charset=utf-8
#spring.banner.image.location=banner.png
  • 결과

Bean 등록 및 DI 설정

※ 빈(Bean)은 컨테이너에 의해 관리되는 재사용 가능한 소프트웨어 컴포넌트이다.

📕빈 생성(같은 패키지)

같은 서브 패키지인 경우 자동 scan한다. 특별한 설정은 필요 없다.
(SpringBootApplication에서 Component-scan을 하지 않아도 된다는 의미)

❗다만 Service 클래스에 Component생성은 해줘야한다.
(빈을 해당 클래스에서 생성해야 사용할 수 있기 때문이다.)


📙빈 생성(권장 패키지 구조)

다른 패키지인 경우 명시적 등록한다.
@SpringBootApplication의 빈에서 @Configuration 빈을 @Import로 등록

com.example.person.JavaConfig.java

package com.example.person;

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

@Configuration	// 빈생성 .xml 역할을 한다.
public class JavaConfig {
	@Bean("xxx")
	public Person getPerson() {	
		return new Person("홍길동", 20);
	}
//<bean id="getPerson" class="com.example.person.Person">
//	<constructargs name="name" value="홍길동"/>
//	<constructargs name="age" value="20"/>
//</bean> 과 동일하다.
}

com.example.SpringBootApplication.java

package com.example;

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

import com.example.person.Person;

@SpringBootApplication
public class Boot02logback3LogApplication {

	public static void main(String[] args) {
		ApplicationContext ctx = SpringApplication.run(Boot02logback3LogApplication.class, args);
		Person p = ctx.getBean("xxx", Person.class);
		System.out.println(p);
	}
}

📒빈 생성(다른 패키지)

첫 번쨰 방법

다른 패키지인 경우 명시적 등록한다.
@SpringBootApplication의 빈에서 @Configuration 빈을 @Import로 등록

com.example.SpringBootApplication.java

@SpringBootApplication
@Import(value=JavaConfig.class)
public class Boot04Bean01OtherPackageExplicitApplication {

	public static void main(String[] args) {
		ApplicationContext ctx = SpringApplication.run(Boot04Bean01OtherPackageExplicitApplication.class, args);
		DBService service = ctx.getBean("myService", DBService.class);
		System.out.println(service.getList());
	}
	
}

com.config.JavaConfig.java

@Configuration
public class JavaConfig {
	
	@Bean("myService")
	public DBService service() {
		return new DBService();
	}
 }

com.service.DBService

public class DBService {
	
	public List<String> getList(){
		return Arrays.asList("홍길동", "이순신");
	}
}

두 번째 방법

다른 패키지인 경우 scan한다.

@SpringBootApplication의 빈에서

  • scanBasePackages = "com.*" 속성 이용 or
  • @ComponentScan("com.*") 이용.

SpringBootApplication.java

//@SpringBootApplication(scanBasePackages = "com.*")
@SpringBootApplication
@ComponentScan("com.*")
public class Boot04Bean01OtherPackage2ExplicitApplication {

	public static void main(String[] args) {
		ApplicationContext ctx = SpringApplication.run(Boot04Bean01OtherPackage2ExplicitApplication.class, args);
		DBService service = ctx.getBean("myService",DBService.class);
		
		System.out.println(service.getList());
	}

}

🧐DI (Dependency Injection) 란??

Spring framework가 지원하는 핵심 기능 중 하나이다.

객체 사이의 의존 관계가 객체 자신이 아닌 외부에 의해서 결정된다는 개념이다.

IoC컨테이너는 어떤 객체(A)가 필요로 하는 의존관계에 있는 다른 객체(B)를 직접 생성하여 어떤 객체(A)로 주입(설정)해주는 역할을 담당한다.
이때 주입해주는 것을 의존성 주입(Dependency Injection:DI) 이라고 부른다.



🔴생성자를 활용한 의존성 주입

Component-scan을 사용하지 않고 JavaConfig파일에서 명시적 등록하기.

DAO.java

public class DAO {
	public List<String> list(){
		return Arrays.asList("홍길동","이순신");
	}
}

DBService.java

public class DBService {
	
	private DAO dao;

	public DBService(DAO dao) {
		super();
		this.dao = dao;
	}

	public List<String> list() {
		return dao.list();
	}
}

JavaConfiguration.java

@Configuration
public class configuration {
	
	@Bean
	public DAO dao() {
		return new DAO();
	}

	@Bean
	public DBService service() {
		return new DBService(dao());
	}
}

SpringBootApplication.java

@SpringBootApplication
public class Boot05DiConstructor2MethodParameterApplication {

	public static void main(String[] args) {
		ApplicationContext ctx = 
        SpringApplication.run(Boot05DiConstructor2MethodParameterApplication.class, args);
		DBService service = ctx.getBean("service",DBService.class);
        //Bean alias를 등록하지 않으면 자동으로 함수 이름이 alias
		System.out.println(service.list());
	}
}

🟠Set을 활용한 의존성 주입

DBService.java

public class DBService {
	
	private DAO dao;

	public DBService() {
		super();
	}

	public DAO getDao() {
		return dao;
	}

	public void setDao(DAO dao) {
		this.dao = dao;
	}
	
	public List<String> list() {
		return dao.list();
	}
}

configuration.java

@Configuration
public class configuration {
	
	@Bean
	public DAO dao() {
		return new DAO();
	}

	@Bean
	public DBService service() {
		DBService service = new DBService();
		service.setDao(dao()); //set으로 dao생성자 주입
		return service;
	}
}

SpringBootApplication.javaDAO.java는 위와 동일하다.


🟡Bytype을 활용한 의존성 주입

@Configuration
public class configuration {
	
	@Bean("service")
	public DBService service(OracleDAO dao) { //바로 받을 수 있다.
		DBService service = new DBService();
		service.setDao(dao);
		return service;
	}
}

Bean Scope

기본적으로 컨테이너 한 개의 빈 객체를 생성하여 재사용된다.(per Container)
Bean의 Scope를 설정할 수 있는 방법: <bean>태그의 scope 속성을 사용한다.

  • singleton: 컨테이너 한 개의 빈 객체만 생성한다. (기본값)
  • prototype: 빈을 요청할 때 마다 빈 객체를 생성한다.

JavaConfig.java

@Configuration
public class configuration {
	
	@Bean("dao")
	@Scope("prototype")	//스코프가 붙으면 싱글톤 주소로 생성.
	public OracleDAO dao() {
		return new OracleDAO();
	}

	@Bean("service1")
	public DBService service1(OracleDAO dao) {
		DBService service = new DBService(dao());
		return service;
	}

	@Bean("service2")
	public DBService service2(OracleDAO dao) {
		DBService service = new DBService(dao());
		return service;
	}
}

SpringBootApplication.java

@SpringBootApplication
public class Boot05Di2Setter2MethodParameterApplication {

	public static void main(String[] args) {
		ApplicationContext ctx = SpringApplication.run(Boot05Di2Setter2MethodParameterApplication.class, args);
		DBService service = ctx.getBean("service1",DBService.class);
		DBService service2 = ctx.getBean("service2",DBService.class);

		System.out.println(service.getDao().hashCode());	
		System.out.println(service2.getDao().hashCode());
		System.out.println(service.getDao()==service2.getDao());
		//dao가 singletone으로 생성되었기 때문에 서로 다른 주소이다.
	}
}

결과


Property Setting by Collection Type

  • <list> : java.util.List or 배열
  • <set> : java.util.set
  • <map> : java.util.Map
  • <props> : java.util.Properties

🔴Collection_list

MysqlDao.javaOracleDao.java를 interface인 DBDao.java에 오버라이딩.

DBMySQLDAO.java

public class DBMySQLDAO implements DBDAO{

	@Override
	public List<String> list(){
		return Arrays.asList("MySQL 홍길동","MySQL 이순신");
	}
}

DBOracleDAO.java

public class DBOracleDAO implements DBDAO{

	@Override
	public List<String> list(){
		return Arrays.asList("Oracle 홍길동","Oracle 이순신");
	}
}

DBDAO.java

public interface DBDAO {
	public abstract List<String> list();
}

DBService.java
List<DBDAO> 를 멤버변수로 받아 사용한다.

public class DBService {
	
	private List<DBDAO> list;

	public DBService(List<DBDAO> list) {
		super();
		this.list = list;
	}

	public void setList(List<DBDAO> list) {
		this.list = list;
	}

	public List<DBDAO> getList(){
		return list;
	}
}

configuration.java

@Configuration
public class configuration {
	
	@Bean
	public DBOracleDAO dao() {
		return new DBOracleDAO();
	}

	@Bean
	public DBMySQLDAO dao2() {
		return new DBMySQLDAO();
	}
	
	@Bean("myService")
	public DBService service(List<DBDAO> list) {
		return new DBService(list);
	}

}

SpringBootApplication.java

@SpringBootApplication
public class Boot05Di2Setter2MethodParameterApplication {

	public static void main(String[] args) {
		ApplicationContext ctx = SpringApplication.run(Boot05Di2Setter2MethodParameterApplication.class, args);
		DBService service = ctx.getBean("myService",DBService.class);
		
		System.out.println(service.getList().get(0).list());
		System.out.println(service.getList().get(1).list());
	}
}

만약 DBService.java의 멤버변수가 List<DBDAO>가 아닌 그냥 DBDAO라면?

configuration.java

@Configuration
public class configuration {
	
//	@Bean
//	public OracleDAO dao() {
//		return new OracleDAO();
//	}

	@Bean
	public DBMySQLDAO dao2() {
		return new DBMySQLDAO();
	}
	
	@Bean("myService")
	public DBService service(DBDAO dao) {
		return new DBService(dao);
	}

}

@Bean 을 생성할 때 DBDAO는 OracleDAO나 MySQlDAO 중 하나밖에 받지 못하기 때문에 하나만 생성할 수 있다.


❗@Autowired

configuration에서 Bytype 또는 생성자 활용이 아닌 Autowired로 생성하기.

configuration.java

@Configuration
public class configuration {
	
	@Bean
	public OracleDAO dao() {
		return new OracleDAO();
	}
	
	@Bean("myService")
	public DBService service() {
		return new DBService();
	}

}

DBService

public class DBService {
	
	@Autowired
	private DBDAO DBDAO;
	
	public DBService() {
		super();
		// TODO Auto-generated constructor stub
	}

	public DBService(com.example.dao.DBDAO dBDAO) {
		super();
		DBDAO = dBDAO;
	}

	public List<String> getList() {
		return DBDAO.list();
	}
	
}
  • @Autowired는 속성 또는 생성자, setter 메소드에 설정, 필수 속성이다.
    (required = false)로 필수 속성 해제가능)
  • 기본적으로 일치하는 타입을 찾아서 injection 된다.

🧐@Qualifier

@Autowired와 같이 사용한다.
같은 타입의 빈이 여러 개 있는 경우에 예외가 발생하는데, 이떄 @Qualifier를 통해 특정 빈을 사용하도록 명시적으로 설정 가능하게 한다.

configuration.java

@Configuration
public class configuration {
	
	@Bean("xxx")
	public DBOracleDAO dao() {
		return new DBOracleDAO();
	}

	@Bean("yyy")
	public DBMySQLDAO dao2() {
		return new DBMySQLDAO();
	}

}

DBService.java

	//멤버변수 @Qualifier 설정
	@Autowired
	@Qualifier("yyy")
	private DBDAO DBDAO;


@Configuration 없이 인젝션

OracleDAO.java 또는 MySQLDAO.java 중 사용할 DAO에 @Component(== @Repository) 설정.

DBDAO.java

public interface DBDAO {
	public abstract List<String> list();
}

DBOracleDAO.java

@Repository
public class DBOracleDAO implements DBDAO{

	@Override
	public List<String> list(){
		return Arrays.asList("Oracle 홍길동","Oracle 이순신");
	}
}

DBService.java

//alise 설정
@Service("myService")
public class DBService {
	
	@Autowired
	private DBDAO DBDAO;
	
	public DBService() {
		super();
	}

	public DBService(com.example.dao.DBDAO dBDAO) {
		super();
		DBDAO = dBDAO;
	}

	public DBDAO getDBDAO() {
		return DBDAO;
	}

	public void setDBDAO(DBDAO dBDAO) {
		DBDAO = dBDAO;
	}

	public List<String> getList() {
		return DBDAO.list();
	}
	
}

@Autowired는 결국 명칭 그대로 자동주입이다.


❗권장 패키지 구조❗

Configration을 없애고, Component-scan과 @Autowired를 사용한다.

DBDao.java

public interface DBDao {
	public abstract List<String> list();
}

DBOracleDAO.java

@Repository
public class DBOracleDAO implements DBDao{

	@Override
	public List<String> list() {
		return Arrays.asList("oracle 홍길동","oracle 이순신");
	}
}

DBService.java

public interface DBSerivce {
	public List<String> list();
}

DBOracleService

@Service("myService")
public class DBOracleService implements DBSerivce{
	//@Autowired
	private DBDao dao;
	
	public DBOracleService(DBDao dao) {
		super();
		this.dao = dao;
	}

	@Override
	public List<String> list() {
		return dao.list();
	}

}

SpringBootApplication.java

@SpringBootApplication
//@Component-scan 생략가능
public class Boot06RecommendPakageStructureApplication {

	public static void main(String[] args) {
		ApplicationContext ctx = SpringApplication.run(Boot06RecommendPakageStructureApplication.class, args);
		DBSerivce service = ctx.getBean("myService",DBSerivce.class);
		System.out.println(service.list());
	}
}



📚Three method for lifeCycle can checked

📕 기본 함수 행성

DBService.java

public class DBSerivce{
	public void xxx() {
		System.out.println("initMethod.xxx");
	}
    
	public void yyy() {
		System.out.println("destoryMethod.xxx");
	}
    
    public List<String> list(){
		return Arrays.asList("홍길동", "이순신");
	}
}

JavaConfig.java

@Configuration
public class JavaConfig {
	
	@Bean(value="myService", initMethod = "xxx", destroyMethod = "yyy")
	public DBSerivce service() {
		return new DBSerivce();
	}
}

📙{InitializingBean, DisposableBean} implements해서 확인

DBService.java

public class DBSerivce implements InitializingBean, DisposableBean{

	@Override
	public void destroy() throws Exception {
		System.out.println("DisposableBean.destroy=================");
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		System.out.println("InitializingBean.afterPropertiesSet==============");
	}
    
    public List<String> list(){
		return Arrays.asList("홍길동", "이순신");
	}
}

JavaConfig.java

@Configuration
public class JavaConfig {
	
	@Bean(value="myService")
	public DBSerivce service() {
		return new DBSerivce();
	}
}

📒 Annotation 기반 확인

DBService.java

public class DBSerivce{

	@PostConstruct
	public void init() {
		System.out.println("@PostConstruct.init==========");
	}

	@PreDestroy
	public void dest() {
		System.out.println("@PreDestroy.dest==========");
	}
	
	public List<String> list(){
		return Arrays.asList("홍길동", "이순신");
	}
}

JavaConfig.java

@Configuration
public class JavaConfig {
	
	@Bean(value="myService")
	public DBSerivce service() {
		return new DBSerivce();
	}
}


❗@Value

@Value는 특정 값을 주입하기 위한 용도.
주로 외부의 리소스나 환경정보 설정값을 사용하는 경우이다.
이를 .properties에서 값을 가져와 사용할 것이다.
application.properties

user.username=홍길동
user.age=20

User.java

public class User {
	@Value("${user.username}")
	String username;
	@Value("${user.age}")
	int age;		//username과 age에 값 주입
    
	@Override
	public String toString() {
		return "User [username="+username+"] [age="+age+"]";
	}
}

JavaConfig.java
bean 생성

@Configuration
public class JavaConfig {
	
    @Bean
	public User user() {
		return new User();
	}
}

SpringBootApplication.java

@SpringBootApplication
public class Boot08externalizeConfig1ValueApplication {

	public static void main(String[] args) {
		ApplicationContext ctx = SpringApplication.run(Boot08externalizeConfig1ValueApplication.class, args);
		User user = ctx.getBean("user",User.class);
		System.out.println(user.toString());
	}
}

Environment

User.java

public class User {
	
	String username;
	int age;
	
	@Override
	public String toString() {
		return "User [username="+username+"] [age="+age+"]";
	}
	
	public User(String username, int age) {
		super();
		this.username = username;
		this.age = age;
	}
	
}

JavaConfig.java

@Configuration
public class JavaConfig {
	@Autowired
	Environment env;
	
	@Bean
	public User user() {
		return new User(env.getProperty("user.username"), Integer.parseInt(env.getProperty("user.age")));
	}
}

@ConfigurationProperties

User.java는 위의 Environment와 동일.
MyConfigurationProperties.java

@ConfigurationProperties(prefix = "user")
public class MyConfigurationProperties {
	
	private String username;
	private int age;
	
	public MyConfigurationProperties() {
		super();
		// TODO Auto-generated constructor stub
	}
	public MyConfigurationProperties(String username, int age) {
		super();
		this.username = username;
		this.age = age;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "MyConfigurationProperties [username=" + username + ", age=" + age + "]";
	}
}

JavaConfig.java

@Configuration
public class JavaConfig {
	
	@Autowired
	MyConfigurationProperties p;
	
	@Bean
	public User user() {
		return new User(p.getUsername(), p.getAge());
	}
}

SpringBootAppliaction.java
메인에 @EnableConfigurationProperties(MyConfigurationPropertis.class)를 넣어줘야 제대로 동작한다.

@SpringBootApplication
@EnableConfigurationProperties(MyConfigurationProperties.class)
public class Boot08externalizeConfig1ValueApplication {

	public static void main(String[] args) {
		ApplicationContext ctx = SpringApplication.run(Boot08externalizeConfig1ValueApplication.class, args);
		User user = ctx.getBean("myUser",User.class);
		System.out.println(user.toString());
	}
	
}


PropertySource - @Value / Environment

application.properties에 작성한 텍스트를 jdbc.properties파일을 만들어서 옮겨준다.

user.username=\uD64D\uAE38\uB3D9
user.age=20

JavaConfig.java
@Property는 Spring의 placeholder와 같다.

@Configuration
@PropertySource(value = "classpath:jdbc.properties")
public class JavaConfig {
	
	@Bean
	public User user() {
		return new User();
	}
}

User.java

public class User {
	
	@Value("${user.username}")
	String username;
	@Value("${user.age}")
	int age;
	
	@Override
	public String toString() {
		return "User [username="+username+"] [age="+age+"]";
	}
	
}



JavaConfig.java

@Configuration
@PropertySource(value = "classpath:jdbc.properties")
public class JavaConfig {
	
	@Autowired
	Environment env;
	
	@Bean
	public User user() {
		return new User(env.getProperty("user.username"),Integer.parseInt(env.getProperty("user.age")));
	}
}

User.java

public class User {
	
	String username;
	int age;
	
	public User(String username, int age) {
		super();
		this.username = username;
		this.age = age;
	}

	@Override
	public String toString() {
		return "User [username="+username+"] [age="+age+"]";
	}
	
}

Profile

여러 개의 Configuration

JavaConfig.java

@Configuration
@Profile("prod")
public class JavaConfig {
	
	@Bean
	public DBDao MysqlDao() {
		return new DBMySQLDAO();
	}
	
}

JavaConfig2.java

@Configuration
@Profile("dev")
public class JavaConfig2 {

	@Bean
	public DBDao OracleDao() {
		return new DBOracleDAO();
	}
	
}

application.properties
원하는 config파일을 지정한 alias로 SpringBootapplication에서 동작하게 만들 수 있다.

spring.profiles.active=dev
#spring.profiles.active=prod

단일 Configuration

@Configuration
public class JavaConfig {
	
	@Bean("xxx")
	@Profile("prod")	//application.properties에 설정
	public DBDao MysqlDao() {
		return new DBMySQLDAO();
	}

	@Bean("xxx")
	@Profile("dev")	//application.properties에 설정
	public DBDao OracleDao() {
		return new DBOracleDAO();
	}
	
}

❗none Configuration❗

DBOracleDAO.java

@Repository
@Profile("prod")
public class DBOracleDAO implements DBDao{

	@Override
	public List<String> list() {
		return Arrays.asList("oracle 홍길동", "oracle 이순신");
	}

}

DBMySQLDAO.java

@Repository
@Profile("dev")
public class DBMySQLDAO implements DBDao{
	
	@Override
	public List<String> list() {
		return Arrays.asList("mysql 홍길동", "mysql 이순신");
	}
	
}

2개의 댓글

comment-user-thumbnail
2023년 8월 18일

이렇게 유용한 정보를 공유해주셔서 감사합니다.

1개의 답글