
public abstract class Application {
public void openDocument() {
// do something
Document document = createDocument();
document.open();
// do something
}
// 구현 객체가 runtime 에 결정된다.(Dependency Injection)
protected abstract Document createDocument();
}
public abstract class Document {
public abstract void open();
}
public class CsvApplication extends Application {
@Override
protected Document createDocument() {
return new CsvDocument();
}
}
public class CsvDocument extends Document {
@Override
public void open() {
System.out.println("csvDocument opened");
}
}
public class Main {
public static void main(String[] args) {
Application csvApplication = new CsvApplication();
csvApplication.openDocument();
Application jsonApplication = new JsonApplication();
jsonApplication.openDocument();
}
}
스프링 프레임워크 DI를 사용하면 Application 클래스와 같이 사용할 클래스의 구현체를 확정하지 않는 업무코드를 작성할 수 있다.
DI 정의
프로그래밍에서 구성 요소간의 의존관계가 소스코드 내부가 아닌 외부의 설정파일 등을 통해 정의되게 하는 디자인 패턴 중의 하나
DI는 디자인 패턴이다. 핵심 원칙은 의존성 이슈로부터 행동(behaviour)를 분리시키는 것이다.
DI는 IoC의 구현일뿐이다.
Annotation 기반 설정을 가능하게 하려면 설정을 변경해야 한다.
Autowired를 annotation에 쓰면 확장할 수 있다.
public class GreetingService {
private final List<Greeter> greeters;
@Autowired
public GreetingService(List<Greeter> greeters) {
// EnglishGreeter, KoreanGreeter 스프링 빈이 주입 됨.
// 순서는 알 수 없다.
this.greeters = greeters;
}
public void greet() {
greeters.forEach(Greeter::sayHello);
}
}
Stream에 대해서 함수 조작을 한 결과를 Stream으로 반환하는 중간조작과 처리 결과를 Data로 반환하는 종단조작이 있다.
type으로 autowired한 경우, 같은 타입의 여러 빈이 존재할 경우에 오류가 발생한다. 이 때, 특정 빈을 지정하는 방법을 제공한다.
<bean id="koreanGreeter" class="com.nhn.edu.springframework.ioc.helloworld.KoreanGreeter" scope="prototype" primary="true" >
</bean>
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Lang {
String value();
}
@Autowired
public GreetingService(@Lang("englishGreeter") Greeter greeter) {
this.greeter = greeter;
}
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Lang {
}
@Autowired
public GreetingService(@Lang Greeter greeter) {
this.greeter = greeter;
}
<bean id="englishGreeter" class="com.nhnacademy.edu.springframework.greeting.service.EnglishGreeter" scope="singleton">
<qualifier type="com.nhnacademy.edu.springframework.greeting.annotation.Lang"/>
</bean>
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface GreeterQualifier {
Language language();
boolean dummy();
}
public enum Language {
KOREAN, ENGLISH
}
@Autowired
public GreetingProcessor(@GreeterQualifier(language = Language.ENGLISH, dummy = false) Greeter greeter) {
this.greeter = greeter;
}
<bean id="englishGreeter" class="com.nhn.edu.springframework.ioc.helloworld.EnglishGreeter" scope="singleton" init-method="init">
<qualifier type="GreeterQualifier">
<attribute key="language" value="ENGLISH"/>
<attribute key="dummy" value="false"/>
</qualifier>
</bean>
from=Manty
<beans>
....
<context:property-placeholder location="classpath:greeter.properties" />
</beans>
public class GreetingService {
private final Greeter greeter;
@Value("${from}")
private String from;
@Autowired
public GreetingService(@Qualifier("koreanGreeter") Greeter greeter) {
this.greeter = greeter;
}
public boolean greet() {
// 인터페이스의 메소드를 호출하지만 실제 구현 객체의 메소드가 실행됩니다.
System.out.println("From : " + from);
return greeter.sayHello();
}
}
Spring Bean에서만 Application context에 등록한 설정을 사용할 수 있다.
@Configuration
public class JavaConfig {
@Bean/*(name = "dbms")*/
public String dbms() {
return new String("MYSQL");
}
}
위의 설정은 다음의 XML 설정과 동일하다.
<bean id="dbms" class="java.lang.String">
<constructor-arg type="java.lang.String" value="MYSQL" />
</bean>
default
public interface BaseJavaConfig {
@Bean
default String dbms() {
return new String("MYSQL");
}
}
@Configuration
public class JavaConfig implements BaseJavaConfig{
}
AnnotationConfigApplicationContext(Class<?>... componentClasses)
AnnotationConfigApplicationContext(String... basePackages)
public class GreetingService {
private final Greeter greeter;
@Autowired
public GreetingService(@GreeterQualifier(language = Language.KOREAN, dummy = false) Greeter greeter) {
this.greeter = greeter;
}
public void greet() {
greeter.sayHello();
}
public void init() {
System.out.println(this.getClass().getCanonicalName()+ ": init!!");
}
public void cleanup() {
System.out.println(this.getClass().getCanonicalName()+ ": cleanup!!");
}
}
@Configuration
public class BeanConfig {
@Bean(initMethod = "init", destroyMethod = "cleanup")
public GreetingService greetingService(Greeter greeter) {
GreetingService greetingService = new GreetingService(greeter);
return greetingService;
}
}
암묵적인 destroyMethod
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
return (DataSource) jndiTemplate.lookup("MyDS");
}
@Bean
@Scope("prototype")
public Greeter koreanGreeter() {
return new KoreanGreeter();
}
// 아래 스프링빈의 이름은 korean 입니다.
@Bean(name="korean")
public Greeter koreanGreeter() {
return new KoreanGreeter();
}
// 아래 스프링빈의 alias 는 korean, koreanGreeter 입니다.
@Bean(name={"korean", "koreanGreeter"})
public Greeter koreanGreeter() {
}
@Configuration
public class JavaConfig {
@Bean
public ARepository aRepository() {
return new ARepositoryImpl();
}
@Bean
public AService aService(ARepository aRepository) {
return new ASergice(aRepository);
}
}
@Configuration
public class JavaConfig {
@Bean
public ARepository aRepository() {
return new ARepositoryImpl();
}
// with method parameter
@Bean
public AService aService() {
return new AService(aRepository());
}
}
@Configuration
public class JavaConfig {
@Autowired
private ARepository aRepository;
@Bean
public AService aService() {
return new AService(aRepository);
}
@Bean
public BService bService() {
return new ASergice(aRepository);
}
}
@Configuration
public class JavaConfig {
@Autowired
private ARepositoryConfig aRepositoryConfig;
@Bean
public AService aService() {
return new AService(aRepositoryConfig.aRepository());
}
}
@Configuration
public class JavaConfig {
private final ARepository aRepository;
public JavaConfig(ARepository aRepository) {
this.aRepository = aRepository;
}
@Bean
public AService aService() {
return new AService(aRepository);
}
@Bean
public BService bService() {
return new ASergice(aRepository);
}
}
@Conditional(TestCondition.class)
@Bean
public GreetingService greetingService(Greeter greeter) {
GreetingService greetingService = new GreetingService(greeter);
return greetingProcessor;
}
class TestCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return true;
}
}
@Configuration
@ComponentScan(basePackages = "com.nhnacademy.edu.springframework.greeting")
public class BeanConfig {
// .. 생략 ..
}
Bean Scanning의 대상이 되는 어노테이션들
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
// ...
}
| Filter type | Example Expression | 설명 |
|---|---|---|
| annotation(default) | org.example.SomeAnnotation | 어노테이션이나 메타 어노테이션이 설정된 컴포넌트 |
| assignable | org.example.SomeClass | 특정 클래스를 상속/구현(extends/implements)한 컴포넌트 |
| aspectj | org.example..*Service+ | Aspect 표현식에 대응 되는 컴포넌트 |
| regex | org\.example\.Default.* | 정규식 표현에 대응되는 컴포넌트 |
| custom | org.example.MyTypeFilter | org.springframework.core.type.TypeFilter 인터페이스의 커스텀 구현체 |
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
// ...
}
AOP는 OOP의 확장이다.
cross-cutting concerns을 각각 모듈로 분리할 수 있다.
유지보수성 때문에 Spring AOP를 사용한다고 해도 과언이 아니다.
Aspect
Join point
public interface JoinPoint {
/* 중간 생략 */
/**
* The legal return values from getKind()
*/
String METHOD_EXECUTION = "method-execution";
String METHOD_CALL = "method-call";
String CONSTRUCTOR_EXECUTION = "constructor-execution";
String CONSTRUCTOR_CALL = "constructor-call";
String FIELD_GET = "field-get";
String FIELD_SET = "field-set";
String STATICINITIALIZATION = "staticinitialization";
String PREINITIALIZATION = "preinitialization";
String INITIALIZATION = "initialization";
String EXCEPTION_HANDLER = "exception-handler";
String SYNCHRONIZATION_LOCK = "lock";
String SYNCHRONIZATION_UNLOCK = "unlock";
String ADVICE_EXECUTION = "adviceexecution";
}
Advice
Pointcut
Target object
AOP Proxy
Advisor
Weaving
@Pointcut("execution(* transfer(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature
@Pointcut(
"execution(" // Pointcut Designator
+ "[접근제한자 패턴] " // public
+ "리턴타입 패턴" // long
+ "[패키지명, 클래스 경로 패턴]" // com.nhnacademy.GreetingService
+ "메소드명 패턴(파라미터 타입 패턴|..)" // .greet(User, String)
+ "[throws 예외 타입 패턴]"
+")"
)
@Pointcut("execution(public boolean com.nhnacademy.edu.springframework.greeting.service.Greeter.sayHello(..))
@Pointcut("execution(boolean com.nhnacademy.edu.springframework.greeting.service.Greeter.sayHello())")
public void greeterPointCut(){}
@Pointcut("within(com.nhnacademy.edu.springframework.greeting.service.Greeter)")
public void greeterPointCut(){}
bean(idOrNameOfBean)
// anyPublicOperation 포인트컷은 모든 public 메소드 실행에 매칭 됩니다.
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}
// inTrading 포인트컷은 com.xyz.myapp.trading 패키지 내의 메소드 실행에 매칭
@Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {}
// tradingOperation 포인트컷은 com.xyz.myapp.trading 패키지 내의 퍼블릭 메소드 실행에 매칭
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class CommonPointcuts {
/**
* com.xyz.myapp.web 패키지와 서브패키지(web layer)를
* 지정하는 포인트컷
*/
@Pointcut("within(com.xyz.myapp.web..*)")
public void inWebLayer() {}
/**
* com.xyz.myapp.service 패키지와 서브패키지(service layer)를
* 지정하는 포인트컷
*/
@Pointcut("within(com.xyz.myapp.service..*)")
public void inServiceLayer() {}
/**
* com.xyz.myapp.dao 패키지와 서브패키지(data access layer)를
* 지정하는 포인트컷
*/
@Pointcut("within(com.xyz.myapp.dao..*)")
public void inDataAccessLayer() {}
/**
* 아래 businessService 포인트컷 정의는 서비스인터페이스가 service 패키지에 있고
* 구현체가 service 패키지 하위에 포한된 것을 가정하고 선언되어 있습니다.
*
* com.xyz.myapp.send.service, com.xyz.myapp.receive.service 와 같이 기능단위의 패키지 구성이라면 "execution(* com.xyz.myapp..service.*.*(..))" 포인트컷 표현식을 사용할 수 있습니다.
*
* 만약 스프링빈 이름이 Service 로 항상 끝난다면 "bean(*Service)" 표현식을 사용할 수도 있습니다.
*/
@Pointcut("execution(* com.xyz.myapp.service.*.*(..))")
public void businessService() {}
/**
* 아래 dataAccessOperation 포인트컷 정의는 Dao 인터페이스가 dao 패키지에 있고
* 구현체가 dao 패키지 하위에 포한된 것을 가정하고 선언되어 있습니다.
*/
@Pointcut("execution(* com.xyz.myapp.dao.*.*(..))")
public void dataAccessOperation() {}
}
<aop:config>
<aop:advisor
pointcut="com.xyz.myapp.CommonPointcuts.businessService()"
advice-ref="tx-advice"/>
</aop:config>
<tx:advice id="tx-advice">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
execution(public * *(..))
execution(* get*(..))
execution(* com.nhnent.edu.spring_core.*.*(..))
execution(* com.nhnent.edu.spring_core.service.MemberService.*(..))
within(com.nhnent.edu.spring_core.service.*)
this(com.nhnent.edu.spring_core.service.TestService)
target(com.nhnent.edu.spring_core.service.TestService)
args(java.io.Serializable)
@target(org.springframework.transaction.annotation.Transactional)