bdfgdfg
스프링 프레임워크 간단히 짚기(DI,AOP) 본문
프레임워크
라이브러리가 특정한 기능을 수행하는 모듈(함수등)들이 모인 집합이라고 하면,
프레임워크는 그 모듈들을 사용해 만들어진 틀, 구조이다.
-> 즉 사용자는 그 구조안에서 작업을 해야함.
-> 자유도는 떨어지지만 프레임워크의 특징만 잘 이해한다면 빠른 구현과 재사용성, 유지보수는 높아진다.
스프링 프레임워크
자바 언어로 이루어졌으며 웹 애플리케이션 개발을 위한 오픈 소스 프레임워크.
톰캣이 서블릿 컨테이너라면 스프링은 경량 컨테이너. 애플리케이션에서 사용되는 여러가지 빈을 스프링이 권한을가지고 직접 관리한다.
자세한 특징
- 제어 역행(IoC) 기술을 이용해 애플리케이션간의 느슨한 결합을 제어한다.
- 의존성 주입(DI) 기능을 지원
- 관점 지향(AOP) 기능을 이용해 자원을 관리
의존성 주입 : 클래스 객체를 개발자가 코드에서 생성하지않고 프레임워크가 생성하여 사용하는 방법
제어 역행 : 서블릿이나 빈등을 개발자가 코드에서 생성하지않고 프레임워크가 직접 수행
관점 지향 : 핵심 기능외 부수 기능들을 분리 구현함으로서 모듈성을 증가.
의존성 주입(DI, Dependency Injection)
지금까지는 한 클래스가 다른 클래스의 기능을 사용하려면 개발자가 코드에서 클래스의 생성자를 호출해서 사용했다.
의존성 주입은 이런 연관 관계를 개발자가 관리하는것이 아닌, 컨테이너가 연관 관계를 직접 규정.
즉 스프링 프레임워크에서는 각 클래스들의 연관 관계를 클래스들 사이에서 맺는것이 아니라 스프링 설정을 통해서 클래스들이 연관 관계를 맺지 않게 구현했음.
정리하면
의존성 주입은 클래스들 간 의존 관계를 최소화해 코드를 단순화. 이로 인한 쉽게 유지보수가 가능.
기존 방식은 직접 코드를 생성해(DAO,Service등) 객체의 생성과 소멸을 제어. 의존성 주입은 생성,소멸과 객체간 의존 관계를 컨테이너가 제어.
스프링에서 의존성 주입(이제 DI라고함)를 구현하려면 XML이나 어노테이션을 이용해 객체를 주입하면 된다.
-> 스프링에서는 의존하는 객체를 컨테이너 실행 시 주입하기에 DI라고하며, 여기서 각 클래스 객체를 bean이라 한다.
스프링에서 DI 설정은 XML파일에서 한다. (생성자를 이용한 주입방식과 setter를 이용한 주입방식이 있음)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<bean id="personService" class="com.spring.ex01.PersonServiceImpl">
<property name="name">
<value>홍길동</value>
</property>
</bean>
</beans>
각 속성들 특징
id : bean 객체의 고유 이름으로, bean id를 통해 빈에 접근.
name : 객체의 별칭
class : 생설할 클래스. 패키지 명까지 입력해야한다.
constructor-arg : 생성자를 이용해 값을 주입할 때 사용
property : setter를 이용해 값을 주입할 때 사용
PersonService 인터페이스를 다음과 같이 작성
package com.spring.ex01;
public interface PersonService {
public void sayHello();
}
PersonServiceImpl클래스를 다음과 같이 작성.
package com.spring.ex01;
public class PersonServiceImpl implements PersonService {
private String name;
private int age;
//setter를 이용한 주입
public void setName(String name) {
this.name = name;
}
@Override
public void sayHello() {
System.out.println("이름: " + name);
System.out.println("나이: " + age);
}
}
인터페이스 PersonService를 통한 구현 및 setter를 이용해 앞선 person.xml의 value태그로 설정한 값을 name속성에 주입.
-> age속성은 value태그에 넣어주지않았으므로 주입되지않는다 -> 값이 초기화 되지않음.
이제 실행을 위한 PersonTest클래스.
package com.spring.ex01;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;
public class PersonTest {
public static void main(String[] args) {
BeanFactory factory = new XmlBeanFactory(new FileSystemResource("person.xml"));
// id가 personService인 bean을 가져온다.
PersonService person = (PersonService) factory.getBean("personService");
// PersonService person = new PersonServiceImpl(); (더이상 직접 생성하지않는다.)
person.sayHello();
}
}
XML파일을 읽어와 빈을 생성하는 코드며 PersonService를 구현한 PersonService의 sayHello를 호출하게 된다.
생성자를 이용한 방식은 xml에서 property가 아닌 constructor-arg를 통해 하면된다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<bean id="personService1" class="com.spring.ex02.PersonServiceImpl">
<constructor-arg value="이순신" />
</bean>
<bean id="personService2" class="com.spring.ex02.PersonServiceImpl">
<constructor-arg value="손흥민" />
<constructor-arg value="23" />
</bean>
</beans>
빈의 속성으로 주입되는 타입이 기본형 데이터가 아닌 참조형 데이터일 경우.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<bean id="memberService" class="com.spring.ex03.MemberSerivceImpl">
<property name="memberDAO" ref="memberDAO"/>
</bean>
<bean id="memberDAO" class="com.spring.ex01.MemberDAOImpl"/>
</beans>
보면 memberSerivce인 bean은 memberDAO주입되고 있다. (ref 태그 이용)
package com.spring.ex01;
public class MemberServiceImpl implements MemberService {
private MemberDAO memberDAO;
public void setMemberDAO(MemberDAO memberDAO) {
this.memberDAO = memberDAO;
}
@Override
public void listMembers() {
memberDAO.listMembers();
}
}
setter를 이용해 memberDAO가 주입되고 있고.
주입된 Bean의 구현은
package com.spring.ex01;
public class MemberDAOImpl implements MemberDAO {
@Override
public void listMembers() {
System.out.println("listMembers 메소드 호출.");
}
}
중요한건 인터페이스를 상속받은 클래스를 직접 생성하지않고도 bean에 설정된 값만으로(구현된 클래스, 속성값) 들고오기만 하여 수행이 가능해짐.
스프링 AOP(Aspect Oriented Programming)
AOP는 메소드안의 주기능과 보조기능을 분리한 후 선택적으로 메소드에 적용해 사용한다는 개념.
예로들어 주기능은 회원관리이며, 보조 기능은 로깅, 보안, 트랜잭션 등등이 될 수 있겠다.
AOP 관련 용어
aspect : 구현하고자 하는 보조 기능을 의미
advice : aspect의 실제 구현체(클래스)를 의미. 메소드 호출을 기준으로 여러 지점에서 실행
joinpoint : advice를 적용하는 지점을 의미. 스프링은 method 결합점만 제공
pointcut : advice가 적용되는 대상을 지정. 패키지이름/클래스이름/메소드이름을 정규식으로 지정하여 사용
target : advice가 적용되는 클래스를 의미. (어느 대상에 보조 기능을 부여할지?)
weaving : advice를 주기능에 적용하는 것을 의미
스프링 프레임워크에서 AOP기능을 구현하는 방법은 다음과 같다.
- 스프링 프레임워크에서 제공하는 api사용
- 애너테이션 사용(@Aspect)
AOP 기능을 구현하는 과정은 다음과 같다.
- 타깃 클래스 및 어드바이스 클래스를 지정.
- 설정파일에서 포인트컷을 설정하고, 어드바이스와 포인트컷을 결합하는 어드바이저 설정
- 설정파일에서 스프링의 ProxyFactoryBean 클래스를 이용해 타깃에 어드바이스를 설정
- getBean() 메소드로 빈 객체에 접근해 사용
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<bean id="calcTarget" class="com.spring.ex01.Calculator" />
<bean id="logAdvice" class="com.spring.ex01.LoggingAdvice" />
<bean id="proxyCal"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="calcTarget"/>
<property name="interceptorNames">
<list>
<value>logAdvice</value>
</list>
</property>
</bean>
</beans>
<bean>태그를 이용해 타깃 빈과 어드바이스 빈을 생성 후 스프링의 ProxyFactoryBean 클래스 빈 생성시,
property태그를 이용해 타깃 빈과 어드바이스 빈을 엮어준다. (어드바이스가 여러개라면 value태그로 추가하면 된다)
이제 타깃이 되는 객체 Calculator클래스를 만들면
package com.spring.ex01;
public class Calculator {
public void add(int x, int y) {
int result=x+y;
System.out.println("결과:"+ result);
}
public void subtract(int x, int y) {
int result=x - y;
System.out.println("결과:"+ result);
}
public void multiply(int x, int y) {
int result=x * y;
System.out.println("결과:"+ result);
}
public void divide(int x, int y) {
int result=x / y;
System.out.println("결과:"+ result);
}
}
그리고 어드바이스 클래스. (여기서는 MethodInterceptor라는 인터페이스를 구현한 어드바이스클래스)
package com.spring.ex01;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class LoggingAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("[메소드 호출 전 : LogginAdvice");
System.out.println(invocation.getMethod() + "메소드 호출 전");
// invocation을 통해 메소드를 호출.
Object object = invocation.proceed();
System.out.println("[메소드 호출 후 : loggingAdvice");
System.out.println(invocation.getMethod() + "메소드 호출 후");
return object;
}
}
methodInfocation 객체는 대상 객체의 모든 정보를 담고있는 객체.
package com.spring.ex01;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class CalcTest {
public static void main(String[] args){
ApplicationContext context=new ClassPathXmlApplicationContext("AOPTest.xml");
// id가 ProxyCal인 빈에 접근
Calculator cal=(Calculator)context.getBean("proxyCal");
// 메소드 호출 전후에 어드바이스 빈이 적용된다.
cal.add(100,20);
System.out.println();
cal.subtract(100,20);
System.out.println();
cal.multiply(100,20);
System.out.println();
cal.divide(100,20);
}
}
타깃클래스인 Calculator에 메소드 호출 전 후로 로깅을 찍는 어드바이스 클래스에서 작성한 내용대로
메소드 호출전에는 LogginAdvice 를 찍고, 실제 메소드를 수행한 후, 메소드 호출 후인 loggingAdvice를 찍게된다.
정리하면 AOP는 보조기능이 핵심. 이 보조기능을 매번 코드로 넣었다면 코드의 중복성이 높아질 수 밖에없지만,
AOP를 활용해 우리는 주기능(비즈니스로직)에만 집중할 수 있다는것.
'웹프로그래밍 > Spring' 카테고리의 다른 글
rootContext.xml vs servletContext.xml vs web.xml (0) | 2024.01.20 |
---|