AOP가 어떤식으로 구현되는지는 이해를 했습니다.

 

이제 그 AOP를 내가 하는게 아니라 스프링 컨테이너가 해줘야 할텐데요

 

applicationContext 안의 namespace를 보면 AOP를 체크해 줄 수가 있습니다.

 

이전에는 context를 체크해줘서 component-scan을 해주었던게 기억이 나실겁니다.

 

이번엔 AOP를 체크해주어 자동으로 구현해보도록 하겠습니다.

 

일단 Aspect를 하나 만들어봅니다.

 

package com.duljji;

import org.springframework.stereotype.Component;

@Component
public class TestAspect {

	public void before() {
		System.out.println("음료수 뚜껑을 연다");
	}
	
	public void after() {
		System.out.println("음료수 뚜껑을 닫는다.");
	}
}

 

 

이렇게 aspect를 등록해준뒤, applicationContext에 들어가 AOP를 사용할 준비를 해줍니다.

 

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

	<context:component-scan base-package="com.duljji"></context:component-scan>
	
	<aop:config>
	<aop:pointcut expression="execution(public void com.duljji.*.greet())" id="pointcut"/>
	<aop:aspect ref="testAspect">
	<aop:before method="before" pointcut-ref="pointcut"/>
	</aop:aspect>
	</aop:config>
</beans>

namespace에서 aop를 체크하여 xmlns:aop가 자동으로 등록되게 한 뒤

 

aop config를 이용하여 aop를 등록해줍니다.

 

여기서 AOP의 용어에 대해 잠깐 체크하고 넘어가겠습니다.

 

  1. 애스펙트 (Aspect): 애스펙트는 AOP의 기본 단위로, 관심사를 모듈화한 것을 말합니다. 애스펙트는 어드바이스(Advice)와 포인트컷(Pointcut)의 조합으로 구성됩니다. 어드바이스는 특정 지점에서 실행되는 동작을 정의하고, 포인트컷은 어드바이스가 적용될 조인포인트(Joinpoint)를 선택하는 기준입니다.
  2. 조인포인트 (Joinpoint): 조인포인트는 AOP에서 어드바이스를 적용할 수 있는 프로그램의 실행 지점을 의미합니다. 예를 들어, 메서드 호출, 필드 접근, 예외 발생 등이 조인포인트의 예시입니다. 어드바이스는 조인포인트에서 실행됩니다.
  3. 포인트컷 (Pointcut): 포인트컷은 애스펙트가 어드바이스를 적용할 조인포인트를 선택하기 위한 기준을 정의하는 것입니다. 포인트컷은 표현식이나 패턴을 사용하여 특정 조인포인트를 지정할 수 있습니다. 예를 들어, "모든 메서드 호출" 또는 "특정 패키지의 모든 클래스의 특정 메서드"와 같은 조건을 포인트컷으로 지정할 수 있습니다.
  4. 어드바이스 (Advice): 어드바이스는 애스펙트가 조인포인트에서 실행하는 동작을 정의하는 것입니다. 예를 들어, 메서드 호출 전에 실행되는 "Before" 어드바이스, 메서드 실행 후에 실행되는 "After" 어드바이스, 예외가 발생했을 때 실행되는 "AfterThrowing" 어드바이스 등이 있습니다.
  5. 위빙 (Weaving): 위빙은 애스펙트를 대상 코드에 적용하는 과정을 의미합니다. 즉, 애스펙트의 어드바이스를 포인트컷에 맞는 조인포인트에 삽입하는 것입니다. 위빙은 컴파일 시점, 로드 시점, 런타임 시점에 수행될 수 있습니다. 애스펙트를 적용하는 방법에는 컴파일러, 로더, 프록시 객체 등을 사용하여 위빙을 수행할 수 있습니다.

Aspect를 어느 Joinpoint에서 실행할 지를 정해주는 것이 Poincut입니다.

 

즉, pointcut부분을 보면 public void com.duljji.*.greet() 이라고 되어있는데 duljji패키지의 모든 클래스가 greet을 실행할 때 동작을 하게 됩니다.

 

그리고 그 포인트컷의 before가 joinpoint가 되겠네요.

 

그리고 aop:before는 조인포인트 method="before"는 TestAspect 안에 들어가있는 메서드의 이름입니다.

 

그럼 이제 스프링이 알아서 weaving을 해주겠군요.

 

AOP에서 쓰이는 용어도 알아보았습니다.

 

aop:aspect 에는 ref로 해당 빈의 id값이 들어갈 것이고,

aop:before에는 당연히 어떤 포인트컷의 조인포인트인지 알아야하니 pointcut-ref가 들어가야겠죠?

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

	<context:component-scan base-package="com.duljji"></context:component-scan>
	
	<aop:config>
	<aop:pointcut expression="execution(public void com.duljji.*.greet())" id="pointcut"/>
	<aop:aspect ref="testAspect">
	<aop:before method="before" pointcut-ref="pointcut"/>
	<aop:after method="after" pointcut-ref="pointcut"/>
	</aop:aspect>
	</aop:config>
</beans>

그럼 이렇게 만들어 준뒤 실행을 하면 어떻게 될까요?

 

음료수 뚜껑을 연다 -> 인사 -> 음료수 뚜껑을 닫는다 가 실행될것입니다.

 

아까처럼 proxy패턴을 만들지 않고 그냥 Person객체를 만들어서 실행을 시켰는데도, 자동으로 함수 실행 이전과 이후에 특정 작업을 해주는 것을 볼 수 있습니다.

 

xml에 직접 넣어주지 않고 간단하게 어노테이션으로도  aspect를 작성할 수가 있습니다.

 

package com.duljji;

import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class TestAspect {

	
	
	
	public void before() {
		System.out.println("음료수 뚜껑을 연다");
	}
	
	public void after() {
		System.out.println("음료수 뚜껑을 닫는다.");
	}
}

@Aspect를 붙여주어 Aspect를 만들었습니다.

 

이 aspect는 파이썬의 decorator와 아주 흡사하게 작동을 합니다.

 

wrapper메서드를 만들고 함수를 받아서 그 이전, 이후에 작업을 해주는거죠.

 

wrapper메서드를 하나 만들어주도록 하겟습니다.

 

 

package com.duljji;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class TestAspect {

	@Pointcut("execution(public * com.duljji.*.greet())")
	public void wrapper() {}
	
	
	@Before("wrapper()")
	public void before() {
		System.out.println("음료수 뚜껑을 연다");
	}
	
	@After("wrapper()")
	public void after() {
		System.out.println("음료수 뚜껑을 닫는다.");
	}
}

 

이렇게 wrapper 메서드가 Pointcut이 되는것이죠. 

 

이제 어노테이션이 동작하게끔 하기 위해서 xml에 당므과 같이 autoproxy를 추가합니다.

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
	<aop:aspectj-autoproxy />
	<context:component-scan base-package="com.duljji"></context:component-scan>
	
	
</beans>

 

그럼 이제 어노테이션으로도 간단하게 AOP를 작성할 수 있게 되었습니다.

 

그럼 추가적으로 AOP의 다른 Joinpoint도 사용해보도록 하겠습니다.

 

