ApplicationContext 클래스도 Bean으로 등록되어 있기 때문에 의존주입 받을수 있습니다. 이 뿐 아니라 ApplicationContext가 상속하고 있는 BeanFactory, ResourceLoader 등도 Bean으로 등록되어 있기 때문에 ApplicationContext를 의존주입 받지 않고 필요한 기능을 가진 Bean을 의존주입해서 사용하는 것이 가시성에 좋습니다(ex : Bean과 관련된 처리를 할때는 BeanFactory사용).
setter를 활용하면 Bean을 필수적으로 의존주입 받을지 여부를 선택할수 있습니다. 옵션을 주지 않는 경우 의존주입 받을 Bean이 없는 경우 에러가 발생합니다. 이때 @Autowired(required = false)를 사용해서 만약 Bean이 존재하지 않는 경우 의존주입을 받지 않도록 설정이 가능합니다.
setter 메소드 이름은 상관이 없습니다.
필드에 직접 사용
필드 위에 @Autowired를 등록해서 사용이 가능합니다. 가장 심플하게 코드를 작성할수 있습니다. setter와 마찬가지로 required 옵션을 사용가능합니다.
1 2 3 4 5
@Component classBookStore{ @Autowired private Book book; }
외부 라이브러리를 Bean 등록하고 의존주입
수정이 불가능한 외부라이브러리를 Bean으로 등록하고 해당 Bean에 의존주입까지 해주어야 하는 경우가 있습니다.
testController.test(); System.out.println("등록 된 Bean" + Arrays.toString(applicationContext.getBeanDefinitionNames())); } }
1
등록 된 Bean[test, testController]
// 클래스 패스로 ApplicationContext 생성 (다른 방법도 존재) ApplicationContext context = new ClassPathXmlApplicationContext(“클래스패스경로”); context.getBeanDefinitionNames(); // 등록된 모든 Bean Name확인 context.getBean(“빈Name”); // Bean 가져오기
단점
XML로 작성하는 경우에는 문자열로 작성을 하기 때문에 자동완성기능 등 IDE의 도움을 받는 것에 제한이 되기 때문에 작성하는데에 어려움이 존재 합니다(지원 되는 것도 존재하지만 그래도 자바소스 작성시보다는 불편하다).
component-scan
지금까지는 수동으로 Bean을 등록을 하였었는데 이 방법으로는 수많은 Bean들을 직접 등록하기가 힘듭니다. 그래서 어노테이션을 통해서 좀더 간편하게 Bean등록하는 방법인 component-scan이 등장하게 되었습니다. package를 설정하여 해당 package내에서 @Component 어느테이션이 등록 된 객체를 Bean에 등록해 줍니다.
@Controller, @Service, @Repository 등 @Component 어노테이션을 상속한 어노테이션 또한 자동으로 등록 됩니다.
@Test publicvoidtest(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring/spring-bean.xml");
System.out.println("등록 된 Bean" + Arrays.toString(applicationContext.getBeanDefinitionNames())); } }
1
등록 된 Bean[componentScanTest, ...]
클래스 설정파일로 Bean 등록
Spring 3.0 이후 보전부터 자바 소스 코드로도 Bean을 등록 할수 있는 방법이 추가 되었습니다. 자바 소스 코드로 작성하게 되면 IED의 기능을 활용할수 있기 때문에 XML보다 작성이 용이하다는 장점이 있습니다.
자바소스로 Bean 구성방법은 @Configuration과 @Bean으로 쉽게 구성이 가능합니다. @Configuration은 해당 클래스가 설정파일임을 명시해 줍니다. @Bean은 메소드 정의시에 사용하며 해당 메소드가 반환한 객체를 Bean으로 등록해줍니다. default로 메소드이름이 Bean Name이 됩니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
@Configuration publicclassBeanConfig{
@Bean public Test test(){ returnnew Test(); }
@Bean public TestController testController(Test test){ TestController testController = new TestController(); testController.setTest(test); return testController; } }
1 2 3 4 5 6 7 8 9 10
public class TestControllerTest {
@Test public void test() { ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class); // 클래스설정파일로 Bean등록할때 사용하는 ApplicationContext System.out.println("등록 된 Bean" + Arrays.toString(applicationContext.getBeanDefinitionNames()));
} }
1
등록 된 Bean[..., test, testController]
@Bean을 활용한 DI에는 2가지 방법이 존재 합니다.
파라미터로 Bean 등록 된 객체를 작성하면 자동으로 주입해 줍니다(위의 방법).
@Bean을 사용하는 메소드를 호출합니다(이 방법은 같은 클래스 내부에 존재할때만 가능).
클래스 설정파일로 component-scan
@Bean을 이용하는 방법 또한 직접 작성을 해야하기 때문에 등록하기가 힘듭니다. XML에서 compoennt-scan을 사용하였듯이 클래스 설정파일에서도 마찬가지로 component-scan을 사용할수 있습니다.
@ComponentScan(“base 패키지명”)으로 설정파일에 작성해주면 사용이 가능합니다.
1 2 3 4 5
@Configuration @ComponentScan("kr.co.spring") public class BeanConfig {
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
@Component public class ComponentScanTest {
}
public class TestControllerTest {
@Test public void test() { ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class); System.out.println("등록 된 Bean" + Arrays.toString(applicationContext.getBeanDefinitionNames()));
} }
1
등록 된 Bean[..., componentScanTest]
@ComponentScan 옵션
@ComponentScan : 아무런 옵션도 주지 않는 경우로 해당 클래스가 위치한 패키지 내부를 basePackage로 설정합니다.
@ComponentScan(basePackages = “패키지명”) : 위의 예제와 같은 경우로 직접 basePackage를 설정합니다.
@ComponentScan(basePackageClasses = 클래스명.class) : 특정 클래스가 위치한 패키지를 basePackage로 설정할때 사용합니다.
스프링부트에서의 Bean 등록
스프링부트에서 제공하는 SpringBootApplication 어노테이션을 사용하면 위에서 작업했었던 component-scan, ApplicationContext 생성과 같은 소스코드를 작성할 필요가 없습니다. 해당 어노테이션이 자동으로 생성을 해 줍니다.
공부하면서 느낀점
XML로 Bean을 등록 하는 것보다는 IDE의 도움을 받을수 있는 자바소스로 설정하는게 좀더 편하다고 생각한다. 그리고 Bean을 직접 등록해서 사용하는 것보다는 component-scan을 이용하것이 좋을것 같다.
결론
직접 작성 한 클래스는 component-scan을 활용하고, 외부 라이브러리를 사용할시에는 @Bean을 직접 등록 해서 사용하자!
스프링은 Junit을 이용하는 테스트 컨텍스트 프레임워크를 제공한다. 이를 이용하면 어노테이션을 이용하여 간편하게 컨텍스트를 사용할수 있다. 해당 방법이 없이 Bean을 사용하려면 직접 ApplicationContext를 생성하는 번거로운 작업을 진행하여야 하나 이로 인해 편하게 작업을 진행 할수 있다.
DataSource는 ConnectionPool을 관리하기 위한 목적으로 사용되는 객체로 DataSource를 통해서 Connection을 얻어오고 반납하는 등의 작업을 구현합니다. ConnectionPool에는 여러개의 Connection 객체가 생성되어 운용되는데, 이를 직접 웹 어플리케이션에서 다루기 힘들기 때문에 DataSource라는 개념이 도입되었습니다.
DataSourceUtils
DataSourceUtils 클래스는 JNDI에서 연결을 얻고 필요한 경우 연결을 닫는 메소드를 제공하는 기능을 제공 합니다. 그리고 DataSourceTransactionManager로 쓰레드에 기반한 연결을 지원합니다.
DataSourceUtils에서 제공하는 메소드
getConnection(DataSource dataSource)
getConnection메소드는 제공 된 DataSource를 기반으로 하여 Connection을 생성해줍니다. Connection 생성은 트랜잭션 동기화 여부에 따라 다르게 작동합니다. 만약 트랜잭션 동기화가 활성화 되어 있다면 Connection을 생성하고 트랜잭션 저장소에 바인딩 합니다. 이후 트랜잭션 동기화 작업을 종료 할때까지 getConnection메소드를 호출하면 동일한 Connection을 반환하는 것을 확인할수 있습니다.
releaseConnection메소드 또한 getConnection와 비슷하게 동작 합니다. 만약 트랜잭션 동기화가 활성화 되어 있다면 트랜잭션 저장소에서 Connection을 초기화 합니다. 이때 Connection의 연결은 종료하지 않습니다. 그리고 release 후 다시 getConnection으로 Connection을 받아왔을때 동일한 값이라는 것을 확인 할수 있습니다.
var path = require('path') // output 속성에서 사용할 노드 path 라이브러리 저장 var webpack = require('webpack') // 웹팩 플러그인에서 사용할 웹팩 라이브러리를 저장
// 아래부터가 실질적인 웹팩의 설정 내용 module.exports = { entry: './src/main.js', // 웹팩으로 빌드할 파일 지정(해당 파일의 앱의 첫 시작 파일이 되는 것) output: { // output은 build 후에 생성 될 파일들에 대한 설정 path: path.resolve(__dirname, './dist'), // 빌드후에 결과 파일 저장 될 디렉토리 publicPath: '/dist/', // 이미지 파일 같이 주소의 앞에 prefix로 붙여주어 주소에 접근 하도록 해주는 것 filename : 'build.js'// 빌드하고 난 후 결과 파일 } module: { // 지금까지는 loader를 사용하는 것 정도만 확인 했음 rules: [ { test: /\.vue$/, // 로더가 적용 될 대상 파일 지정(정규식) loader: 'vue-loader'// loader는 사용할 로더 지정 }, { test: /\.js$/ loader: 'babel-loader', exclude: /node_moules/// 해당 디렉토리에서의 탐색은 제외하겠다 } ] }, resolve: { alias: { // 해당 하는 경로를 @로 대신 사용 가능하다, 여러개 편의성있게 등록 가능하다 '@': path.resolve(__dirname, 'src/') }, extensions: ['*', '.js', '.vue', '.json'] // 해당하는 확장자들을 import하게 도와주는 역할 }, devtool: '#eval-source-map'// 웹팩으로 빌드된 파일로 웹앱 구동시에 개발자 도구에서 사용할 디버깅 방식 지정 } ``` ## 웹팩 설정 구성에 사용 되는 용어 정리 ### 1. entry
entry : 웹팩을 통해서 build를 할 대상 파일을 지정하는 속성 - 해당 파일에는 전체 애플리케이션 로직과 필요한 라이브러리를 로딩하는 로직이 들어가게 됩니다.
1 2
### 2. output
output : 웹팩으로 빌드한 결과물의 위치와 파일이름 등 세부옵션을 설정하는 속성
1 2
### 3. loader
loader : 웹팩으로 빌드할때 html, css 파일등을 자바스크립트로 변환하기 위해 필요한 설정을 정의
1 2
### 4. plugin
plugin : 웹팩으로 빌드 하고 나온 결과물에 대해 추가기능을 제공하는 속성 - 한마디로 필수옵션은 아니지만 빌드할때 혹은 결과물에 추가적인 기능을 적용하고 싶을 때 사용
1 2
### 5. resolve
resolve : 웹팩으로 빌드할때 해당 파일이 어떻게 해석되는지 정의하는 속성 - 예) 라이브러리 로딩시 버전은 어떤걸로 하고, 경로는 어디로 하고 등에 관한 것
흐름 : 부모 => actions => API => mutations => state => 자식에서 state 접근
이 방법은 vuex를 최대한 활용 하는 방법으로 데이터를 표현하는 곳에서 직접 state에 접근 하는 방법이다. 이때 데이터를 표현하는 곳에서는 actions를 호출하지 않는다. 내 생각에 actions에 대한 결합도가 높아지기 때문인듯하다(해당 컴포넌트의 범용성이 낮아진다).
2. props 활용
1
흐름 : 부모 => actions => API => mutations => state => 부모에서 state에 접근 후 props로 전달 => 자식
this.$store.disaptch('메소드명'); this.$store.disaptch('메소드명', 값); // 값은 오직 하나만 전달 가능하므로 여러개를 전달하려면 객체를 통해 전달한다.
actions에서 api통신을 한 결과를 state에 전달하여야 하는데 이때 actions 내부에서 바로 state에 접근이 불가능하다. mutations라는 것을 통해서만 가능하다. actions에 정의된 메소드의 파라미터로 context값이 넘어오는데 context의 commit 메소드를 호출하면 mutation에 전달가능하다.
1 2 3 4 5 6 7 8 9 10 11 12
var actions = { state: {}, actions: { FETCH_DATA(context, param) { // param에 dispatch를 실행할때 넘겼던 값을 받을수 있다. fetchDataList() .then(({ data }) => { context.commit('mutation메소드명', '전달할 값') }) .catch() } } }
3. mutations
actions에서 backend(api)와이 실행 결과를 state에 저장할때 사용되는 중간 연결자이다. actions에서 context.commit으로 실행될 메소드를 정의한다.
// state 값 사용 할 파일 import { mapGetters } from'vuex';
var Vue = { computed: { ...mapGetters(['value']) } }
getters는 computed와 비슷한 역할을 한다. 컴포넌트에서는 mapGetter에서 배열을 넘겨준다(이때 배열의 문자열이 vuex의 getters에 정의 된 메소드 이름을 넘겨주면 해당값을 받아온다. getters에 등록 하는 번거로움이 있지만 사용할때는 변수명이 명료해서 보기 좋다.
5. vuex의 mapGetter를 이용(mapGetters의 객체를 이용하는 방법)
vuex 파일의 사용법은 위와 동일하다.
1 2 3 4 5 6 7 8 9 10
// state 값 사용 할 파일 import { mapGetters } from'vuex';
배열을 이용할때는 변수명을 마음대로 지정이 불가능하지만 객체를 이용하면 이름을 컴포넌트에서 마음대로 지정 할수 있다.
모듈화
하나의 파일에서 모든 데이터를 관리하면 알아보기 힘들기 때문에 모듈화 작업을 한다. mutations, actions, state, getters를 따로 파일을 만들어서 관리한다. 상황에 따라 분리 할수도 있고 길지 않다면 하나의 파일에서 관리할수 있고 더 세분화로 분리할수도 있다.