手寫springIOC、AOP
- 一、核心思想
- 1、IoC
- 1.1 什么是IoC?
- 1.2 IoC解決了什么問題
- 1.3 IoC和DI的區別
- 2、AOP
- 2.1 什么是AOP?
- 2.2 AOP解決的什么問題
- 2.3 為什么叫面向切面編程
- 二、手寫實作IOC(包括mvc相關的邏輯)
- 1、基本思路
- 2、目錄結構
- 3、代碼
- 3.1 pom
- 3.2 application.properties
- 3.3 web.xml
- 3.4 SZDispatcherServlet.java
- 3.5 SZAutowired、SZControllerSZController、SZRequestMapping、SZService
- 3.6 IDemoService.java、DemoServiceImpl.java、DemoController.java
- 4、結果
- 5、springICO回圈依賴問題
- 5.1什么是回圈依賴
- 5.2回圈依賴處理機制
- 三、手寫實作AOP
- 1、基本思路
- 2、目錄結構
- 3、代碼
- 3.1 SZAopConfig.java、TestAspect.java、SZAdivce.java
- 3.2 SZAdvisedSupport.java
- 3.3 SZJdkDynamicAopProxy.java
- 4、結果
- 四、本文原始碼
思想部分抄自:https://blog.csdn.net/riemann_/article/details/106503900
一、核心思想
IoC 和 AOP 不是 spring 提出來的,在 spring 之前就已經存在,只不過更偏向理論化, spring 在技術層面把這兩個思想做了非常好的實作,在手寫 spring 中的 IoC 和 AOP 之前,我們先來了解 IoC 和 AOP 的思想,
1、IoC
1.1 什么是IoC?
IoC Inversion of Control (控制反轉、反轉控制),注意它是一個技術思想,不是技術實作,
描述的事情: Java 開發領域物件的創建、管理的問題,
傳統開發模式:比如類A依賴類B,往往會在類A中 new 一個B的物件,
IoC 思想下開發模式:我們不再自己去 new 物件了,而是由 IoC 容器( Spring 框架)去幫助我們實體化物件并且管理它,我們需要哪個物件,去問IoC容器要即可,
我們喪失了一個權力(創建、管理物件的權力),得到一個福利(不用考慮物件的創建、管理等一系列事情),
為什么叫控制反轉?
控制:指的是物件創建(實體化、管理)的權力,
反轉:控制權交給外部環境了( Spring 框架、 IoC容器),

1.2 IoC解決了什么問題
IoC解決物件之間的耦合問題

1.3 IoC和DI的區別
DI:Dependency Injection(依賴注入)
如何理解:
IoC和DI描述的是同一件事情,只不過角度不一樣罷了,

2、AOP
2.1 什么是AOP?
AOP:Aspect Oriented Programming 面向切面編程/面向方面編程
AOP是OOP的延續,我們從OOP說起
OOP三大特征:封裝、繼承、多型
OOP是一種垂直繼承體系:

OOP編程思想可以解決大多數的代碼重復問題,但是有一些情況是處理不了的,比如下面的在頂級父類Animal中的多個方法中相同位置出現了重復代碼,OOP就解決不了了,

橫切邏輯代碼:

橫切邏輯代碼存在什么問題:
- 橫切代碼重復問題
- 橫切邏輯代碼和業務代碼混雜在一起,代碼臃腫,維護不方便,
AOP出場,AOP獨辟蹊徑提出橫向抽取機制,將橫切邏輯代碼和業務邏輯代碼分離:

代碼拆分容易,那么如何在不改變原有業務邏輯的情況下,悄無聲息的把橫切邏輯代碼應用到原有的業 務邏輯中,達到和原來一樣的效果,這個是比較難的,
2.2 AOP解決的什么問題
在不改變原有業務邏輯情況下,增強橫切邏輯代碼,根本上解耦合,避免橫切邏輯代碼重復,
2.3 為什么叫面向切面編程
「切」 : 指的是橫切邏輯,原有業務邏輯代碼我們不能動,只能操作橫切邏輯代碼,所以面向橫切邏輯,
「面」 : 橫切邏輯代碼往往要影響的是很多個方法,每一個方法都如同一個點,多個點構成面,有一個 面的概念在里面,
二、手寫實作IOC(包括mvc相關的邏輯)
手寫ioc和aop只是簡單的實作了其功能,和spring對應的功能還是有很大區別的,主要就是為了感受其思想!!
1、基本思路