package com.duljji;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class TestAspect {

	@Pointcut("execution(public * com.duljji.*.greet())")
	public void wrapper() {}
	
	
	@Before("wrapper()")
	public void before() {
		System.out.println("음료수 뚜껑을 연다");
	}
	
	@After("wrapper()")
	public void after() {
		System.out.println("음료수 뚜껑을 닫는다.");
	}
	
	@AfterReturning(value="wrapper()", returning="num")
	public void afterRunning(boolean num) {
		if(num == false) {
			System.out.println("음료수 병을 버립니다.");
		}else {
			System.out.println("음료수를 가방안에 넣습니다.");
		}
	}
}

이번엔 greet을 한 뒤 return값을 만들어주고 num 이라는 변수로 받았습니다. 

 

package com.duljji;

import java.util.Random;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Person {
	@Autowired
	public Beverage beverage;
	

	public Person() {
		
	}
	
	public Person(Beverage beverage) {
		this.beverage = beverage;
	}
	
	public boolean greet() {
		System.out.println("안녕하세요!! Person입니다.");
		return new Random().nextBoolean();
	}
	
	
	
	
}

Person객체의 greet에는 저렇게 boolean값을 리턴해주고, 그 값을 num으로 자동으로 넘겨받는겁니다.

 

하지만 순서를 보니 AfterReturning 이후에 After 함수가 실행이 되는것을 볼 수 있습니다.

 

순서를 잘 체크해서 returning에는 return값에 따라 바뀔 함수를 넣고 after함수에는 모든것이 마무리 된 이후에 작업할 것을 넣어야 할 것 같습니다.

 

AfterThrowing도 써보니 after의 역할을 좀 더 알 것 같습니다.

 

greet메서드를 다음과 같이 수정하여 간헐적으로 에러가 발생하게 만듭니다.

 

package com.duljji;

import java.util.Random;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Person {
	@Autowired
	public Beverage beverage;
	

	public Person() {
		
	}
	
	public Person(Beverage beverage) {
		this.beverage = beverage;
	}
	
	public boolean greet() {
		System.out.println("안녕하세요!! Person입니다.");
		if((int)(Math.random() * 100) > 50 ) {
			throw new RuntimeException();
		}
		return new Random().nextBoolean();
	}
	
	
	
	
}

 

그리고 AfterThrowing 어노테이션을 달아 에러가 발생할 때 호출할 메서드도 만들어줍시다.

 

package com.duljji;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class TestAspect {

	@Pointcut("execution(public * com.duljji.*.greet())")
	public void wrapper() {}
	
	
	@Before("wrapper()")
	public void before() {
		System.out.println("음료수 뚜껑을 연다");
	}
	
	@After("wrapper()")
	public void after() {
		System.out.println("음료수 뚜껑을 닫는다.");
	}
	
	@AfterReturning(value="wrapper()", returning="num")
	public void afterRunning(boolean num) {
		if(num == false) {
			System.out.println("음료수 병을 버립니다.");
		}else {
			System.out.println("음료수를 가방안에 넣습니다.");
		}
	}
	@AfterThrowing(value="wrapper()", throwing="error")
	public void afterThrowing(Throwable error) {
		System.out.println("음료수를 바닥에 떨어뜨렸습니다!");
	}
}

 

 

이렇게 하면 에러가 간헐적으로 발생하고 에러가 발생할 경우 음료수를 바닥에 떨어뜨렸습니다!! 라는 문구가 출력이 되겠죠.

 

음료수를 바닥에 떨어뜨리는 에러가 났음에도 After 메서드는 정상적으로 작동하는 것을 알 수 있습니다.

 

After보다는 AfterReturning을 더 자주 쓰게 될 것 같은 느낌이 강하게 듭니다.

 

이렇게 Aspect를 만들어서 사용을 해보았습니다.

 

이렇게 Logging이나 Security, 트랙잭션 관리와 같은 공통 관심사항들이 이렇게 관점들로 만들어진다면 아주 간단하게 재사용하면서 효율을 올릴 수 있을 것 같습니다.

OOP에 대한 개념밖에 알지 못했었지만 스프링은 AOP. 관점 지향 프로그래밍 개념을 사용합니다.

 

AOP란, 같은 일을 하는 것들을 모든 객체에 적용 시키는 것을 의미합니다.

 

예를들어 트랜잭션 관리가 있다고 했을 때, 배달 주문을 하든, 포장 주문을 하든, 전화 주문을 하든 모든 주문들은 주문이 생성될 때 데이터베이스에 주문 정보를 저장하고, 결제가 성공하면 커밋(Commit)을 수행하고, 결제가 실패하면 롤백(Rollback)을 수행할 것입니다.

 

각각의 주문들에 이것들을 넣어주기보단 트랜잭션 관리 aspect(관점)를 각각의 pointcut(해당 시점,지점)에 advice(실행)하는 것이 AOP입니다.

 

트랜잭션 관리 뿐 아니라, 보안작업, 로깅작업, 캐싱작업 등 같은 작업이 여러곳에 쓰일 때 그 작업을 하나의 관점으로 보고 (Cross Cutting 공통 관심사항) 처리해주는 것이 바로 AOP입니다.

 

같은 작업이라면 상속을 통해서도 처리해 줄 수 있지 않을까? 하는 생각이 들었지만,

 

상속은 객체의 계층구조를 만드는데, 특정 작업을 한다고 해서 그것을 상속시키는건 OOP적이지 않은 발상인 것 같습니다.

 

interface를 만든다 하더라도 결국 구현체에서 계속 overriding을 해주어야 하는데 이것도 여간 귀찮은 작업이 아니죠.

 

AOP는 동일한 기능을 하는 작업들을 하나의 관점으로 보고 모듈화 시켜 다양한 객체에 적용시키는 것이라고 이해하면 되겠습니다.

 

OOP를 보완해주는 느낌이네요.

 

AOP와 OOP의 차이점은 다음과 같습니다:

  1. 초점:
    • OOP: 객체지향 프로그래밍에서는 프로그램을 객체들의 상호작용으로 모델링하고, 객체의 상태와 동작에 중점을 둡니다.
    • AOP: 관심사 분리를 통해 프로그램의 공통된 관심사를 분리하여 모듈화하고, 핵심 비즈니스 로직과 분리된 관심사를 적용하는 데 중점을 둡니다.
  2. 목적:
    • OOP: 코드의 재사용성, 유지보수성, 확장성 등을 개선하며, 객체들 간의 상호작용을 중심으로 프로그램을 구조화합니다.
    • AOP: 프로그램의 횡단 관심사(로그 기록, 보안, 트랜잭션 관리 등)를 분리하여 모듈화하고, 이를 여러 모듈에 적용함으로써 코드 중복을 줄이고 가독성을 향상시킵니다.
  3. 적용 방식:
    • OOP: 상속, 다형성, 캡슐화 등의 개념을 사용하여 클래스와 객체의 구조를 설계하고 구현합니다.
    • AOP: 어드바이스, 애스펙트, 포인트컷 등의 개념을 사용하여 핵심 비즈니스 로직에 영향을 주지 않고 공통된 관심사를 모듈화합니다.

그렇다면 이 AOP는 어떻게 작성할 수 있을까요?

 

Proxy를 이용해서 쉽게 구현할 수가 있습니다.

 

Proxy가 뭔지 잘 이해가 가지 않는다면 파이썬의 Decorator를 생각하면 됩니다.

 

wrapper 함수를 만들고  wrapper함수는 특정 함수를 인자로 받습니다.

 

wrapper함수에서 인자로 받은 함수를 실행을 시켜주는데 그 전 후에 특정 행동을 추가해 줄 수가 있죠.

 

