Java核心編程高階實戰案例:MySpring
本博文通過學習 中國大學MOOC 平臺上陳良育老師講的 Java核心技術(高階) 課程,因為老師視頻中的講解有些較為簡略,于是我自己另外搜集資料,從老師提供的代碼中剖析,再深入研究與應用,并記錄于該博文中,
本文涉及知識點:
代理,反射,泛型,注解,類加載器,IoC/DI,AOP,lamdba運算式,stream流等
一、了解Spring
Spring 框架是 Java EE 中最優秀的框架,Spring 框架的核心是基于 控制反轉(Inversion of Control, IoC) 的原理,在 Spring 中,控制反轉是 Spring 的重要組成部分,Spring 實作的核心是基于 依賴注入(dependency injection, DI) ,Spring 框架的兩個核心功能是 依賴注入 和 面向切面編程(Aspect Oriented Programing, AOP) ,
Spring Boot 專案旨在簡化使用Spring 構建應用程式的入門體驗,SpringBoot 框架中有兩個非常重要的策略:開箱即用 和 約定優于配置,從最根本上來講,Spring Boot 就是一些庫的集合,它能夠被任意專案的構建系統所使用,它提供了配置好的 starter 依賴來簡化構建配置,簡便起見,該框架也提供了命令列界面,它可以用來運行和測驗Boot應用,
1.1 控制反轉和依賴注入
控制反轉的核心是依賴注入,旨在提供一種更簡單的機制來設定組件依賴項(通常稱為物件的協作者),并在整個生命周期中管理這些依賴項,IoC可以分解為兩種子型別:依賴注入和依賴查找,因此,依賴注入是IoC的一種特殊形式,一般情況下,這兩個個術語可以互換使用,需要依賴項的組件通常被稱為依賴物件,或者在IoC的情況下被稱為目標物件,
1.2 AOP相關概念
與大多數技術一樣,AOP 帶有自己特定的一組概念術語,了解它們的含義非常重要,
- 連接點(JoinPoint):連接點是應用程式執行期間明確定義的一個點,連接點是AOP的核心概念,并且定義了在應用程式中可以使用 AOP 插入其他邏輯的點,連接點可以使用在方法呼叫、方法呼叫本身、類初始化和物件實體化等,
- 通知(Advice):在特定連接點執行的代碼就是通知,它是由類中的方法定義的,有許多型別的通知,比如前置通知(在連接點之前執行)和后置通知(在連接點之后執行),
- 切入點(Pointcut):切入點是用于定義何時執行通知的連接點集合,通過創建切入點,可以更細致地控制如何將通知應用于應用程式中的組件,
- 切面(Aspect):切面是封裝在類中的的通知和切入點的組合,這種組合定義了應該包括在應用程式中的邏輯以及應該執行的位置,
- 織入(Weaving):織入是在適當的位置將切面插入到應用程式代碼中的程序,對于編譯時 AOP 解決方案,織入程序通常在生成程序時完成,同樣,對于運行時AOP解決方案,織入程序在運行時動態執行,此外,AspectJ 還支持另外一種稱為加載時織入(LTW)的織入機制,在該機制中,攔截底層的 JVM 類加載器,并在類加載器加載位元組碼時向其提供織入功能,
- 目標物件(Target):執行流由 AOP 行程修改的物件被稱為目標物件,
- 引入(Introduction):這是通過引入其他方法或欄位來修改物件結果的程序,可以通過引入AOP來使任何物件來實作特定的介面,而無需物件類顯式地實作該介面,
Spring AOP 中最明顯的簡化是只支持一種連接點型別:方法呼叫,方法呼叫連接點是迄今為止最有用的連接點,使用它可以實作AOP在日常編程中許多有用的任務,如果需要使用除方法呼叫之外的連接點通知一些代碼,那么可以一起使用Spring和AspectJ,
1.3 代理
Spring AOP 的核心架構基于代理,Spring有兩個代理實作:JDK 動態代理和 CGLIB 代理,默認情況下,當被通知的目標物件實作一個介面時,Spring 將使用 JDK 動態代理來創建目標的代理實體,但是,當被通知目標物件沒有實作介面(例如,它是一個具體的類)時,將使用 CGLIB 來創建代理實體,一個主要的原因是 JDK 動態代理僅支持介面代理,
Java 中的代理包括靜態代理和動態代理,靜態代理的特點是代理者和被代理者的關系在編譯期間就已經確認了,即代理類在程式運行的時候就存在;而動態代理的特點是代理者和被代理者的關系要在程式運行期間才能確認,即代理類在程式運行前是不存在的,代理類是在程式運行時根據代碼的“指示”動態生成的,動態代理比靜態代理的優勢在于,動態代理可以很方便的對代理類的函式進行統一的處理(invoke),而不是修改每個代理類的函式,更靈活和擴展,
JDK 的動態代理以實作介面的方式,利用 java 的反射機制來實作的,JAVA反射機制是動態獲取資訊以及動態呼叫物件方法的功能,在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個物件,都能夠呼叫它的任意方法和屬性,在Java的動態代理中,有兩個重要的類或介面,一個是InvocationHandler 介面,另一個是Proxy類,InvocationHandler 介面是給動態代理類實作的,負責處理被代理物件的操作,Proxy 類是用來創建動態代理類實體物件的,只有得到這個物件,才能呼叫需要代理的方法,動態代理的代理類是在靜態代理類上進行修改,將動態代理類實作 InvocationHandler 介面,重寫 Invoke 方法,Invoke 方法通過傳入的被代理類方法和引數來執行,
CGLIB 代理以繼承類的方式,利用 FastClass 機制來實作的,在 CGLIB 代理機制中,CGLIB 會為每個代理動態生成新類的位元組碼,并盡可能重用已經生成的類,因為 CGLIB 是繼承機制,所以CGLIB無法代理被final修飾的方法,
二、構建 Spring 的示例應用程式
從官方檔案中學習SpringBoot:https://spring.io/projects/spring-boot#learn
首先引入 Spring 的依賴,如果沒有依賴項管理工具,那么應用程式中選擇所需使用的模塊就會比較麻煩,這里使用 Maven 來管理 Java 應用程式依賴項, 這里參考的依賴十分簡單,就只有一個 Spring Boot 的 starter,為了更容易地管理依賴版本和使用默認配置,框架提供了一個parent,工程可以繼承它,
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.1</version>
<relativePath/> <-!-- lookup parent from repository ->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
前面引入的依賴為程式提供了基礎,接下來為專案增加可執行代碼,撰寫一個引導類源檔案,編譯之后就可以運行專案了,這里寫個最簡單的應用程式,
DemoApplication.java
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
System.out.println("Hello Spring!");
}
}
@SpringBootApplication 被用于激活 @EnableAutoConfiguration、@ComponentScan 和 @Configuration 三個注解的特性,其中@EnableAutoConfiguration 負責激活 SpringBoot 自動裝配機制, @ComponentScan 激活 @Component 的掃描 ,@Configuration 宣告被標注為配置類,@SpringBootApplication 注解等同于這三個注解,
啟動運行后顯示 Spring 的相關資訊,并且列印出Hello Spring!,
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.1)
2020-12-23 15:35:15.786 INFO 6600 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication using Java 1.8.0_201 on LAPTOP-Ting with PID 6600 (D:\IdeaProjects\demo\target\classes started by Oseting in D:\IdeaProjects\demo)
2020-12-23 15:35:15.789 INFO 6600 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default
2020-12-23 15:35:16.518 INFO 6600 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 1.339 seconds (JVM running for 2.003)
Hello Spring!
如果是使用控制臺命令的話,那么實作 CommandLineRunner 這個類,并重寫run方法即可,運行結果會跟上面輸出的資訊一樣,
DemoApplication.java
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
System.out.println("Hello Spring!");
}
}
注意,在啟動類中撰寫一些代碼可以實作一些任務,但是如果涉及到依賴一些 Spring 管理的目標物件,那么在 main 函式里面就不合適了,因為 main 函式是靜態方法,只能呼叫靜態成員變數,而通過依賴注入創建的物件顯然不是靜態的,如果在 main 函式里進行依賴注入,那么將會出現編譯錯誤,當我們需要在啟動類中依賴注入怎么辦?有個解決辦法是繼承 CommandLineRunner 類,重寫其中的run方法,引導類啟動時呼叫run方法執行,程式中需要依賴注入的目標物件就可以放在這個地方執行任務,
這里寫個Hello World 應用程式,
HelloWord.java
@Component
public class HelloWord {
public void sayHi() {
System.out.println("Hello World!");
}
}
DemoApplication.java
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
@Autowired
HelloWord helloWord;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
System.out.println("Hello Spring!");
}
@Override
public void run(String... args) throws Exception {
helloWord.sayHi();
}
}
啟動后輸出
/* 省略 Spring 啟動資訊 */
Hello World!
Hello Spring!
這里要注意run方法是在啟動時執行的,所以會先列印出Hello World!,啟動完成之后才繼續執行列印Hello Spring!,
三、撰寫代碼實作MySpring
這節來演示自己動手寫代碼實作簡單的 Spring 框架,實作程序主要模仿 Spring Boot 的結構,暫且將這個自定義框架命名為 MySpring 吧!
從整體上看,MySpring 框架要做到以下幾點:
- 使用注解標注需要創建的Bean
- 構建Bean容器
- 對成員變數初始化,實作IoC/DI
- 使用代理,解決方法的Aspect注解呼叫
為了讓框架支持命令列執行,并且讓啟動類支持使用依賴注入的物件,這里需要創建一個 CommandLineRunner 介面,定義一個 run 函式,引導類將在啟動的時候執行 run 函式,引導類實作該介面,重寫 run 函式,就可以自定義所需執行的任務,
CommandLineRunner.java
public interface CommandLineRunner {
void run();
}
然后創建 MySpring 的引導類,引導類將會做引導一系列作業,其核心就是實作IoC和AOP,包括創建并管理Bean物件、通過代理實作AOP, 這里引導需要做以下任務:
- 創建 Bean 物件(createBeans());
- 決議所有 Bean 上的通知(aop());
- 給所有的帶有
@Autowire注解的成員變數賦值(di()) ; - 查找 Bean 里面所有的方法,如果有
@PostConstruct注解,則執行該方法,相當于是給系統初始化的時候默認執行一些方法(post()); - 如果重寫了 CommandLineRunner 類里面的 run 函式,那么就執行 run 函式,
MySpringApplication.java
public class MySpringApplication {
private CommandLineRunner runner;
public static void run(Class main) {
MySpringApplication app = new MySpringApplication();
app.createBeans(main);
app.aop();
app.di();
app.post();
System.out.println("My Spring init successfully............");
if (app.runner != null) {
app.runner.run();
}
}
}
3.1 創建Bean
根據控制反轉的原理,Bean 的整個生命周期由框架管理,在創建 Bean 物件之前,需要知道哪些 Bean 將會被框架管理,利用注解標識哪些類需要產出物件,這里先寫好注解,標注哪些類要注入到容器中進行管理的,模仿 Spring 框架,這里同樣創建一個 @Component 的注解,注解的內容為空,因為目前該注解僅起到標識作用,
Component.java
@Retention(RetentionPolicy.RUNTIME)
public @interface Component { }
引導類中的 main 是整個程式的入口,框架根據 main 類,去加載相應的 Bean,從引導類中獲取存放加載的目錄佇列,遍歷所有目錄下所有子目錄里面所有非介面的位元組碼檔案(*.class),通過Stream回傳這些類檔案,
MySpringApplication.java
private Stream<Class> loadClasses(Class main) throws MalformedURLException, ClassNotFoundException {
URL resource = main.getResource("");
File baseDir = new File(resource.getFile());
Queue<File> dirs = new LinkedList<>();
dirs.add(baseDir);
int offset = main.getResource("/").getPath().length();
Stream.Builder<Class> classesBuilder = Stream.builder();
while (!dirs.isEmpty()) {
File tmp = dirs.poll();
for (File f : tmp.listFiles()) {
if (f.isDirectory()) {
dirs.add(f);
} else {
if (f.getName().endsWith(".class")) {
String clsName = f.toURI().toURL().getPath().substring(offset).replaceAll("/", ".").replace(".class", "");
if (!clsName.contains("cn.edu.zhku.java.core.advanced.mooc14.annotations")) {
Class cls = main.getClassLoader().loadClass(clsName);
log("load class: " + cls.getName());
classesBuilder.accept(cls);
}
}
}
}
}
return classesBuilder.build();
}
從剛才的 loadClasses 函式獲取所有非介面的類, 并進行過濾, 只留下帶有 Component 的物件,利用的反射的方法生產該 Bean 物件,這里暫時忽略例外處理,將處理之后獲得的所有物件保存在類的 List 集合變數 beans,
MySpringApplication::createBeans
beans = loadClasses(main).filter(cls ->
Arrays.stream(cls.getAnnotations()).anyMatch(a -> a instanceof Component)
).map(cls -> {
return cls.getConstructor().newInstance();
}).collect(Collectors.toList());
留下第一個既有 @Component 注解的, 又有 @CommandLineRunner 介面的物件, 作為 runner(也是類里面的 CommandLineRunner 物件),這里的處理是為了之后能在引導類執行的任務流中自定義任務用的,
MySpringApplication::createBeans
runner = (CommandLineRunner) beans.stream().filter(bean ->
Arrays.stream(bean.getClass().getInterfaces()).anyMatch(i -> i.equals(CommandLineRunner.class))
).findFirst().orElse(null);
為了簡單起見,MySpring僅獲取第一個第一個既有
@Component注解的, 又有@CommandLineRunner介面的物件,在Spring中,當服務中有多個CommandLineRunner物件時,默認情況下是按照自然順序執行的,可以通過@Order指定執行順序,@Component @Order(value = 1) public class StartRunnerOne implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("第一個服務啟動,開始執行加載資料等操作,"); } } @Component @Order(value = 2) public class StartupRunnerTwo implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("第二個服務啟動,開始執行加載資料等操作,"); } }
整個 createBeans 函式
MySpringApplication.java
private List<Object> beans;
private CommandLineRunner runner;
private void createBeans(Class main) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, MalformedURLException, ClassNotFoundException {
beans = loadClasses(main).filter(cls ->
Arrays.stream(cls.getAnnotations()).anyMatch(a -> a instanceof Component)
).map(cls -> {
try {
return cls.getConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
throw new IllegalStateException(e);
}
}).collect(Collectors.toList());
runner = (CommandLineRunner) beans.stream().filter(bean ->
Arrays.stream(bean.getClass().getInterfaces()).anyMatch(i -> i.equals(CommandLineRunner.class))
).findFirst().orElse(null);
}
3.2 實作AOP
根據 AOP 的概念,創建一些 AOP 注解來對 MySpring 管理的 Bean 進行標識,簡單起見,這里需要創建的注解僅有通知(前置通知Before和后置通知After)和切面(Aspect),同樣的,MySpring 的注解名字盡量與 Spring 的一致,
創建通知注解Before 和 After,這兩個注解可以分別在創建 Bean 的前后執行任務,
Before.java
@Retention(RetentionPolicy.RUNTIME)
public @interface Before {
String value();
}
After.java
@Retention(RetentionPolicy.RUNTIME)
public @interface After {
String value();
}
創建切面 Aspect 注解,用來表示哪些類屬于切面類,切面類在類中封裝了一些通知和切入點的組合,
Aspect.java
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect { }
創建一個 AdviceEnum 列舉類,表示 AOP 中通知所包含的型別,目前 MySpring 只有兩種通知:前置通知(before)和后置通知(after),
AdviceEnum.java
public enum AdviceEnum {
BEFORE, AFTER
}
定義一個 AOP 的物體類,用來描述切面的資訊,包括連接點(目標物件類與類名,目標物件方法與方法名)和通知(前置通知和后置通知),
MyAop.java
public class MyAop {
private final String target;
private final String targetMethod;
private final Object aspect;
private final AdviceEnum advice;
private final Method method;
public MyAop(String target, String targetMethod, Object aspect, AdviceEnum advice, Method method) {
super();
this.target = target;
this.targetMethod = targetMethod;
this.aspect = aspect;
this.advice = advice;
this.method = method;
}
public String getTarget() {
return target;
}
public String getTargetMethod() {
return targetMethod;
}
public Object getAspect() {
return aspect;
}
public AdviceEnum getAdvice() {
return advice;
}
public Method getMethod() {
return method;
}
}
織入由 JDK 動態代理技術來實作,
MySpringApplication.java
private void runAop(List<MyAop> aops, AdviceEnum advice, Method m, Object... args) {
if (aops != null) {
aops.stream().filter(a -> a.getAdvice() == advice).forEach(aop -> {
try {
aop.getMethod().invoke(aop.getAspect(), m, args);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new IllegalStateException(e);
}
});
}
}
遍歷所有 MySpring 管理的 Bean,檢查是否有 @Aspect 注解,如果有,則該 Bean 是個切面的類,那么遍歷該 Bean 的所有方法,決議 Bean 上的通知,將每個通知用 MyAOP 類封裝資訊并生成一個物件(將注解中目標物件的類名和方法名分開),
MySpringApplication::aop
Stream.Builder<MyAop> myAopsBuilder = Stream.builder();
for (Object bean: beans) {
boolean isAspect = Arrays.stream(bean.getClass().getAnnotations()).anyMatch(a -> (a instanceof Aspect));
if (isAspect) {
for (Method m: bean.getClass().getMethods()) {
for (Annotation a: m.getAnnotations()) {
String pointCut = null;
AdviceEnum advice = null;
if (a instanceof Before) {
pointCut = ((Before)a).value();
advice = AdviceEnum.BEFORE;
}
if (a instanceof After) {
pointCut = ((After)a).value();
advice = AdviceEnum.AFTER;
}
if (pointCut != null) {
int sep = pointCut.lastIndexOf(".");
String targetClass = pointCut.substring(0, sep);
String targetMethod = pointCut.substring(sep + 1);
myAopsBuilder.accept(new MyAop(targetClass, targetMethod, bean, advice, m));
}
}
}
}
}
通過 JDK 的動態代理(依賴于介面)來把 MySpring 管理的 Bean 織入切面,
MySpringApplication::aop
private Object[] proxyBeans;
Map<String, Map<String, List<MyAop>>> clsMethodAopMapping = myAopsBuilder.build().collect(Collectors.groupingBy(MyAop::getTarget,
Collectors.groupingBy(MyAop::getTargetMethod)));
proxyBeans = new Object[beans.size()];
for (int i = 0; i < beans.size(); i++) {
Object bean = beans.get(i);
String clsName = bean.getClass().getName();
if (clsMethodAopMapping.containsKey(clsName)) {
Class[] interfaces = bean.getClass().getInterfaces();
if (interfaces.length > 0) {
Object proxyInstance = Proxy.newProxyInstance(bean.getClass().getClassLoader(),
interfaces, (proxy, method, args) -> {
String methodName = method.getName();
List<MyAop> aops = clsMethodAopMapping.get(clsName).get(methodName);
runAop(aops, AdviceEnum.BEFORE, method, args);
Object res = method.invoke(bean, args);
runAop(aops, AdviceEnum.AFTER, method, args);
return res;
});
proxyBeans[i] = proxyInstance;
}
}
}
3.3 依賴注入
完成 Bean 的創建和切面的處理之后,需要將代理的 Bean 物件注入到另外需要依賴的 Bean 中,
先創建依賴注入的注解,用于標識哪些類需要注入物件,同樣的,這里只有標識作用,所以這里也是空內容,
Autowired.java
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired { }
對 Bean 賦值,根據不同 Bean 型別做不同的處理,如果是介面,就根據Bean的父類介面判定來賦予物件,
MySpringApplication.java
private Object getRequiredInterfaceBean(Class cls) {
for (int i = 0; i < beans.size(); i++) {
Object bean = beans.get(i);
for (Class beanI: bean.getClass().getInterfaces()) {
if (beanI.equals(cls)) {
return proxyBeans[i] != null ? proxyBeans[i] : bean;
}
}
}
return null;
}
如果不是介面,則直接賦予物件,
MySpringApplication.java
private Object getRequiredBean(Class cls) {
return beans.stream().filter(bean -> bean.getClass().equals(cls)).findFirst().orElse(null);
}
遍歷所有的 Bean,獲取每個 Bean 的成員變數,如果成員變數是帶@Autowired 注解,則給所有的 @Autowire 成員變數賦值,如果獲取物件成功,就給成員變數賦值,
MySpringApplication.java
private void di() throws IllegalArgumentException, IllegalAccessException {
for(Object bean: beans) {
for (Field f: bean.getClass().getDeclaredFields()) {
if (Arrays.stream(f.getAnnotations()).anyMatch(a -> (a instanceof Autowired))) {
Class fCls = f.getType();
Object requiredBean;
if (fCls.isInterface()) {
requiredBean = getRequiredInterfaceBean(fCls);
} else {
requiredBean = getRequiredBean(fCls);
}
if (requiredBean != null) {
f.setAccessible(true);
f.set(bean, requiredBean);
}
}
}
}
}
3.4 執行默認方法
在完成依賴注入之后,代理產生的 Bean 物件就基本可以投入使用了,在使用的時候 Bean 的整個生命周期仍然歸屬 MySpring 管理,在使用的之前執行一些默認方法,也可以自己再添加一些自定義方法,這里通過 @PostConstruct 注解來標識使用之前執行的方法,
PostConstruct.java
@Retention(RetentionPolicy.RUNTIME)
public @interface PostConstruct { }
MySpringApplication.java
private void post() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
log("Start Post Processes:");
for (Object bean: beans) {
for (Method m: bean.getClass().getMethods()) {
if (Arrays.stream(m.getAnnotations()).anyMatch(a -> (a instanceof PostConstruct))) {
m.setAccessible(true);
m.invoke(bean);
}
}
}
}
至此,MySpring 框架的核心搭建好了,
3.5 測驗MySpring
框架構建完成之后,使用一個簡單的例子來測驗框架是否能夠運行并且與預期相符,
創建一個簡單的類,并通過 hello 函式列印出Hello World!資訊,使用 @Component 注解標識當前類物件的生命周期是由 MySpring 容器管理,
HelloWorld.java
@Component
public class HelloWorld {
public String hello() {
return "Hello world!";
}
}
再創建一個介面,里面包含一個簡單的問候的方法,
GreetingService.java
public interface GreetingService {
void greet();
}
創建一個實作類并添加 @Component 注解,重寫 greet 方法,使用依賴注入的注解 @Autowired,標識當前成員變數由容器自動裝配合適物件,并且添加一個使用 @PostConstruct 來標識方法,使物件在生產之后,使用之前執行一些特定的任務,
GreetingServiceImpl.java
@Component
public class GreetingServiceImpl implements GreetingService {
@Autowired
HelloWorld helloWorld;
@PostConstruct
public void post() {
System.out.println("Greeting Service Impl is ready: " + helloWorld.hello());
}
@Override
public void greet() {
System.out.println("Hello CSDN!");
}
}
創建一個切面,使用 @Aspect 注解標識該類為切面,并且由MySpring管理,將類里面的連接點和通知按需求織入Bean中,
GreetingServiceAspect.java
@Aspect
@Component
public class GreetingServiceAspect {
@Before(value = "cn.edu.zhku.java.core.advanced.mooc14.di.GreetingServiceImpl.greet")
public void beforeAdvice(Method method, Object... args) {
System.out.println("Before method:" + method);
}
@After(value = "cn.edu.zhku.java.core.advanced.mooc14.di.GreetingServiceImpl.greet")
public void afterAdvice(Method method, Object... args) {
System.out.println("After method:" + method);
}
}
創建引導類,引導類也是一個 Bean,同樣歸屬框架管理,這里實作 MySpring 框架的 CommandLineRunner 類并重寫 run 方法來類中使用依賴注入的物件,
MySpring.java
@Component
public class MySpring implements CommandLineRunner {
@Autowired
private GreetingService greetingService;
public static void main(String[] args) {
MySpringApplication.ENABLE_LOG = false;
MySpringApplication.run(MySpring.class);
}
@Override
public void run() {
System.out.println("Now the application is running");
System.out.println("This is my spring");
greetingService.greet();
}
}
編譯并運行,輸出資訊
Greeting Service Impl is ready: Hello world!
Now the application is running.
This is my spring.
Before method:public abstract void cn.edu.zhku.java.core.advanced.mooc14.di.GreetingService.greet()
Hello CSDN!
After method:public abstract void cn.edu.zhku.java.core.advanced.mooc14.di.GreetingService.greet()
程式在創建完Bean之后進行了AOP處理并進行依賴注入,投入使用后,從列印資訊中可以看出執行任務的順序與與預期的相符,
小結
這個 MySpring 案例使用了反射、泛型、代理、注解、類加載器等技術,實作了一個高仿Spring的專案,其中最重要的就是注解,有了這些注解,就可以在程式啟動之前利用反射加載這些注解,對程式做很多定制化,反射和注解是框架軟體里面實作的一個重要基礎,MySpring是使用標準的Java代理來做的AOP,那么這些 AOP 的類就要求都需要實作一個介面,對于一些非介面的類,就需要做位元組碼的絞入,這個在上面的代碼并沒有體現,而在標準 Spring 框架中是使用 CGLIB 代理的,
源代碼
地址:MySpring案例
參考資料
[1] 中國大學mooc. Java核心技術(高階). 陳良育
https://www.icourse163.org/learn/ECNU-1206500807#/learn/announce
[2] Spring. https://spring.io
[3] 《Spring 5 高級編程》(第5版). Iuliana Cosmina. Rob Harrop. Chris Schaefer. Clarence Ho. 著;王凈 譯.
[4] 《Spring Boot 編程思想》(核心篇). 小馬哥(mercyblitz)著.
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/240603.html
標籤:其他
上一篇:【ArcGIS風暴】ArcGIS tif轉jpg:JPEG壓縮僅支持8位或16位無符號資料(具有一個或三個波段,且沒有色彩映射表)解決方案!
下一篇:ABP后臺作業詳細設計簡單分析