2、目錄結構

3、代碼
3.1 pom
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
</dependencies>
3.2 application.properties
#用于掃描生成bean
scanPackage=com.sz.handwrittenspring
#以下為aop相關配置
#切面運算式(即切點)
pointCut=public .* com.sz.handwrittenspring.service..*ServiceImpl..*(.*)
#切面類
aspectClass=com.sz.handwrittenspring.aop.aspect.TestAspect
#切面前置通知
aspectBefore=before
#切面后置通知
aspectAfter=after
#切面例外通知
aspectAfterThrow=afterThrowing
#切面例外型別
aspectAfterThrowingName=java.lang.Exception
3.3 web.xml
<servlet>
<servlet-name>szmvc</servlet-name>
<servlet-class>com.sz.handwrittenspring.servlet.SZDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>application.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>szmvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
3.4 SZDispatcherServlet.java
啟動時加載init方法,方法內大致步驟:
1.加載組態檔
2.掃描相關的類
3.實體化相關的類,并且快取到ico容器
4.完成依賴注入
5.初始化HandlerMapping
package com.sz.handwrittenspring.servlet;
import com.sz.handwrittenspring.annotation.SZAutowired;
import com.sz.handwrittenspring.annotation.SZController;
import com.sz.handwrittenspring.annotation.SZRequestMapping;
import com.sz.handwrittenspring.annotation.SZService;
import com.sz.handwrittenspring.aop.config.SZAopConfig;
import com.sz.handwrittenspring.aop.support.SZAdvisedSupport;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
public class SZDispatcherServlet extends HttpServlet {
//ioc容器,存放實體化的物件
private Map<String, Object> ioc = new HashMap<String, Object>();
//獲取組態檔內容
private Properties contextConfig = new Properties();
//存放掃描到的檔案路徑
private List<String> classNames = new ArrayList<>();
//存放http請求與方法的對應關系
private Map<String, Method> handlerMapping = new HashMap<>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//6.完成url的調度
try {
doDispatch(req, resp);
} catch (Exception e) {
e.printStackTrace();
resp.getWriter().write("500 Exception Detail :" + Arrays.toString(e.getStackTrace()));
}
}
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replaceAll(contextPath, "").replaceAll("/+", "/");
if (!this.handlerMapping.containsKey(url)) {
resp.getWriter().write("404 Not Found!");
return;
}
Method method = this.handlerMapping.get(url);
Map<String, String[]> parameterMap = req.getParameterMap();
//硬編碼,感受下思想就行(對應SZDispatcherServlet類中的query方法)
String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
method.invoke(ioc.get(beanName), new Object[]{req, resp, parameterMap.get("name")[0]});
}
@Override
public void init(ServletConfig config) throws ServletException {
//1.加載組態檔
doLoadConfig(config.getInitParameter("contextConfigLocation"));
//2.掃描相關的類
doScanner(contextConfig.getProperty("scanPackage"));
//3.實體化相關的類,并且快取到ico容器
doInstance();
//4.完成依賴注入
doAutowired();
//5.初始化HandlerMapping
doInitHandlerMapping();
System.out.println("SZ spring framework Initialization is complete");
}
private void doInitHandlerMapping() {
if (ioc.isEmpty()) return;
try {
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
Class<?> clazz = entry.getValue().getClass();
if (!clazz.isAnnotationPresent(SZController.class)) continue;
String baseUrl = "";
if (clazz.isAnnotationPresent(SZRequestMapping.class)) {
SZRequestMapping requestMapping = clazz.getAnnotation(SZRequestMapping.class);
baseUrl = requestMapping.value();
}
for (Method method : clazz.getMethods()) {
if (!method.isAnnotationPresent(SZRequestMapping.class)) continue;
SZRequestMapping requestMapping = method.getAnnotation(SZRequestMapping.class);
String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
if (handlerMapping.containsKey(url)) {
throw new Exception("The handlerMapping is exist");
}
handlerMapping.put(url, method);
System.out.println("Mapped" + url + "," + method);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void doAutowired() {
if (ioc.isEmpty()) return;
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
Field[] fields = entry.getValue().getClass().getDeclaredFields();
for (Field field : fields) {
if (!field.isAnnotationPresent(SZAutowired.class)) continue;
SZAutowired autowired = field.getAnnotation(SZAutowired.class);
String beanName = autowired.value().trim();
if ("".equals(beanName)) {
beanName = field.getType().getName();
}
//暴力強制訪問
field.setAccessible(true);
try {
//傳說中的依賴注入
field.set(entry.getValue(), ioc.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
private void doInstance() {
if (classNames.isEmpty()) return;
try {
for (String className : classNames) {
Class clazz = Class.forName(className);
if (clazz.isAnnotationPresent(SZController.class)) {
String beanName = toLowerFirstCase(clazz.getSimpleName());
Object instance = clazz.newInstance();
ioc.put(beanName, instance);
} else if (clazz.isAnnotationPresent(SZService.class)) {
//1.默認是類名首字母小寫
String beanName = toLowerFirstCase(clazz.getSimpleName());
//2.自定義命名
SZService service = (SZService) clazz.getAnnotation(SZService.class);
if (!"".equals(service.value())) {
beanName = service.value();
}
Object instance = clazz.newInstance();
//--------這是aop相關的,可以先忽略這塊代碼--------
/*SZAdvisedSupport config = instantionAopConfig();
config.setTargetClass(clazz);
config.setTarget(instance);
if (config.ponitCutMatch()) {
instance = new SZJdkDynamicAopProxy(config).getProxy();
}*/
//-------------------------------------------------
ioc.put(beanName, instance);
//3.如果是介面
for (Class i : clazz.getInterfaces()) {
if (ioc.containsKey(i.getName())) {
throw new Exception("The beanNames is exist");
}
ioc.put(i.getName(), instance);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private SZAdvisedSupport instantionAopConfig() {
SZAopConfig config = new SZAopConfig();
config.setPointCut(contextConfig.getProperty("pointCut"));
config.setAspectClass(contextConfig.getProperty("aspectClass"));
config.setAspectBefore(contextConfig.getProperty("aspectBefore"));
config.setAspectAfter(contextConfig.getProperty("aspectAfter"));
config.setAspectAfterThrow(contextConfig.getProperty("aspectAfterThrow"));
config.setAspectAfterThrowingName(contextConfig.getProperty("aspectAfterThrowingName"));
return new SZAdvisedSupport(config);
}
/**
* 類首字母小寫
*
* @param simpleName 類名
* @return 結果
*/
private String toLowerFirstCase(String simpleName) {
char[] chars = simpleName.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
private void doScanner(String scanPackage) {
URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
File classPath = new File(url.getFile());
for (File file : classPath.listFiles()) {
if (file.isDirectory()) {
doScanner(scanPackage + "." + file.getName());
} else {
if (!file.getName().endsWith(".class")) continue;
String className = (scanPackage + "." + file.getName().replace(".class", ""));
classNames.add(className);
}
}
}
private void doLoadConfig(String contextConfigLocation) {
//獲取組態檔的檔案流
InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
try {
contextConfig.load(is);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != is) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3.5 SZAutowired、SZControllerSZController、SZRequestMapping、SZService
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SZAutowired {
String value() default "";
}
-----------------------------------------------------------------------------------
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SZController {
String value() default "";
}
-----------------------------------------------------------------------------------
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SZRequestMapping {
String value() default "";
}
-----------------------------------------------------------------------------------
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SZService {
String value() default "";
}
3.6 IDemoService.java、DemoServiceImpl.java、DemoController.java
public interface IDemoService {
String get(String name);
}
------------------------------------------------------------------------------------
@SZService
public class DemoServiceImpl implements IDemoService {
@Override
public String get(String name) {
System.out.println("Invoker DemoService get method!!");
// int a = 1 / 0;
return "My name is " + name + ",from service";
}
}
------------------------------------------------------------------------------------
@SZController
@SZRequestMapping("/demo")
public class DemoController {
@SZAutowired
private IDemoService iDemoService;
@SZRequestMapping("/query")
public void query(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
String name) {
String result = iDemoService.get(name);
try {
httpServletResponse.getWriter().write(result);
} catch (IOException e) {
e.printStackTrace();
}
}
}
4、結果
專案啟動后,訪問controller地址:
/demo/query?name=sz

iDemoService.get(name)執行成功, 即成功注冊了bean,并且進行了依賴注入,
5、springICO回圈依賴問題
5.1什么是回圈依賴
回圈依賴其實就是回圈參考,也就是兩個或者兩個以上的 Bean 互相持有對方,最終形成倍訓,
比如,A依賴于B,B又依賴于A,
Spring中回圈依賴場景有:
- 構造器的回圈依賴(構造器注入)
- Field 屬性的回圈依賴(set注入)(我們上面手寫的springioc就是知使用了Field 屬性進行注入,只不過我們只是簡單的實作其功能,并不支持回圈依賴)
5.2回圈依賴處理機制
? 單例 bean 構造器引數回圈依賴(無法解決)
? prototype 原型 bean回圈依賴(無法解決)
? 單例bean通過setXxx或者@Autowired進行回圈依賴的解決方案:


beanA初始化時,圖中1 先將該beanA放入 Map<String, ObjectFactory<?>> singletonFactories 中,然會給beanA的屬性賦值時,如果需要注入beanB,則在singletonFactories 中找有沒有beanB,如果有則注入,如果沒有則創建beanB,
在創建beanB時依舊會將beanB放入Map<String, ObjectFactory<?>> singletonFactories 中,給beanB的屬性賦值時,如果需要注入beanA,則在singletonFactories 中尋找beanA注入,此時beanA已經在map中了,所以成功注入beanA,并且創建beanB成功,
最后再將beanB從singletonFactories中取出來,然后注入給beanA,這樣beanA和beanB都完成了物件初始化操作,解決了回圈依賴問題,
三、手寫實作AOP
1、基本思路

AdvisedSupport 專門用來決議AOP配置的類
AopConfig 封裝AOP的配置資訊(把鍵值對封裝成物件)
Advice 通知, 封裝了要織入的切面類,回呼的方法
JdkDynamicAopProxy 用來生成代理類
這幾個類是aop主要的類,我們模仿這幾個類,實作aop相關功能,
2、目錄結構

aop實作的代碼寫在了ioc實體化的方法中(3.4 doInstance()方法中注釋的那段代碼),因為要把代理類注入到容器中,所以在這個位置實作aop的相關功能,
3、代碼
3.1 SZAopConfig.java、TestAspect.java、SZAdivce.java
@Data
public class SZAopConfig {
private String pointCut;
private String aspectClass;
private String aspectBefore;
private String aspectAfter;
private String aspectAfterThrow;
private String aspectAfterThrowingName;
}
---------------------------------------------------------------------------
public class TestAspect {
public void before() {
System.out.print("Invoker Before Method!!\n");
}
public void after() {
System.out.print("Invoker After Method!!\n");
}
public void afterThrowing() {
System.out.print("出現例外!!\n");
}
}
---------------------------------------------------------------------------
@Data
public class SZAdivce {
private Object aspect;
private Method adviceMethod;
private String throwName;
public SZAdivce(Object aspect, Method adviceMethod) {
this.aspect = aspect;
this.adviceMethod = adviceMethod;
}
}
3.2 SZAdvisedSupport.java
public class SZAdvisedSupport {
private Class targetClass;
private Object target;
private SZAopConfig config;
private Pattern pointCutClassPattern;
Map<Method, Map<String, SZAdivce>> methodCache;
public SZAdvisedSupport(SZAopConfig config) {
this.config = config;
}
public boolean ponitCutMatch() {
return pointCutClassPattern.matcher(this.targetClass.getName()).matches();
}
public Map<String, SZAdivce> getAdvices(Method method, Class targetClass) throws Exception {
Map<String, SZAdivce> cache = methodCache.get(method);
if (cache == null) {
Method m = targetClass.getMethod(method.getName(), method.getParameterTypes());
cache = methodCache.get(m);
this.methodCache.put(method, cache);
}
return cache;
}
private void parse() {
//public .* com.sz.handwirttenspring.service..*Service..*(.*)
String pointCut = config.getPointCut()
.replaceAll("\\.", "\\\\.")
.replaceAll("\\\\.\\*", ".*")
.replaceAll("\\(", "\\\\(")
.replaceAll("\\)", "\\\\)");
String pointCutForClassRegex = pointCut.substring(0, pointCut.lastIndexOf("\\(") - 4);
pointCutClassPattern = Pattern.compile(pointCutForClassRegex.substring(pointCutForClassRegex.lastIndexOf(" ") + 1));
try {
methodCache = new HashMap<>();
//先快取所有的通知回呼方法
Map<String, Method> aspectMethods = new HashMap<>();
Class aspectClass = Class.forName(this.config.getAspectClass());
for (Method method : aspectClass.getMethods()) {
aspectMethods.put(method.getName(), method);
}
Pattern pointCutPattern = Pattern.compile(pointCut);
for (Method method : this.targetClass.getMethods()) {
String methodString = method.toString();
if (methodString.contains("throws")) {
methodString = methodString.substring(0, methodString.lastIndexOf("throws")).trim();
}
Matcher matcher = pointCutPattern.matcher(methodString);
if (matcher.matches()) {
Map<String, SZAdivce> advices = new HashMap<>();
//前置通知
if (!(config.getAspectBefore() == null || "".equals(config.getAspectBefore()))) {
advices.put("before", new SZAdivce(aspectClass.newInstance(), aspectMethods.get(config.getAspectBefore())));
}
//后置通知
if (!(config.getAspectAfter() == null || "".equals(config.getAspectAfter()))) {
advices.put("after", new SZAdivce(aspectClass.newInstance(), aspectMethods.get(config.getAspectAfter())));
}
//例外通知
if (!(config.getAspectAfterThrow() == null || "".equals(config.getAspectAfterThrow()))) {
SZAdivce adivce = new SZAdivce(aspectClass.newInstance(), aspectMethods.get(config.getAspectAfterThrow()));
adivce.setThrowName(config.getAspectAfterThrowingName());
advices.put("afterThrowing", adivce);
}
methodCache.put(method, advices);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public Class getTargetClass() {
return targetClass;
}
public void setTargetClass(Class targetClass) {
this.targetClass = targetClass;
parse();
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
public SZAopConfig getConfig() {
return config;
}
public void setConfig(SZAopConfig config) {
this.config = config;
}
}
3.3 SZJdkDynamicAopProxy.java
public class SZJdkDynamicAopProxy implements InvocationHandler {
private SZAdvisedSupport config;
public SZJdkDynamicAopProxy(SZAdvisedSupport config) {
this.config = config;
}
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(), this.config.getTargetClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Map<String, SZAdivce> adivces = this.config.getAdvices(method, this.config.getTargetClass());
Object returnValue = null;
try {
invokeAdvice(adivces.get("before"));
returnValue = method.invoke(this.config.getTarget(), args);
invokeAdvice(adivces.get("after"));
} catch (Exception e) {
invokeAdvice(adivces.get("afterThrowing"));
}
return returnValue;
}
private void invokeAdvice(SZAdivce adivce) {
try {
adivce.getAdviceMethod().invoke(adivce.getAspect());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
4、結果
啟動專案后,再次訪問/demo/query?name=sz,控制臺列印:

測驗下出現例外時的情況(放開 DemoServiceImpl 中 get() 的 int a = 1 / 0;)

四、本文原始碼
https://github.com/sunzhensky/handWrittenSpring.git
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/237624.html
標籤:java
下一篇:序列化流和反序列化流的使用