그렇다면 그 wrapper 함수가 특정 작업을 하는 기능이고 그것이 여러곳에 쓰인다면?

 

그것이 바로 Aspect가 되겠죠!!

 

그럼 한번 만들어보도록 하겠습니다.

 

이제 인사를 할 때, person의 greet 메서드를 실행 시키기 전과 후에 추가 동작을 해주는 PersonProxy를 만들겁니다.

 

package com.duljji;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class PersonProxy {

	@Autowired
	public Person person;
	
	public void greet() {
		
		//인사하기 전에 하는 행동
		System.out.println("사람 발견!!");
		//인사
		person.greet();
		
		//인사한 후에 하는 행동
		System.out.println("안부 묻기");
	}
}

person 객체의 의존성을 주입하고 똑같이 greet 메소드를 만듭니다.

 

하지만 이 greet메소드는 person.greet을 실행시키기 이전과 이후에 추가 동작을 수행합니다.

 

이렇게 Proxy 패턴을 사용하면 해당 메서드를 구현하기 이전과 이후에 추가적인 행동들을 구현해 줄 수가 있습니다.

 

그럼 저 이전의 위치에 관점들을 실행하는 advice들을 넣어주면 되겠군요!!

 

아!! 그럼 스플이 컨테이너의 AOP는 해당 메서드를 이런식으로 실행을 시켜서, 실행 전후에 동작을 추가해줄 수 있구나! 라고 이해하면 되겠습니다. 

 

이제 스프링 컨테이너에게 AOP를 시켜보도록 하겠습니다.

class를 만들 때 마다 일일이 넣어주는건 상당히 귀찮은 일입니다.

 

물론 필요할 때도 있겠지만요. 정해진 value 값을 넣어준다거나 할 때, Bean을 직접 만들어 property값들을 제어할 수 있다면 좀 더 디테일하게 다룰 수 있을 것 같습니다.

 

하지만 클래스를 만들 때마다 저렇게 xml에 들어가기에는 상당히 귀찮은 작업이 될 수 있습니다. 

 

이번엔 스프링이 자동으로 Component scan을 하게 만들어 봅시다.

 

아래쪽에 Namespaces를 클릭해주면 이런 체크박스가 나옵니다.

 

여기서 context에 체크를 해주면 이제 component-scan할 패키지를 적어줄 수 있게 됩니다.

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

	<context:component-scan base-package="com.duljji"></context:component-scan>
</beans>

 

자 이제 component scan의 패키지도 지정을 해주었으니 우리가 만들어둔 클래스를 Component로 만들어주도록 합시다.

 

방법은 매우 간단합니다. 클래스에 어노테이션만 달아주면 됩니다.

package com.duljji;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Person {
	
	
	public Coke coke;
	
	public Person() {
		
	}
	
	public Person(Coke coke) {
		this.coke = coke;
	}
	
	public void greet() {
		System.out.println("안녕하세요!! Person입니다.");
	}
	
	
	
	
}

 

 

자 다시 greet을 실행시켜 봅니다.

 

와... 자동으로 컴포넌트를 읽어들여 빈으로 만들어준 것을 확인할 수 있습니다.

 

이렇게 Component로 만들어두면 자동으로 의존성을 주입시켜 줄 수도 있는데요 

 

package com.duljji;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Person {
	
	@Autowired
	public Coke coke;
	
	public Person() {
		
	}
	
	public Person(Coke coke) {
		this.coke = coke;
	}
	
	public void greet() {
		System.out.println("안녕하세요!! Person입니다.");
	}
	
	
	
	
}

이렇게 Person클래스에 Coke클래스를 @Autowired를 이용하여 의존성 주입을 해주었습니다.

 

이렇게 컴포넌트로 만들어주면  @Autowired 어노테이션을 이용해 필드주입을 하는 것이 가능해집니다.

 

이전 방식과 마찬가지로 생성자와 Setter를 이용하여 주입도 당연히 가능합니다.

 

<생성자 주입>

package com.duljji;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Person {
	
	public Coke coke;
	
	public Person() {
		
	}
	
	@Autowired
	public Person(Coke coke) {
		this.coke = coke;
	}
	
	public void greet() {
		System.out.println("안녕하세요!! Person입니다.");
	}
	
	
	
	
}

 

Setter주입은 직접 해보시기 바랍니다.

 

 

자, 근데 이런식으로 음료수를 하나만 받는 것은 좀 이상합니다.

 

우리는 이제 음료수를 만들어서 모든 음료수들을 모두 받게 만들겁니다.

 

package com.duljji;

import org.springframework.stereotype.Component;

@Component
public class Coke implements Beverage {

		public void drink() {
			System.out.println("Coke를 마십니다.");
		}
}
package com.duljji;

import org.springframework.stereotype.Component;

@Component
public class Juice implements Beverage {

		public void drink() {
			System.out.println("Juice를 마십니다.");
		}
}

이렇게 Beverage 인터페이스를 만들어 Coke와 Juice 모두를 받을 수 있도록 합니다.

 

package com.duljji;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Person {
	@Autowired
	public Beverage beverage;
	

	public Person() {
		
	}
	
	public Person(Beverage beverage) {
		this.beverage = beverage;
	}
	
	public void greet() {
		System.out.println("안녕하세요!! Person입니다.");
	}
	
	
	
	
}

그런 뒤, 이렇게 Autowired를 하면 어떻게 될까요?

 

당연하지만 에러가 나게 됩니다. 인터페이스를 Autowired해주게 되면, 스프링 컨테이너는 해당 인터페이스를 구현하고 있는 구현체를 넣으려고 하는데, 구현체가 여러개면 어떤걸 넣어주어야 할 지 모르기 때문입니다.

 

하지만 그렇다고, 구현체에 Autowired를 하기에는 너무 결합이 강하게 됩니다.

**

Spring에서 @Autowired를 사용하여 인터페이스를 구현하고 있는 여러 개의 Bean 중 어떤 Bean을 주입해야 할지 알려주어야 합니다. 이를 위해 다음과 같은 방법들이 있습니다:

  1. @Qualifier 어노테이션 사용:
    • @Qualifier 어노테이션은 주입할 Bean을 한정할 수 있는 방법입니다.
    • 해당 어노테이션을 사용하여 주입받을 Bean의 이름이나 식별자를 지정합니다.

Qualifier 를 사용해보도록 합시다.

package com.duljji;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class Person {
	@Autowired
	@Qualifier("Coke")
	public Beverage beverage;
	

	public Person() {
		
	}
	
	public Person(Beverage beverage) {
		this.beverage = beverage;
	}
	
	public void greet() {
		System.out.println("안녕하세요!! Person입니다.");
	}
	
	
	
	
}

그런데 이렇게 해주면... Beverage를 나눈 의미가 없어 보입니다.

 

또 한가지 방법은

 

package com.duljji;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

@Component
@Primary
public class Juice implements Beverage {

	public void drink() {
		System.out.println("Juice를 마십니다.");
	}
	
}

 기본적으로 넣어줄 객체를 선정해주는 겁니다. 이렇게 Juice에 Primary 어노테이션을 넣어주면?

 

이렇게 기본적으로 Juice객체가 들어가게 됩니다.

 

아무래도 Autowired를 사용하면 정적인 주입을 하게 되니, 일기 1편에서 썼던것처럼 ApplicationContext 객체를 이용하여 동적으로 직접 주입해주는 것도 필요할 듯 합니다.

