쉽게 말해서 Spring이 대신 만들어주고 관리해주는 객체예요!
// 🙅♂️ 일반적인 방법 - 내가 직접 만들어야 함
public class MyService {
public static void main(String[] args) {
UserService userService = new UserService(); // 내가 직접 new
OrderService orderService = new OrderService(); // 내가 직접 new
// 각 객체들 연결도 내가 해야 함 😰
orderService.setUserService(userService);
}
}
// 🙆♂️ Spring Bean - Spring이 알아서 다 해줌!
@Service // "Spring아, 이 클래스 알아서 만들어줘!"
public class UserService {
// Spring이 알아서 만들어줌 😎
}
@Service
public class OrderService {
@Autowired // "Spring아, UserService 넣어줘!"
private UserService userService;
// Spring이 알아서 연결해줌 😍
}
new 안 써도 됨Bean의 인생을 사람으로 비유해볼게요!
👶 탄생 (생성자)
↓
📚 교육 (의존성 주입)
↓
🎓 성인식 (@PostConstruct) ← 진짜 일할 준비 완료!
↓
💼 일하는 중... (우리가 사용)
↓
👋 은퇴식 (@PreDestroy) ← 정리하고 떠날 준비
↓
💀 사망 (소멸)
@Component
public class MyBean {
// 1️⃣ 탄생 - 생성자
public MyBean() {
System.out.println("👶 MyBean 태어남!");
}
// 2️⃣ 교육 완료 후 성인식 - 진짜 일할 준비 완료!
@PostConstruct
public void 성인식() {
System.out.println("🎓 MyBean 성인됨! 이제 일할 수 있어요!");
// 진짜 중요한 초기화 작업은 여기서!
}
// 3️⃣ 일하는 중...
public void doWork() {
System.out.println("💼 MyBean 열심히 일하는 중...");
}
// 4️⃣ 은퇴식 - 정리하고 떠날 준비
@PreDestroy
public void 은퇴식() {
System.out.println("👋 MyBean 은퇴! 뒷정리 하고 갑니다~");
// 정리 작업은 여기서!
}
}
👶 MyBean 태어남!
🎓 MyBean 성인됨! 이제 일할 수 있어요!
💼 MyBean 열심히 일하는 중...
💼 MyBean 열심히 일하는 중...
👋 MyBean 은퇴! 뒷정리 하고 갑니다~
"특정 상황이 되면 자동으로 호출되는 메서드"예요!
📱 "알람 8시에 울려줘" → 8시 되면 자동으로 울림
🏠 "현관문 열리면 '어서오세요' 말해줘" → 문 열리면 자동으로 말함
🏗️ "Bean 준비 완료되면 init() 호출해줘" → @PostConstruct
🗑️ "Bean 사라지기 전에 cleanup() 호출해줘" → @PreDestroy
@Component
public class DatabaseService {
private Connection connection;
public DatabaseService() {
// 생성자에서 DB 연결? 위험해요! 😰
// 아직 설정이 안 끝났을 수도 있어요!
this.connection = DriverManager.getConnection("...");
}
}
@Component
public class DatabaseService {
private Connection connection;
public DatabaseService() {
System.out.println("DatabaseService 객체만 일단 만들었어요");
// 연결은 나중에...
}
@PostConstruct
public void connectDB() {
System.out.println("이제 모든 준비 끝! DB 연결해요!");
this.connection = DriverManager.getConnection("...");
}
@PreDestroy
public void disconnectDB() {
System.out.println("앱 종료하기 전에 DB 연결 끊을게요!");
if (connection != null) {
connection.close();
}
}
}
@Component
public class SimpleService {
@PostConstruct
public void init() {
System.out.println("초기화 완료!");
}
@PreDestroy
public void cleanup() {
System.out.println("정리 완료!");
}
}
@Component
public class InterfaceService implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("인터페이스로 초기화!");
}
@Override
public void destroy() throws Exception {
System.out.println("인터페이스로 정리!");
}
}
@Configuration
public class MyConfig {
@Bean(initMethod = "start", destroyMethod = "stop")
public ExternalLibrary externalLibrary() {
return new ExternalLibrary();
}
}
// 외부 라이브러리 (수정 불가능한 클래스)
public class ExternalLibrary {
public void start() {
System.out.println("외부 라이브러리 시작!");
}
public void stop() {
System.out.println("외부 라이브러리 종료!");
}
}
@Service
public class GameService {
private List<String> players;
private boolean gameStarted;
public GameService() {
System.out.println("🎮 게임 서비스 객체 생성!");
}
@PostConstruct
public void setupGame() {
System.out.println("🏁 게임 초기 설정 시작!");
// 게임 초기화
players = new ArrayList<>();
gameStarted = false;
// 기본 플레이어 추가
players.add("Player1");
System.out.println("✅ 게임 준비 완료! 플레이어 수: " + players.size());
}
public void addPlayer(String playerName) {
players.add(playerName);
System.out.println("👤 새 플레이어 추가: " + playerName);
}
public void startGame() {
gameStarted = true;
System.out.println("🚀 게임 시작! 총 " + players.size() + "명 참여");
}
@PreDestroy
public void endGame() {
System.out.println("🏁 게임 종료 중...");
// 게임 결과 저장
System.out.println("💾 게임 결과를 파일에 저장합니다");
// 플레이어들에게 알림
System.out.println("📧 플레이어들에게 종료 알림 발송");
// 리소스 정리
players.clear();
gameStarted = false;
System.out.println("✅ 게임 종료 완료!");
}
}
@RestController
public class GameController {
@Autowired
private GameService gameService; // Spring이 자동으로 넣어줌!
@PostMapping("/game/player")
public String addPlayer(@RequestParam String name) {
gameService.addPlayer(name);
return "플레이어 추가 완료: " + name;
}
@PostMapping("/game/start")
public String startGame() {
gameService.startGame();
return "게임 시작!";
}
}
1. Spring 시작
👶 게임 서비스 객체 생성!
🏁 게임 초기 설정 시작!
✅ 게임 준비 완료! 플레이어 수: 1
2. API 호출: POST /game/player?name=김철수
👤 새 플레이어 추가: 김철수
3. API 호출: POST /game/start
🚀 게임 시작! 총 2명 참여
4. Spring 종료
🏁 게임 종료 중...
💾 게임 결과를 파일에 저장합니다
📧 플레이어들에게 종료 알림 발송
✅ 게임 종료 완료!
@Component // 기본이 Singleton
public class SingletonBean {
private int count = 0;
public void increase() {
count++;
System.out.println("카운트: " + count + ", 객체: " + this.hashCode());
}
}
// 테스트
@Test
void singletonTest() {
SingletonBean bean1 = context.getBean(SingletonBean.class);
SingletonBean bean2 = context.getBean(SingletonBean.class);
bean1.increase(); // 카운트: 1, 객체: 12345
bean2.increase(); // 카운트: 2, 객체: 12345 (같은 객체!)
System.out.println(bean1 == bean2); // true - 똑같은 객체!
}
특징:
@Component
@Scope("prototype")
public class PrototypeBean {
private String id = UUID.randomUUID().toString();
public String getId() {
return id;
}
}
// 테스트
@Test
void prototypeTest() {
PrototypeBean bean1 = context.getBean(PrototypeBean.class);
PrototypeBean bean2 = context.getBean(PrototypeBean.class);
System.out.println(bean1.getId()); // abc-123-def
System.out.println(bean2.getId()); // xyz-456-ghi (다름!)
System.out.println(bean1 == bean2); // false - 다른 객체!
}
특징:
@Component
@Scope("request")
public class RequestBean {
private String requestId = UUID.randomUUID().toString();
@PostConstruct
public void init() {
System.out.println("Request Bean 생성: " + requestId);
}
@PreDestroy
public void destroy() {
System.out.println("Request Bean 소멸: " + requestId);
}
}
특징:
@Component
public class DatabaseManager {
private Connection connection;
private boolean connected = false;
@PostConstruct
public void connect() {
System.out.println("📡 데이터베이스 연결 중...");
try {
// 실제로는 application.yml에서 설정 읽어옴
connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mydb",
"username",
"password"
);
connected = true;
System.out.println("✅ 데이터베이스 연결 성공!");
} catch (Exception e) {
System.out.println("❌ 데이터베이스 연결 실패: " + e.getMessage());
}
}
@PreDestroy
public void disconnect() {
System.out.println("📡 데이터베이스 연결 해제 중...");
try {
if (connection != null && !connection.isClosed()) {
connection.close();
System.out.println("✅ 데이터베이스 연결 해제 완료");
}
} catch (Exception e) {
System.out.println("❌ 연결 해제 중 오류: " + e.getMessage());
}
}
public boolean isConnected() {
return connected;
}
}
@Component
public class CacheService {
private Map<String, Object> cache;
@PostConstruct
public void initCache() {
System.out.println("🗄️ 캐시 서비스 초기화 중...");
cache = new HashMap<>();
// 자주 쓰는 데이터 미리 로드
cache.put("app_name", "내 멋진 앱");
cache.put("version", "1.0.0");
System.out.println("✅ 캐시 초기화 완료! 기본 데이터 " + cache.size() + "개 로드");
}
public void put(String key, Object value) {
cache.put(key, value);
System.out.println("💾 캐시 저장: " + key);
}
public Object get(String key) {
Object value = cache.get(key);
System.out.println("📖 캐시 조회: " + key + " → " + value);
return value;
}
@PreDestroy
public void clearCache() {
System.out.println("🗑️ 캐시 정리 중...");
if (cache != null) {
System.out.println("📊 캐시 정리 전 데이터 수: " + cache.size());
cache.clear();
System.out.println("✅ 캐시 정리 완료");
}
}
}
@Component
public class EmailService {
private boolean emailServerConnected = false;
@PostConstruct
public void connectEmailServer() {
System.out.println("📧 이메일 서버 연결 중...");
// 실제로는 SMTP 서버 연결
try {
Thread.sleep(1000); // 연결 시뮬레이션
emailServerConnected = true;
System.out.println("✅ 이메일 서버 연결 완료!");
} catch (Exception e) {
System.out.println("❌ 이메일 서버 연결 실패");
}
}
public void sendEmail(String to, String subject, String content) {
if (emailServerConnected) {
System.out.println("📤 이메일 발송: " + to + " - " + subject);
} else {
System.out.println("❌ 이메일 서버 연결되지 않음");
}
}
@PreDestroy
public void disconnectEmailServer() {
System.out.println("📧 이메일 서버 연결 해제 중...");
emailServerConnected = false;
System.out.println("✅ 이메일 서버 연결 해제 완료");
}
}
// 🚫 이렇게 하지 마세요!
@Component
public class BadService {
@Autowired
private DatabaseService dbService;
public BadService() {
// 생성자에서 DB 작업? 위험해요! 😰
// 아직 dbService가 주입되지 않았을 수도 있어요!
dbService.connect(); // NullPointerException 발생 가능!
}
}
// ✅ 이렇게 해주세요!
@Component
public class GoodService {
@Autowired
private DatabaseService dbService;
public GoodService() {
System.out.println("객체만 생성했어요. 아직 일할 준비 안됨!");
}
@PostConstruct
public void init() {
System.out.println("이제 모든 준비 끝! 일할 수 있어요!");
dbService.connect(); // 안전하게 사용 가능!
}
}
// 🚫 이렇게 하면 리소스 낭비!
@Component
public class ResourceWasteService {
private FileInputStream fileStream;
@PostConstruct
public void openFile() {
try {
fileStream = new FileInputStream("large-file.txt");
} catch (Exception e) {
// 파일 열기
}
}
// @PreDestroy가 없어서 파일이 계속 열려있음! 😱
}
// ✅ 이렇게 정리해주세요!
@Component
public class CleanService {
private FileInputStream fileStream;
@PostConstruct
public void openFile() {
try {
fileStream = new FileInputStream("large-file.txt");
System.out.println("📁 파일 열었어요!");
} catch (Exception e) {
System.out.println("❌ 파일 열기 실패");
}
}
@PreDestroy
public void closeFile() {
try {
if (fileStream != null) {
fileStream.close();
System.out.println("📁 파일 닫았어요!");
}
} catch (Exception e) {
System.out.println("❌ 파일 닫기 실패");
}
}
}
// 🚫 Prototype인데 Singleton처럼 주입받기
@Service
public class BadPrototypeUser {
@Autowired
private PrototypeBean prototypeBean; // 한 번만 주입됨! 😰
public void doSomething() {
// 매번 같은 객체 사용... Prototype 의미 없음
prototypeBean.doWork();
}
}
// ✅ Prototype은 필요할 때마다 새로 받기
@Service
public class GoodPrototypeUser {
@Autowired
private ApplicationContext context;
public void doSomething() {
// 필요할 때마다 새로운 Prototype Bean 받기
PrototypeBean bean = context.getBean(PrototypeBean.class);
bean.doWork();
}
}
@Component
public class 실무Bean {
// 생성자는 간단하게
public 실무Bean() {
System.out.println("객체 생성 완료");
}
// 초기화는 여기서
@PostConstruct
public void 초기화() {
System.out.println("초기화 작업 시작");
// DB 연결, 파일 열기, 캐시 로드 등
System.out.println("초기화 완료 - 이제 일할 수 있어요!");
}
// 정리는 여기서
@PreDestroy
public void 정리() {
System.out.println("정리 작업 시작");
// DB 연결 해제, 파일 닫기, 캐시 정리 등
System.out.println("정리 완료 - 안녕히 가세요!");
}
}
@Component
public class QuizBean {
public QuizBean() {
System.out.println("A: 생성자 호출");
}
@PostConstruct
public void init() {
System.out.println("B: PostConstruct 호출");
}
public void work() {
System.out.println("C: work 메서드 호출");
}
@PreDestroy
public void destroy() {
System.out.println("D: PreDestroy 호출");
}
}
💡 정답 보기
실행 순서: A → B → C → D
핵심: 생성자 → @PostConstruct → 사용 → @PreDestroy 순서!
@Component
public class ProblemBean {
@Autowired
private DatabaseService dbService;
public ProblemBean() {
dbService.connect(); // 문제가 있나요?
}
}
💡 정답 보기
문제점: 생성자에서 의존성 사용 시도
왜 문제인가?
@Autowired가 완료되지 않았을 수 있음dbService가 null이어서 NullPointerException 발생 가능해결책:
@Component
public class FixedBean {
@Autowired
private DatabaseService dbService;
public FixedBean() {
System.out.println("객체 생성만 완료");
}
@PostConstruct
public void init() {
dbService.connect(); // 여기서 안전하게 사용!
}
}
핵심: 의존성이 필요한 작업은 @PostConstruct에서!
Spring Bean 생명주기를 이해하면 Spring의 동작 원리가 훨씬 명확해져요! 🚀