內容為自己閱讀「Spring Boot 實戰」的理解,或許不完全正確,若有錯誤,歡迎告知並互相討論。
此系列的閱讀書籍為汪雲飛作者的Spring Boot 實戰,參考程式碼有稍作修改,若有不妥再請告知。
【Spring框架四大原則】
- 使用POJO進行輕量級及最小侵入式開發
- 透過依賴注入和基於接口編成實現鬆耦合
- 透過AOP和默認習慣進行聲明式編程
- 使用AOP和模板減少模式化代碼
簡單來說就是透過Annotation告訴Spring框架,底下的代碼是代表什麼,來減少重複的例行性程式碼
【控制反轉與依賴注入】
- 控制反轉 Inversion of Control - IOC
- 將控制權交到Spring的手上,讓Spring管理程式中的Bean
- 依賴注入 Dependency Injection - DI
- 為實現IOC的手段之一,讓Spring能夠自動創建所需的Bean,並注入到需要這些Bean的地方
【Bean 的註解】
- 聲明Bean
- @Component:組件,沒有特殊的意義
- @Service:Service層使用(業務邏輯)
- @Repository:儲存庫,DAO層使用
- @Controller
這四種註解,目的都只是告訴Spring要記得管理這些類別,因此不論用哪一個,結果都只是交由Spring管理而已,只是語意上有不同而已
- 注入Bean
- @Autowired:Spring提供的註解
- @Inject:JSR-330提供的註解
- @Resource:JSR-250提供的註解
1. 上面三種註解的功能都一樣,都是讓Spring自動注入,但通常用的都是@Autowired
2. 可以用在set方法或屬性上,一般都用在屬性上
3. @Resouce能夠自訂name屬性,指定Key值(可以和實體名稱不同,不過在設定Bean時,需要給value),使用方式如下
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的方式
此範例中沒有使用掃瞄器,因為所有Bean都在這裡面了
和上一個範例的差別在於,並不在每個class中去設定@Service註解,而是統一在Configuration類別中去注入,並且有兩種獲得Bean的方式
【AOP】
AOP的目的在於解耦,可以讓同一組class共享相同行為,例如Log紀錄這種共同行為,便可以透過AOP來處理。
- @Aspect:聲明此class是一個切面
- @After、@Before、@Around:參數可建立攔截規則,也就是切點(PointCut)
- @PointCut:定義攔截規則(可以重複使用)
- 符合條件的被攔截處,稱為連接點(JoinPoint)
- 攔截分為兩種
- 註解攔截
- 方法攔截
【範例】
- 自訂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();
}
}