SSAFY 2학기 프로젝트를 시작하기 전 스프링 부트를 배우고 싶어 인프런 강의를 질렀으나,

 

스프링에 대한 이해가 바탕이 되어야 할 것 같아 스프링을 먼저 복습하기 위해 작성하는 블로그 글입니다.


STS에서 Java Project를 하나 만들어 준 뒤, configure를 이용하여 maven 프로젝트로 converting해줍니다.

 

그럼 아무것도 없는 Maven Project가 하나 만들어집니다.

 

프로젝트에서 New - source Folder를 눌러 resource 폴더를 하나 만들어줍니다.

 

이 source folder는 spring이 탐색하는 폴더가 될 것입니다.

 

이제 이 maven project에서 스프링을 사용할 수 있도록 dependency를 넣어주도록 합니다.

 

mvn repository로 들어가 스프링을 넣어주도록 합시다.

 

pom.xml을 다음과 같이 만들어줍니다.

 

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>Spring1</groupId>
  <artifactId>Spring1</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <build>
    <sourceDirectory>src</sourceDirectory>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependencies>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.18</version>
</dependency>
</dependencies>
  
</project>

 

이제 만들어준 resource 폴더에 applicationContext.xml 파일을 하나 만듭니다.

 

applicationContext가 뭐 하는 친구일까요?

 

Spring에서 ApplicationContext는 IoC (Inversion of Control) 컨테이너의 역할을 수행합니다. IoC 컨테이너는 애플리케이션의 구성 요소들을 생성하고 관리하며, 의존성 주입(Dependency Injection)을 통해 이들 사이의 관계를 설정합니다.

ApplicationContext는 Spring 프레임워크에서 제공하는 IoC 컨테이너의 구현체입니다. 이 컨테이너는 XML, Java 어노테이션, Java 설정 클래스 등을 사용하여 애플리케이션의 구성 요소들을 설정하고 관리합니다. 주요한 역할은 다음과 같습니다:

  1. Bean 인스턴스화: ApplicationContext는 설정된 구성 요소들을 인스턴스화하여 Bean으로 관리합니다. Bean은 Spring에서 관리되는 객체를 의미하며, 애플리케이션에서 사용되는 주요한 구성 요소입니다.
  2. 의존성 주입(Dependency Injection): ApplicationContext는 Bean들 간의 의존성을 관리하고 주입합니다. 이를 통해 객체 간의 결합도를 낮추고 유연한 애플리케이션 개발을 할 수 있습니다. 주입 방식으로는 생성자 주입, 필드 주입, Setter 주입 등이 있습니다.
  3. 라이프사이클 관리: ApplicationContext는 Bean의 라이프사이클을 관리합니다. Bean의 초기화 및 소멸과 관련된 작업을 처리할 수 있습니다. 초기화 콜백(Initialization Callback)과 소멸 콜백(Destruction Callback)을 설정하여 필요한 작업을 수행할 수 있습니다.
  4. 프로퍼티 파일 로딩: ApplicationContext는 애플리케이션에 필요한 프로퍼티 파일을 로딩하고, 필요한 곳에서 사용할 수 있도록 제공합니다. 프로퍼티 파일은 설정 정보나 환경 변수와 같은 애플리케이션 설정을 담고 있습니다.
  5. AOP (Aspect-Oriented Programming) 지원: ApplicationContext는 관점 지향 프로그래밍을 지원합니다. AOP를 사용하여 애플리케이션의 핵심 로직과 공통적인 부가기능(로깅, 트랜잭션 처리 등)을 분리하여 개발할 수 있습니다.
  6. 다국어 지원: ApplicationContext는 메시지 소스(Message Source)를 제공하여 다국어 지원을 할 수 있습니다. 메시지 소스를 사용하면 애플리케이션에서 사용되는 텍스트 메시지들을 다국어로 관리하고 동적으로 변경할 수 있습니다.

가장 중요한 Bean의 인스턴스화를 진행해준다고 합니다. 즉, Bean을 인스턴스화 시켜서 의존성을 역전시키고, 의존성 주입도 알아서 스프링이 해주게 만들려면 applicationContext를 이용해야 한다는 거네요.

 

이번엔 resource폴더에 new를 눌러 Spring configure file을 만들어주도록 합니다.

이렇게 Person이라는 클래스를 만들어 주고 이 클래스를 Bean 인스턴스화 시켜주기 위해서는

 

이런식으로 작성을 해주면 되는겁니다.

 

이제 Person 객체가 필요할 때 스프링이 알아서 인스턴스 생성을 해줄 수 있겠네요

 

이제 스프링 컨테이너를 만들어서 스프링 컨테이너에게 대신 인스턴스를 생성해달라고 해봅시다.

 

package com.duljji;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

public class Test {
	ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
	
	Person p = (Person) context.getBean("person");
	
	
}

new 를 이용하여 Person 객체를 생성하지 않고 getBean을 이용하여 스프링에게 객체 생성을 맡길 수 있음을 확인할 수 있습니다.

 

정말 객체가 만들어졌는지 확인하기 위해 Person 클래스에 메서드를 하나 정의하고 호출해보도록 합니다.

잘 호출되는 것을 확인할 수 있습니다.

 

이 스프링 컨테이너는 기본적으로 객체를 싱글톤 패턴으로 생성해줍니다.

 

즉, 

 

이 두 객체가 동일한 주소를 갖는다는 거!!

 

객체를 생성할 때마다 새로운 객체를 만들어야 한다면 scope를 따로 지정해주어야 합니다.

 

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="singletonBean" class="com.example.SingletonBean" scope="singleton" />
    <bean id="prototypeBean" class="com.example.PrototypeBean" scope="prototype" />

</beans>

Bean에 이런식으로 scope를 지정해주면

 

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        // Singleton Bean 사용
        SingletonBean singletonBean1 = context.getBean(SingletonBean.class);
        singletonBean1.setMessage("Hello, Singleton Bean!");

        SingletonBean singletonBean2 = context.getBean(SingletonBean.class);
        System.out.println(singletonBean2.getMessage()); // 출력: Hello, Singleton Bean!

        // Prototype Bean 사용
        PrototypeBean prototypeBean1 = context.getBean(PrototypeBean.class);
        prototypeBean1.setMessage("Hello, Prototype Bean!");

        PrototypeBean prototypeBean2 = context.getBean(PrototypeBean.class);
        System.out.println(prototypeBean2.getMessage()); // 출력: null
    }
}

이런식으로 다른 객체가 생성이 되는 것이죠.

 

이렇게 스프링 컨테이너를 이용하여 Bean 인스턴스를 만드는 것을 해보았습니다.

 

이번엔 이 Bean 인스턴스에 생성자로 의존성 주입을 해보도록 하겠습니다.

 

Person 객체는 아직 의존성을 갖고있는 객체가 하나도 없으니 하나 만들어주도록 합시다.

 

이렇게 Coke 클래스를 만들어주고, 

package com.duljji;

public class Person {
	
	Coke coke;
	public Person() {
		
	}
	
	public Person(Coke coke) {
		this.coke = coke;
	}
	
	public void greet() {
		System.out.println("안녕하세요!! Person입니다.");
	}
	
	
}

Person 클래스에 Coke 클래스 의존성을 주입해 주도록 합시다.

 

일단 Coke 클래스 또한 Bean으로 만들어 주고 Person클래스에 의존성 주입을 해주도록 합니다.

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean class="com.duljji.Coke" id="coke"></bean>
	<bean class="com.duljji.Person" id="person">
	<constructor-arg ref="coke"></constructor-arg>
	</bean>
