【Spring Boot實戰】Spring基礎

內容為自己閱讀「Spring Boot 實戰」的理解,或許不完全正確,若有錯誤,歡迎告知並互相討論。

此系列的閱讀書籍為汪雲飛作者的Spring Boot 實戰,參考程式碼有稍作修改,若有不妥再請告知。

【Spring框架四大原則】

  1. 使用POJO進行輕量級及最小侵入式開發
  2. 透過依賴注入和基於接口編成實現鬆耦合
  3. 透過AOP和默認習慣進行聲明式編程
  4. 使用AOP和模板減少模式化代碼
簡單來說就是透過Annotation告訴Spring框架,底下的代碼是代表什麼,來減少重複的例行性程式碼

【控制反轉與依賴注入】

  • 控制反轉 Inversion of Control - IOC
    • 將控制權交到Spring的手上,讓Spring管理程式中的Bean
  • 依賴注入 Dependency Injection - DI 
    • 為實現IOC的手段之一,讓Spring能夠自動創建所需的Bean,並注入到需要這些Bean的地方

【Bean 的註解】

  1. 聲明Bean
    1. @Component:組件,沒有特殊的意義
    2. @Service:Service層使用(業務邏輯)
    3. @Repository:儲存庫,DAO層使用
    4. @Controller
這四種註解,目的都只是告訴Spring要記得管理這些類別,因此不論用哪一個,結果都只是交由Spring管理而已,只是語意上有不同而已
  1. 注入Bean
    1. @Autowired:Spring提供的註解
    2. @Inject:JSR-330提供的註解
    3. @Resource:JSR-250提供的註解
1. 上面三種註解的功能都一樣,都是讓Spring自動注入,但通常用的都是@Autowired
2. 可以用在set方法或屬性上,一般都用在屬性上
3. @Resouce能夠自訂name屬性,指定Key值(可以和實體名稱不同,不過在設定Bean時,需要給value),使用方式如下
@Component(value="ps") // 這邊給予value
public class Person{}

@Resource(name="ps") // 這邊給予name,就會去對應到value="ps"
private Person person;

@Resource(name="person") // 這邊會對應不到,注入失敗,啟動時報錯
private Person person;

【範例一】

  • Service1
@Service // 跟Spring說要管理這個class
public class ExampleService {
	public String sayHello(String word){
		return "Hello " + word +" !"; 
	}
}
  • Service2
@Service 
public class UseExampleService {
	@Autowired // 把ExampleService的實體注入到這個class
	ExampleService exampleService;
	
	public String SayHello(String word){
		return exampleService.sayHello(word);
	}
}
  • Configuration
@Configuration // 聲明為配置的class
@ComponentScan("com.myPackage") // 將「com.myPackage」這個路徑底下的Bean都註冊起來(掃瞄器的概念)
public class SpringConfig {

}
  • Context
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
	public static void main(String[] args) {
		 AnnotationConfigApplicationContext context =
	                new AnnotationConfigApplicationContext(SpringConfig.class);
		 
		 UseExampleService useExampleService = context.getBean(UseExampleService.class);
		 
		 System.out.println(UseExampleService.SayHello("world"));
		 
		 context.close();
	}
}

【範例二】

  • Configuration
@Configuration
public class JavaConfig {
	@Bean // 表示此方法會返回一個Bean,Bean的實體名稱為functionService(方法名稱)
	public FunctionService functionService(){
		return new FunctionService();
	}
	
	@Bean 
	public UseFunctionService useFunctionService(){
		UseFunctionService useFunctionService = new UseFunctionService();
		useFunctionService.setFunctionService(functionService()); // 調用functionService()方法得到Bean
		return useFunctionService;
	}

	@Bean 
	public UseFunctionService useFunctionService(FunctionService functionService){// 用參數的方式注入Bean
		UseFunctionService useFunctionService = new UseFunctionService();
		useFunctionService.setFunctionService(functionService);
		return useFunctionService;
	}
}
@Configuration表示此class是一個配置類別,裡面可能有0~多個@Bean註解
此範例中沒有使用掃瞄器,因為所有Bean都在這裡面了
和上一個範例的差別在於,並不在每個class中去設定@Service註解,而是統一在Configuration類別中去注入,並且有兩種獲得Bean的方式

【AOP】

AOP的目的在於解耦,可以讓同一組class共享相同行為,例如Log紀錄這種共同行為,便可以透過AOP來處理。

  • @Aspect:聲明此class是一個切面
  • @After、@Before、@Around:參數可建立攔截規則,也就是切點(PointCut)
  • @PointCut:定義攔截規則(可以重複使用)
  • 符合條件的被攔截處,稱為連接點(JoinPoint)
  • 攔截分為兩種
    1. 註解攔截
    2. 方法攔截

【範例】

  • 自訂Annotation(在此為@Action)
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Action {
    String name();
}
  • 註解攔截
import org.springframework.stereotype.Service;

@Service
public class DemoAnnotationService {
    @Action(name="註解攔截") // 用剛才自訂的Annotation,給予name屬性的值
    public void add(){} 
}
  • 方法攔截
import org.springframework.stereotype.Service;

@Service
public class DemoMethodService {
    // 沒有使用Annotation
	public void add(){}
}
  • Aspect
import java.lang.reflect.Method;

import org.aspectj.lang.JoinPoint;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

@Aspect // 告知Spring這是一個Aspect
@Component // 告知Spring要管理這個class
public class LogAspect {
	
	@Pointcut("@annotation(com.myPackage.Action)") // 設定前面自訂的Annotation @Action 是一個切點
	public void annotationPointCut(){}; // 透過這個方法去執行切點
	
	  @After("annotationPointCut()") // 使用@Pointcut定義的切點
	    public void after(JoinPoint joinPoint) {
	        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
	        Method method = signature.getMethod();
	        Action action = method.getAnnotation(Action.class); // 透過方法去取得action註解,以及action的name屬性
	        System.out.println("註解攔截:" + action.name()); // 此行印出「註解攔截:註解攔截」
	    }
	  
	   @Before("execution(* com.myPackage.DemoMethodService*(..))") // 使用攔截規則作為參數
	    public void before(JoinPoint joinPoint){
	        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
	        Method method = signature.getMethod();
	        System.out.println("方法攔截:"+method.getName()); // 此行印出「方法攔截:add」
	    }
}
  • Configuration
@Configuration
@ComponentScan("com.myPackage")
@EnableAspectJAutoProxy // 開啟AspectJ功能
public class AopConfig {

}
  • Context
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
	public static void main(String[] args) {
		 AnnotationConfigApplicationContext context =
	                new AnnotationConfigApplicationContext(AopConfig.class);
		 
		 DemoAnnotationService demoAnnotationService = context.getBean(DemoAnnotationService.class);
		 
		 DemoMethodService demoMethodService = context.getBean(DemoMethodService.class);
		 
		 demoAnnotationService.add();
		 
		 demoMethodService.add();
		 
		 context.close();
	}

}