</beans>

person Bean에 constructor-arg를 이용하여 coke의존성을 생성자로 주입해주었습니다.

 

이제 Person을 만들어서 drink를 실행해보도록 합시다.

 

빈으로 만들어주고 의존성 주입만 해주면 new Coke를 하지 않아도 자동으로 coke를 만들어서 person안에 넣어준 것을 확인할 수 있습니다.

 

이렇듯 스프링 컨테이너의 IoC는 개발자가 해야하는 것들을 대신 해주는 편의성을 가져다준다는 것을 알 수 있습니다.

 

많은 것들이 Bean으로 관리되고 있다면 개발자가 해야하는 일은 팍팍 줄어들겠네요.

 

이렇게 생성자로 주입하는 것 말고도 Setter 주입이라는 것이 있습니다.

 

이번엔 Setter 주입으로 한 번 만들어봅시다.

 

package com.duljji;

public class Person {
	
	Coke coke;
	public Person() {
		
	}
	
	public Person(Coke coke) {
		this.coke = coke;
	}
	
	public void greet() {
		System.out.println("안녕하세요!! Person입니다.");
	}
	
	public void setCoke(Coke coke) {
		this.coke = coke;
	}
	
	
}

Setter를 만들었습니다. 이제 이 Setter의 이름을 이용하여 의존성을 주입할 수 있습니다.

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean class="com.duljji.Coke" id="coke"></bean>
	<bean class="com.duljji.Person" id="person">
	<property name="coke" ref="coke"></property>
	</bean>
</beans>

property를 이용하여 Setter 주입을 할 수 있는데 ref는 생성자 주입을 할 때 봤다시피 bean instance의 id일텐데 name은 뭘까요??

 

name은 바로 Setter의 메소드 이름입니다.

 

Setter의 이름을 바꿔보도록 하겠습니다.

 

package com.duljji;

public class Person {
	
	Coke coke;
	public Person() {
		
	}
	
	public Person(Coke coke) {
		this.coke = coke;
	}
	
	public void greet() {
		System.out.println("안녕하세요!! Person입니다.");
	}
	
	public void setBeverage(Coke coke) {
		this.coke = coke;
	}
	
	
}

이렇게 Beverage로 Setter의 이름을 바꾸면 어떻게 될까요? 에러가 나겠죠?

name 을 beverage로 바꿔야 다시 에러가 나지 않게 됩니다.

 

이 외에도 필드 주입이라는 것이 있는데요.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean class="com.duljji.Coke" id="coke"></bean>
	<bean class="com.duljji.Person" id="person">
	</bean>
</beans>

이렇게 의존성 주입을 일단 해주지 않도록 해보겠습니다.

 

이렇게 되면 당연 Coke의존성을 주입해주지 않았으니 에러가 날것입니다.

NullPointerException이 나옵니다. 당연하죠 Coke를 안 넣어줬으니까요.

 

이 에러가 안나오게 하기 위해서 xml을 직접 수정해보시기 바랍니다.

 

지난 2023년 4월 23일 정보처리기사 실기 시험을 보고 왔습니다.

정보처리기사 블로그 카테고리도 만들어놨었는데, 이걸 작성하지 않게 된 가장 큰 이유는

 

정보처리기사 실기 시험이 코앞이고, 가장 효율적으로 공부를 해야하는데, 저렇게 정리하느라 시간을 소비하면 안되겠다는 생각 때문이었습니다.

 

결국 그 카테고리는 쓰이지 않을 카테고리가 되었네요.

 

정보처리기사를 공부하던 때에 포스팅을 하지 않은 이유로 서문을 열도록 하겠습니다.


 

1. 공부방법 - 정보처리기사 관련 공부를 블로그 포스팅 하지 않은 이유

 

다른 사람들에게 정보를 공유하고, 스스로 공부한 것들을 소화시키기 위해서 블로그에 포스팅을 하곤 하는데,

 

정보처리기사 시험 공부 또한 같은 방법으로 공부를 하기 위해서 카테고리를 만들어 놓았습니다.

 

같은 이유로 SSAFY 2학기가 시작되고 CS공부도 좀 더 중점적으로 해야하기 때문에 CS 카테고리를 만들어 소화시키는 과정이 필요할 것 같아 CS 카테고리도 만들 예정입니다. 

 

하지만, 정보처리기사 공부는 그렇게 하게 될 경우 시간이 매우 촉박해질 것이라는 생각이 들었습니다.

 

실기 공부의 양이 굉장히 많았는데, 그 많은 범위의 내용들 중 무엇이 덜 중요한지 더 중요한지 파악하기 어려웠기 때문에

 

시간을 써서 블로그에 작성하며 깊게 소화시키려다 그것이 시험에 나오지 않아 낭패를 볼 가능성도 있었기 때문입니다.

 

그래서 바로 깊게 소화시키기보다는 많이 읽어 머리에 각인시키는 식으로 다회독 공부 방법으로 방식을 전환하였습니다.


 

2. 시험의 난이도 - 분명 쉬워지고 있다!!!

 

개정이 되고 난 후 정보처리기사 시험의 난이도가 꽤 높아졌다고 알고 있습니다.

 

시험 합격률도 2021년 3회 23% , 2022년1회 25.6%, 2022년 2회는 16.1%

 

그런데 이번 2023년 1회는 문제가 매우 평이하게 나왔다는 이야기도 많았고, 심지어 수제비에서 설문조사를 통해 받은 예상 합격률이 50% 가까이 측정되기도 했었습니다.

 

특히나 2023년 1회 실기시험을 통해 약술형이 줄어들고 단답형으로만 문제들이 나오도록 바뀌었다는 점과, 프로그래밍 언어 관련 문제 비중이 압도적으로 높다. 라는 것이 확인 됐으므로,

 

무엇에 선택과 집중을 해야할지 감이 잡히지 않았던 것이, 이제는 어느정도 가닥이 보이기에 앞으로도 좀 더 체감 난이도가 낮아질 것이라고 생각합니다.

 

정말 이전 시험들에 비해 체감 난이도가 대폭 하향됐고, 2회차 실기 시험은 얼마나 어렵게 나올지 불안했을 정도였는데

 

 

정작 실제 합격률은 27%였습니다.

 

이건 정말.. 그냥 공부를 안 한 사람들이 하필이면 많이 봤다고밖에 이야기를 할 수 없을 것 같습니다.

 

분명 난이도가 평이했고, 쉽게 나온것이 맞습니다.

 

합격률이라는 평균값에 속지 않으셨으면 하는 바람입니다.

 

다들 쉬워지고있는 시험 난이도에 안심하고 프로그래밍 파트 공부를 열심히 한다면 충분히 합격할 수 있을 것이라 생각합니다.


3. 공부방법

저는 흥달쌤 교재로 공부를 했는데, 이번 시험은 흥달쌤 교재 하나로 충분하였으나, 다음 시험도 흥달쌤 교재 하나만으로 충분할까? 의문이 들긴 했습니다.

 

60점을 넘기 위해서는 충분할 수 있겠다는 생각이 들지만... 확실히 하나의 교재만 보다보면 놓치게 되는 개념들이 존재했습니다.

 

하나 하나 자세하게 외우기보다는 회독을 여러번 하면서 단어를 익숙하게 만드는 것들이 중요했고,

 

버스를 타고 SSAFY를 오고 가는 왕복 2시간의 시간 때마다 핸드폰으로 다른 블로그들에 포스팅 되어있는 내용들을 계속 회독하고,

 

집에서는 교재에 나와있는 프로그래밍 문제들을 계속 풀어보고,

 

자기 전에는 누워서 2배속으로 인강을 계속 돌려보는 방식으로 공부를 하였습니다.

 

일일이 적고, 타이핑하고, 필기하고 하면 공부양이 너무 많아 잘못 집중했다가는 시간 배분에 실패해 놓치는 부분들이 많이 생기게 되고, 하필 공부한 내용이 나오질 않아 낭패를 보게 될텐데

 

여러번 회독하게 되면 주관식이라 할지라도 문제를 보면 답이 유추되는 느낌을 갖게 됩니다.

 

이번 시험에서 운이 좋아 마침 알고 있는 것들이 나왔기에 고득점을 할 수 있었지만,

 

운이 좋지 않아 그것들이 나오지 않았다 하더라도 60점은 넘을 수 있었을거라 장담할 수 있습니다.

 

꼭 불안해서 개념 하나하나에 몰두하기보다는 전체적으로 머리에 각인시키는 것이 좀 더 효율적인 공부방법이 아닐까 생각 해봅니다.

 

 

7. add 구현하기

이제 addFirst와 addLast가 아닌 특정 인덱스에 data를 삽입하는 add를 구현해보도록 하겠습니다.

 

우리는 이제 get을 통하여 특정 인덱스의 Node에 접근을 할 수 있었습니다.

 

그럼 add를 구현하기 아주 쉬워지는데요.

 

0번째 인덱스에 데이터를 삽입하는 경우는 addFirst와 같으니 addFirst를 써주고

 

그 외의 인덱스에 추가하는 것은 해당 인덱스와 그 이전의 인덱스 사이에 노드를 넣어주는 것이기 때문에

 

그 두개를 받고 next를 이어주기만 하면 됩니다.

 

public void add(int index, int data){
            if(index==0){
                addFrist(data);
            }else{
                Node newNode = new Node(data);
                Node prev = get(index-1);
                Node next = get(index);
                prev.next = newNode;
                newNode.next = next;
                size++;
                }
}

 

이런 느낌이죠. index가 0이라면 addFirst를 해주면 되고, 그 이외에는 새로운 노드를 만들어서 데이터를 넣어준뒤

 

이전 인덱스와 해당 인덱스의 노드를 받아서 next로 이어주면 끝

 

그런데, addFirst와 addLast를 구현할 때 중요한게 head값과 tail값이었잖아요?

 

인덱스가 0일때는 addFirst를 호출하니 상관 없는데, 마침 해당 인덱스가 마지막 인덱스였다면 tail값을 수정해주는 작업이 필요합니다.

 

간단하게 newNode의 next가 null이라면 tail을 newNode로 바꿔주면 됩니다.

 

 

public void add(int index, int data){
            if(index==0){
                addFrist(data);
            }else{
                Node newNode = new Node(data);
                Node prev = get(index-1);
                Node next = get(index);
                prev.next = newNode;
                newNode.next = next;
                size++;
                if(newNode.next == null){
                    this.tail = newNode;
                }
            }

        }

 

8. removeFirst 구현하기

 

removeFirst는 매우 간단합니다.

 

LinkedList의 head값을 head.next 값으로 바꿔주기만 하면 되니까요

public int removeFirst(){
            Node node = this.head;
            this.head = head.next;
            size--;
            return node.data;
        }

매우 쉽습니다.

 

9. remove 구현하기

 

그럼 이제 특정 인덱스의 값을 remove하는 것을 알아보도록 하겠습니다.

 

add와 크게 다르지 않습니다. 만약 첫번째 인덱스를 remove한다면 removeFirst를 실행해주고,

 

그게 아니라면 해당 인덱스의 이전 인덱스 노드와 다음 인덱스 노드를 받아서 그 두개를 다시 이어주면 되니까요.

 

하지만, add를 할 때와 마찬가지로 우리가 삭제하려는 값이 마지막 인덱스라면 tail;값의 변경이 필요하게 될겁니다.

 

public int remove(int index){
            if(index==0){
                return removeFirst();
            }else{
                Node selectedNode = get(index);
                Node prev = get(index-1);
                Node nextNode = prev.next.next;
                prev.next = nextNode;
                if(selectedNode == tail){
                    this.tail = prev;
                }
                size--;
                return selectedNode.data;
            }
        }

 

10. removeLast 구현하기

 

removeLast remove에 size-1을 실행하면 됩니다.

 

SSAFY B형 특강을 듣다가 처음으로 알게 된 Linked LIst

 

파이썬으로도 구현이 가능하다고 하는데, 비전공자반 수업시간에서 다루지 않았던 자료구조이기도 하고,

 

JAVA를 다루는데 아직 익숙하지 않아서인지 간단해 보이면서도 구현하기가 쉽지 않아 정리를 해두려고 합니다.

 

1. Linked List 만들기

 

Linked List는 노드들로 이루어져있는데요

 

원소 하나하나가 노드로 이루어져 있는 리스트라고 생각하면 될 것 같습니다.

 

맨 앞과 맨 뒤의 노드는 은근히 쓰일 일이 많기 때문에 Linked List 자체적으로 가지고 있도록 만들어 놓습니다.

 

그리고 size나 empty를 쉽게 구현하기 위해 size 값도 멤버변수로 하나 넣어놓습니다.

public class TestLinkedList{

    public static class LinkedList{
        Node head;
        Node tail;
        int size;
        public LinkedList(){
            this.head = null;
            this.tail = null;
            this.size=0;
        }
    }
}

 

2. Node 만들기

 

어차피 노드들은 이 Linked List 안에서만 사용될 것이기 때문에 클래스 안에 Node 클래스를 추가로 만듭니다.

 

각 Node들은 data를 갖고, next라는 레퍼런스 변수를 이용하여 다음 노드를 참조합니다.

 

값이 없는 노드는 없으므로 노드는 노드를 생성할 때 생성자로 받아주고, next 값은 링크드리스트에 연결을 해주어야 하니 일단 null으로 설정해준 뒤 직접 이어줄겁니다.

public class TestLinkedList{

    public static class LinkedList{
        Node head;
        Node tail;
        int size;
        public LinkedList(){
            this.head = null;
            this.tail = null;
            this.size=0;
        }
        // 추가되는 부분
        public class Node{
            int data;
            Node next;
            public Node(int data){
                this.data = data;
                this.next = null;
            }
        }
        //Node 완성
    }
}

 

3. addFirst 만들기

맨 앞에 원소를 추가하는 메소드입니다.

 

원소를 추가할 때 해줘야할 건 총 3가지입니다.

 

 -  head값 바꾸기

일단 새로운 노드를 맨 앞에 위치시킬 것이기 때문에, head값을 바꿔주어야 합니다.

 

일단 새로 만든 노드에 기존의 head를 연결시켜놓고 head를 바꿔주면 됩니다.

 

- size증가시키기

당연합니다.

 

 - 기존의 head가 null인지 체크하기

 이제 새로운 노드가 head가 되었습니다. head.next를 체크해줍니다. 만약 head.next가 null이라면 기존에 아무 노드도 없었다는 뜻이 됩니다. 이제 첫번 째 노드가 생겼으니 null이었던 head와 tail값을 모두 지정해줍니다. head == tail == newNode인 상태겠죠.

 

public class TestLinkedList{

    public static class LinkedList{
        Node head;
        Node tail;
        int size;
        public LinkedList(){
            this.head = null;
            this.tail = null;
            this.size=0;
        }
        public class Node{
            int data;
            Node next;
            public Node(int data){
                this.data = data;
                this.next = null;
            }
        }

        public void addFrist(int data){
            // 노드를 생성하고
            Node newNode = new Node(data);
            // 기존에 있던 head에 연결을 해줍니다.
            newNode.next = head;
            // 그리고 생성한 노드가 이제부터 head가 됩니다.
            head = newNode;
            // 만약 현재 헤드의 next가 없다면 노드가 하나뿐이므로 tail값 역시 현재 노드가 됩니다.
            if(head.next == null){
                tail = head;
            }
            size++;
        }
    }
}

 

4. addLast 만들기

 

 이번엔 처음이 아닌 마지막에 Node를 추가합니다. 당연히 newNode가 tail이 되겠죠?

addFirst와 크게 다르지 않습니다.

public void addLast(int data){
            Node newNode = new Node(data);
            tail.next = newNode;
            tail = newNode;
            size++;
        }

간단하게 이렇게 짜면 될까요?

 

이렇게 짜면 되긴 합니다만, tail값이 null일 경우를 생각하지 않았습니다.

 

tail값이 null이라면 작동하지 않습니다. 그렇기 때문에 tail이 null일 경우, 즉 리스트가 비어있을 경우에 필요한 로직을 짜주어야 합니다.

 

그 로직은 이미 addFirst에서 우리가 만들어두었습니다.

 public void addLast(int data){
            Node newNode = new Node(data);
            if(size == 0){
                addFrist(data);
            }
            tail.next = newNode;
            tail = newNode;
            size++;
        }

 

5. toString으로 확인하기

 

 이제 로직이 잘 확인하는지 테스트하기 위해 toString 함수를 만듭니다.

head부터 next로 이동하면서 data값들을 출력해주면 될 것 같습니다.

 

toString 함수는 간단하게 이렇게 작성하면 될 것 같아서 작성을 해보았는데

 

이렇게 작성하니까 값이 비어있을 때 에러가 나더라구요.

 

size값을 넣어서 size가 0이면 [] 만 출력하게 해놓는게 더 좋을 것 같습니다.

 

public String toString(){
            Node start = this.head;
            String str = "[" + this.head.data;
            while(start.next != null){
                start = start.next;
                str += "," + start.data;
            }
            str +=  "]";
            return str;
        }
 public static void main(String[] args) {
        LinkedList lst = new LinkedList();
        lst.addFrist(1);
        lst.addFrist(2);
        lst.addLast(3);
        lst.addFrist(4);
        System.out.println(lst);
    }

결과가 제대로 출력이 되는 걸 볼 수 있습니다.

 

1 -> 2, 1 -> 2, 1, 3 -> 4, 2, 1, 3 

 

6. index로 접근하기

LinkedList는 인덱스로 바로 접근할 수가 없습니다. 그렇기 때문에 순차적으로 접근해서 값을 얻어내야하는데요

public Node get(int index){
            Node node = this.head;
            for(int i=0; i<index; i++){
                node = node.next;
            }
            return node;
        }

이렇게 하면 간단하게 인덱스에 해당하는 node를 얻을 수 있겠죠?

 

이렇게 LinkedList로 맨 앞과 맨뒤에 값을 추가하고, 인덱스로 접근하는 방법을 알아보았습니다. 

 

다음에는 중간에 삽입하는 방법과 삭제한느 방법을 익혀보도록 하겠습니다.

 

 

 

SSAFY 1학기 후기

SSAFY에서 합격 문자를 받고, 기뻐하던게 생생하게 기억이 나는데, 벌써 SSAFY 1학기 종강을 하게 되었습니다.

 

SAFFY에서 교수님들과 멘토 분들에게 받았던 꿀팁을 모두 실천하였다면, 더 놀라운 결과를 얻을 수 있었겠지만,

 

모두 다 실천하지 못했던게 아쉽습니다.

 

이 글을 보게 될 10기 혹은 미래의 SSAFFY인들은 꼭 좋은 꿀팁들 많이들 실천하셔서 저보다 더 크게 성장하실 수 있으셨으면 좋겠습니다.

 

참고로 저는 프로그래밍을 잘 알고 입과한게 아니었기 때문에, 프로그래밍을 이미 익히고 오신 분들의 후기는 또 다를 수 있으니 찾아보세요!!

 


1. SSAFY에서 얻어간 걸 말하라고 한다면 빼놓을 수 없는 알고리즘!!!

  - 싸피를 다니기 6개월 전부터 프로그래밍에 관심이 생겨 공부를 시작했었지만, 취직의 문턱은 너무나도 높았습니다.

 

포트폴리오 경험, 이력서와 자기소개서, 면접 등 저에게 부족한 것들을 나열하자면 끝도 없겠지만, 그래도 이것들은 그나마

 

6개월동안 공부를 하면서 어떻게든 해볼 수 있는 것들이었지만,

 

이것들을 시도조차 하지 못하고 고배를 마시게 했던게 바로 기업들의 코딩테스트였습니다.

 

 제어문과 반복문 그리고 출력하는 방법만 알았던 저는 그래도 나름 열심히 공부했다 생각하여 간단한 문제들은 풀 수 있지 않을까? 생각했는데,

 

정말 '간단한' 문제들만 풀 수 있었고, 코딩테스트에서 항상 탈락을 면치 못했습니다.

 

싸피에 오기 전 백준 아이디를 한 번 보면....

 

 나름 해본다고 해봤는데, 금세 포기해버렸던 결과가 보입니다.

 

틀린 문제 3003번이 얼마나 어려운 문제인지 보니...

 

 

 

 

...................

 

 

 

ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 아니 반복문 제어문 정도는 자신 있었다며....

 

아마 이 글을 보시는 분들 중에 이 문제를 풀 수 있는 분들이 대다수이지 않을까 싶을 정도로 쉬운 문제이지만 풀지 못할 정도로 정말 알고리즘에 대해 알지 못했습니다.

 

SSAFY에 들어가기 전에 뭐라도 해놓지 않으면 수업을 따라갈 수 없겠다는 판단에 이것 저것 공부를 해보았지만, 각종 유튜브 강의들은 외계어로 들릴 뿐이었고, 

 

믿을건 SSAFY 알고리즘 수업뿐이다 라는 생각에 열심히 알고리즘 강의를 듣고 그걸 바탕으로 꾸준히 문제들을 풀었습니다.

 

싸피 입과하기 전 단계별 풀어보기 문제들을 풀면서 일단 반복, 제어문만이라도 완벽하게 잡자는 마음으로 문제들을 풀어대기 시작했고,

 

알고리즘 수업이 시작 되고나서는 수업시간에 알려주신 알고리즘들을 이용한 문제들을 풀어나가기 시작했습니다.

 

 

그렇게 싸피에 들어온 후로 중간중간 비어있는 칸이 보이긴 하지만 웬만하면 하루도 빠짐없이 알고리즘 문제들을 풀어나가다 보니 실력이 점차 쌓이기 시작했습니다.

 

알고리즘 공부... 미리 하는 것도 좋지만 아무것도 모르는 상태에서는 아무리 쉽게 풀어써준 유튜브 강의도 외계어로 들리더라구요.

 

SSAFY 교육 믿고 그 과정에 맞춰서 해도 괄목상대 할 정도의 실력 향상은 충분히 되는 것 같습니다

 

그리고 SSAFY친구들과 함께했던 알고리즘 스터디, 그리고 같은 반인 친구가 만들어 놓은 백준 문제집도 아주 큰 도움이 됐어요.

 

 

태성이 짱...

 

SSAFY 교수님들의 교육 질도 물론 좋지만, 정말 같은 친구들에게 도움을 받는 것도 무시 못하는 것 같습니다.

 

SSAFY 자랑 쓰라고 하면 정말 줄줄이 써나갈 수 있는데 참... 공간이 부족합니다.

 

 


2. 온라인 라이브 강의지만, 열심히 참여하면 보상이 와르르!!!

1학기 수업 중 하루 2시간은 유튜브 라이브 강의로 진행이 되는데요,

 

라이브강의다 보니 아무래도 아무리 수업의 질이 좋다고 하더라도 오프라인 강의보다 집중이 덜 되는건 어쩔 수 없는 사실입니다.

 

하지만,  SSAFY도 그걸 모를리 없고, 적극적인 라이브채팅 참여를 돋우기 위해 열심히 수업에 참여하고 채팅을 치면 기프티콘을 아낌없이 쏴줍니다

채팅에 참여하다보면 라이브 강의를 하시는 교수님과 소통할 수 있어 온라인 강의의 단점이 어느정도 보완이 되고,

 

직접 채팅을 치면서 질문도 하다보니 기억에도 훨씬 잘 남았습니다.

 

채팅을 치며 라이브강의에 참여하는 것과 채팅을 안 치는 것의 차이를 직접 느껴보기 위해 의도적으로 채팅을 치지 않고 묵묵히 보기도 해봤는데,

 

정말 머릿속에 남는 수업 내용의 질이 크게 차이가 났습니다.

 

기프티콘을 정말 아낌없이 주는 SSAFY인데요.

 

이 글을 작성하면서 기프티콘을 받는 개수를 대략적으로 세보았는데

 

와... 받고 쓰고 하다보니 몰랐는데 세보니까 커피만 20개!!!!! 가 훌쩍 넘어가요!!!

 

2학기 때 부터는 프로젝트 기간이라 의미없겠지만, 1학기에는 정말 꼭 라이브채팅에 열심히 참여하시는 걸 추천드립니다

 


마무리 .  SSAFY를 추천할 수 밖에 없는 이유

1. SSAFY가 제공하는 고퀄리티의 수업

 

https://www.youtube.com/@IT-zk3so

 

문어박사 IT편의점

전자공학을 전공하고(전자공학 박사) 하드웨어, 펌웨어, 소프트웨어 26년차 개발자이며 대학, 대기업, 정부출연기관에서 23년차 강사인 (프로그래밍언어, 알고리즘, 프로세서, RTOS/리눅스, AI 등)

www.youtube.com

저희의 알고리즘 풀이를 담당해주셨던 문교수님이 운영하시는 유튜브 채널입니다.

 

문교수님의 유튜브 영상들도 매우 유익했지만, SSAFY 라이브강의 때의 수업은 더더욱 유익했습니다.

 

이 강의만 보셔도 정말 수업 괜찮다!! 라는 생각을 하실 수 있으실거에요

 

그나마 문교수님은 youtube채널이라도 있어서 이렇게 자랑을 할 수 있는데, 다른 교수님 강의는 참.. 자랑하고 싶은데 공유할 수도 없고...

 

진짜 알차고 이해하기 쉽지만 그러면서도 세세하게 잘 알려주시는 수업들을 진행해주십니다.

 

거기에 교수님들이 간간이 전해주시는 꿀팁들도 잘 얻어가면 정말 SSAFY 입과한 걸 후회할 일이 전혀 없으실 거라고 자신합니다.

 

2. 모두가 열심히 하는 학습 분위기

 

공부 하면서 무시못하는게 역시 학습 분위기일텐데요.

 

다들 SSAFY에 도전할 정도로 학습의 의지가 높은 분들이 입과 시험을 통과하고 면접도 통과하고 오는 것이다 보니,

 

포기하고 대충하려는 사람은 한 명도 없었습니다. 특히 저희 반은 정말 너무 열심히 하는 분위기였어서 정말 도움이 많이 됐습니다.

 

 

주말에 좀 쉬엄쉬엄 하려고 쉬다가도 평일에 다시 SSAFY 생활을 하다보면... 저도 모르게 알고리즘 문제 하나 더 풀게 됩니다...

 

그렇게 오늘도 해야지.. 그래... 해야지... 생각하며 푼 문제만 몇 개인지ㅋㅋㅋㅋ

 

3. 개인적으로 정말 유익했던 삼성 임직원 멘토링

 

SSAFY에 들어오면 삼성 임직원 분들에게 멘토링을 받을 수가 있습니다.

 

인터넷에 정보가 많다고는 하지만, 비전공자 입장에서 그 많은 정보들 중에 어떤걸 보고 배워야할지 전혀 감이 잡히지 않았거든요.

 

게다가 IT쪽은 요새 붐이고, 사람들이 많아서 그런지 조언해주는 사람들의 말이 정말 하나같이 다 달랐었습니다.

 

그 많은 정보들을 취사선택하기란 결코 쉬운것이 아니었어요.

 

아무리 정보를 찾아보고 커뮤니티를 돌아다녀봐도 제 머릿속은 딱 이 상태였습니다.

 

결국 도저히 모르겠다, 뭐라도 해야겠다 싶어서 SSAFY에 들어오기 전에 무작정 길거리에서 광고하는 IT학원에도 들어갔지만, 

 

궁금증은 전혀 해결되지 않았었습니다.

 

하지만, SSAFY에 들어와서 임직원 분들에게 멘토링을 받을 수 있는 기회가 많아, 궁금증들을 많이 해결할 수 있었는데요.

 

이런 질문을 해도 되나...? 싶은 것까지 자세하게 답변을 해주시는 멘토님들 덕분에 번아웃도 이겨내고, 갈피도 어느정도 잡고,

 

또 멘토님들의 응원을 받으면서 자신감과 의욕도 한껏 상승시킬 수 있었습니다.

 

1학기 수업을 이제 다 듣고 이제 2학기 프로젝트를 시작하게 될텐데, 어느정도 기술 스택을 쌓은 이 상태에서 남은 2학기동안 궁금증들이 많이 쌓일텐데

 

적어도 SSAFY를 하는 동안은 앞으로 생길 의문에 대한 두려움보다는 어떤 답변을 해주실까 하는 기대감이 생기게 됩니다.

 

답변을 해줄 수 있는 사람들이 상주한다는 것. 이게 정말 무시 못하거든요!!


이 외에도 SSAFY를 추천하는 이유가 정말 무궁무진합니다.

 

지칠만 하면 SSAFY데이로 리프레쉬도 되고, 다양한 이벤트에 꿀팁들까지

 

물론 블로그를 잘 운영하고 있지는 못하지만, 이 블로그를 시작하게 된 것도 SSAFY 덕분이고, 제가 언급하지 못한 추천할만한 거리들이 무궁무진합니다

 

10기 모집이 마감됐다고 들었는데, 다른 후기들도 꼭 찾아보시고 이후에라도 SSAFY 꼭 지원해보셨으면 좋겠습니다

 

+ Recent posts