Spring
Spring make java more simple;Spring make java more modern;Spring make java more reactive;Spring make java more productive;Spring make java more cloud-ready。
讓我們的Java開發更加簡潔、現代化、響應式編程、高性能高產、微服務。簡而言之Spring是Java目前「第一大框架」,Spring框架是由於軟體開發的複雜性而創建的。Spring使用的是基本的JavaBean來完成以前只可能由EJB完成的事情。然而,Spring的用途不僅僅限於伺服器端的開發。從簡單性、可測試性和鬆耦合性角度而言,絕大部分Java應用都可以從Spring中受益。
目的:解決企業應用開發的複雜性,簡化應用程式的開發。
功能:使用基本的JavaBean代替EJB,並提供了更多的企業應用功能
範圍:任何Java應用
核心點:Spring是一個輕量級控制反轉(==IoC==)和面向切面(==AOP==)的容器框架。
Spring FrameworkSpring BootSpring Cloud常規所說的 Spring 框架就是 Spring Framework,大約20個模塊,主要包括:
Core Container(核心容器)
1、Core 2、Beans 3、Context4、Expression Language (「SpEL」)
Core 和 Beans 是框架的基礎,提供了 「IoC」 和 「DI」,以「工廠模式」的實現來完成配置和業務邏輯的「解藕」。IOC跟DI解釋如下:
IOC容器(Inversion of Controller) 控制反轉Java思想是面向對象的開發,一個應用程式是由一組對象通過相互協作開發出的業務邏輯組成,那麼如何管理這些對象,使他們高效地協作呢?抽象工廠、工廠方法設計模式」可以幫我們創建對象,「生成器模式」幫我們處理對象間的依賴關係,不也能完成這些功能嗎?可是這些又需要我們創建另一些工廠類、生成器類,我們又要而外管理這些類,增加了我們的負擔。所以用另外的方式,如果對象需要的時候,就自動地生成對象,不用再去創建。舉個例子:原來我們餓了,就出去吃飯,但是現在有了外賣之後,就可以訂餐了,我們可以把我們的需求告訴美團,讓他們給我們送飯。這裡主導關係發生了變化,原來是我們自己,但是現在是美團。Spring提出了一種思想:就是由spring來負責控制對象的生命周期和對象間的關係。所有的類都會在spring容器中登記,告訴spring你是個什麼東西,你需要什麼東西,然後spring會在系統運行到適當的時候,把你要的東西主動給你,同時也把你交給其他需要你的東西。所有的類的創建、銷毀都由 spring來控制,也就是說控制對象生存周期的不再是引用它的對象,而是spring。對於某個具體的對象而言,以前是它控制其他對象,現在是所有對象都被spring控制,所以這叫控制反轉(IOC)。當我們程序運行到需要某個對象到時候,會自動到實現依賴注入也就是DI。
Context :基於 Core 和 Beans,提供了大量的擴展,包括「國際化」操作(基於 JDK )、資源加載(基於 JDK properties)、數據校驗(Spring 自己封裝的數據校驗機制)、數據綁定(Spring 特有,HTTP 請求中的參數直接映射稱 POJO)、類型轉換,「ApplicationContext」接口是 Context 的核心。
SpEL:Spring Expression Language 無非就是一些簡單點Spring語法表達式如下:application.yml
server: port: 8181
publicclassHelloHandler{@Value("${server.port}")private String port;}
Data Access
DBC :連接資料庫點框架如 JdbcTemplateORM :對象關係映射如 JPA、 JDO、 Hibernate、 MyBatis、 Spring Data JPA 底層是 HibernateOXM Object XML Mapping :這個主要是XML文件點解析 JMS Java :消息服務( Java Message Service , JMS )是一個 Java 標準,定義了使用消息代理的通用APITransaction :資料庫事務特性Web
Web-Servlet,Spring Web MVC、WebSocket,Spring1- 4隻有Web-Servlet Web-Reactive,Spring Web Flux,Spring 5之後引入的Web-Struts,對 Struts 框架的支持,但是現在已經被拋棄了Spring 3 之後就拋棄了AOP
將面向切面編程直接集成到了 Spring 中,進行面向切面的業務開發,事務管理。可以認為AOP是面向對象編程的一種補充。AOP(Aspect Oriented Programming)稱為面向切面編程。
比如進行一個計算器的編寫,需要實現加、減、乘、除四種簡單的運算,編寫四種不同的方法。還有另外的兩個需求是在每種運算之前和運算之後需要列印日誌進行記錄,需要進行數字合規的校驗。我們就得考慮如何能簡單地實現呢?就是得把日誌記錄和數據校驗等可重用的功能模塊分離出來,然後在程序的執行的合適的地方動態地植入這些代碼並執行。這樣就簡化了代碼的書寫,業務邏輯代碼中沒有參和通用邏輯的代碼,業務模塊更簡潔,只包含核心業務代碼。實現了業務邏輯和通用邏輯的代碼分離,便於維護和升級,降低了業務邏輯和通用邏輯的耦合。有人會想到把這些通用的功能整合到一個方法中,去調用,這樣也是避免不了重複調用,並且在業務邏輯中添加額外的代碼。Spring通過配置的方式,而且不需要在業務邏輯代碼中添加任何額外代碼,就可以很好地實現上述功能。以上這種方式就是spring中實現的AOP:意思是面向切面編程,它提供從另一個角度來考慮程序結構以完善面向對象編程(相對於OOP),即可以通過==在編譯期間、裝載期間或運行期間實現在不修改原始碼的情況下,給程序動態添加功能的一種技術==。通俗點說就是把可重用的功能提取出來,然後將這些通用功能在合適的時候織入到應用程式中;比如安全,日記記錄,這些都是通用的功能,我們可以把它們提取出來,然後在程序執行的合適地方織入這些代碼並執行它們,從而完成需要的功能並復用了這些功能。
Test
Spring Boot Test 單元測試,軟體開發過程中必備,並且閱讀源碼可以發現 SpringRunner 底層使用的是 「JUnit」
Spring 版本與 Java 版本的對應關係
目前Java 開發的標配:Spring Framwork 5、Spring Boot 2、JDK 8。
IoC
前置核心知識:反射 + xml解析。
XML 解析
IoC 讀取 spring-ioc.xml 獲取 bean 相關信息,類信息、屬性值信息,xml解析就跟撥洋蔥一樣一層層地讀取數據。
根據第 1 步獲取的信息,動態創建對象
反射 (1) 創建對象:通過==反射機制==獲取到目標類的構造函數,調用構造函數;(2) 給對象賦值,下面是具體的實現。
spring-ioc.xml 文件如下:
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"><beanid="user"class="com.sowhat.demo.entity.User"><propertyname="id"value="1"></property><propertyname="name"value="張三"></property></bean><beanid="user2"class="com.sowhat.demo.entity.User"><propertyname="id"value="2"></property><propertyname="name"value="李四"></property></bean></beans>
User類
package com.sowhat.demo.entity;import lombok.Data;@DatapublicclassUser{private Integer id;private String name;}
Spring 提供點解析API
package com.sowhat.demo;import com.sowhat.demo.entity.User;import com.sowhat.demo.ioc.MyClassPathXmlApplictionContext;import org.springframework.context.ApplicationContext;publicclassTest{publicstatic void main(String[] args) {ApplicationContext applicationContext = new MyClassPathXmlApplictionContext("spring-ioc.xml");User user1 = (User) applicationContext.getBean("user");System.out.println(user1);User user2 = applicationContext.getBean(User.class);System.out.println(user2); }}
官方 ApplicationContext 接口查看
package org.springframework.context;import org.springframework.beans.factory.HierarchicalBeanFactory;import org.springframework.beans.factory.ListableBeanFactory;import org.springframework.beans.factory.config.AutowireCapableBeanFactory;import org.springframework.core.env.EnvironmentCapable;import org.springframework.core.io.support.ResourcePatternResolver;import org.springframework.lang.Nullable;publicinterfaceApplicationContextextendsEnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver{@NullableString getId();String getApplicationName();String getDisplayName();longgetStartupDate();@NullableApplicationContext getParent();AutowireCapableBeanFactory getAutowireCapableBeanFactory()throws IllegalStateException;}
敲重點!!!
根據官方ApplicationContext實現 MyClassPathXmlApplictionContext,實現接口後我們只實現兩個重要接口,這裡的核心思想無非也就是 「用XML將spring-ioc文件讀取出來」,然後靈活「運用反射實現Class的對象class的創建」,然後根據class 返回創建好的Object。
package com.sowhat.demo.ioc;import com.sowhat.demo.entity.User;import org.dom4j.Document;import org.dom4j.DocumentException;import org.dom4j.Element;import org.dom4j.io.SAXReader;import org.springframework.beans.BeansException;import org.springframework.beans.factory.BeanFactory;import org.springframework.beans.factory.NoSuchBeanDefinitionException;import org.springframework.beans.factory.ObjectProvider;import org.springframework.beans.factory.config.AutowireCapableBeanFactory;import org.springframework.context.ApplicationContext;import org.springframework.context.MessageSourceResolvable;import org.springframework.context.NoSuchMessageException;import org.springframework.core.ResolvableType;import org.springframework.core.env.Environment;import org.springframework.core.io.Resource;import java.io.IOException;import java.lang.annotation.Annotation;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.util.*;public class MyClassPathXmlApplictionContext implements ApplicationContext { private static Map<String,Object> iocContainer; static { iocContainer = new HashMap<>(); } public MyClassPathXmlApplictionContext(String path) { //解析 XML 將 XML 文件轉換成一個對象 SAXReader reader = new SAXReader(); try { Document document = reader.read("src/main/resources/spring-ioc.xml"); Element root = document.getRootElement(); Iterator<Element> rootIter = root.elementIterator(); Class clazz = null; while(rootIter.hasNext()){ Element bean = rootIter.next(); //id值、class值 String id = bean.attributeValue("id"); String className = bean.attributeValue("class"); //1、創建對象 //獲取目標類的運行時類 clazz = Class.forName(className); //獲取無參構造函數 Constructor constructor = clazz.getConstructor(); //調用構造器創建對象 Object object = constructor.newInstance(); Iterator<Element> beanIter = bean.elementIterator(); while(beanIter.hasNext()){ Element property = beanIter.next(); String name = property.attributeValue("name"); String value = property.attributeValue("value"); //獲取 setter 方法 //首字母變大寫 JavaBean寫法 String methodName = "set"+name.substring(0,1).toUpperCase()+name.substring(1); //參數類型就是成員變量的類型 Field field = clazz.getDeclaredField(name); Method method = clazz.getMethod(methodName,field.getType()); //賦值 Object propertyValue = value; switch (field.getType().getName()){ case "java.lang.Integer": propertyValue = Integer.parseInt(value); break; } method.invoke(object,propertyValue); } iocContainer.put(id,object); } } catch (Exception e) { e.printStackTrace(); } } // 根據Bean的名字獲取 @Override public Object getBean(String s) throws BeansException { return iocContainer.get(s); } // 根據Bean的類型來獲取,這裡還可以判斷如果容器中有兩個相同類型對象,則報錯 @Override public <T> T getBean(Class<T> aClass) throws BeansException { Collection values = iocContainer.values(); for(Object object:values){ if(object.getClass().equals(aClass)) return (T) object; } return null; } ......不再顯示}
當然,上面只是比較粗淺的實現但是對於掌握精髓跟梳理流程還有用的,同時也有助於我們發散思維。
AOP
前置知識:動態代理AOP的思路有點繞,不過只有一個重點,就是動態代理.
簡單ADD方法:
package com.sowhat.demo.aop;publicinterfaceCal{publicintadd(int num1,int num2);publicintsub(int num1,int num2);publicintmul(int num1,int num2);publicintdiv(int num1,int num2);}
Spring 官方AOP註解實現
package com.sowhat.demo.aop;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.springframework.stereotype.Component;import java.util.Arrays;@Component@AspectpublicclassLoggerAspect{/** * 列印日誌 */@Before("execution(public int com.sowhat.demo.aop.impl.CalImpl.*(..)))")public void before(JoinPoint joinPoint){ String name = joinPoint.getSignature().getName(); System.out.println(name+"方法的參數是"+ Arrays.toString(joinPoint.getArgs())); }@AfterReturning(value = "execution(public int com.sowhat.demo.aop.impl.CalImpl.*(..)))",returning = "result")public void afterReturn(JoinPoint joinPoint,Object result){ String name = joinPoint.getSignature().getName(); System.out.println(name+"方法的返回值是"+ result); }}
spring-aop.xml
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"><context:component-scanbase-package="com.sowhat.demo"></context:component-scan><aop:aspectj-autoproxy></aop:aspectj-autoproxy></beans>
調用官方AOP
package com.sowhat.demo.aop;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;publicclassTest{publicstaticvoidmain(String[] args){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop.xml"); Cal cal = applicationContext.getBean(Cal.class); System.out.println(cal.add(1,1)); }}
敲重點!!!
自我實現AOP。
動態代理:
靜態代理三要素:
接口函數實現具體接口的業務類,包含業務具體方法包裝類,包含一個實現具體類,實現 類接口類,然後在接口類對前後實現對具體方法對調用包裝。
一個編輯好的Java文件加載到內存中new出對象,一般經過如下幾個步驟:
.java 通過 javac 生成了 Java 二進位文件.class*.class 文件被JVM加載到內存中,然後生成一個Class對象 通過Class對象new出我們經常用到的對象。Java中文件獲取*.class方式有三種:
普通*.java文件編譯成*class 文件通過網絡傳速*class文件。 通過動態代理方式生成*class文件動態代理刨析:
重點理解兩個東西:Proxy(業務對象的代理類),InvocationHandler(裡面有invoke方法來提供服務)。
基本接口:
publicinterfaceIPerson {voidsay();}
業務實現類:
publicclassManimplementsIPerson{@Overridepublicvoidsay(){ System.out.println("man say"); }}
先新建一個類實現「InvocationHandler」,用來實現服務的類。
publicclass NormalHandler implements InvocationHandler {privateObject target;public NormalHandler(Object target) {this.target = target; }@OverridepublicObject invoke(Object proxy, Method method, Object[] args) throws Throwable { L.d("man say invoked at : " + System.currentTimeMillis()); method.invoke(target, args); // 服務類自動實現。returnnull; }}
「Proxy」的子類,來實現提供優質服務的類對象。
Man man = new Man();NormalHandler normalHandler = new NormalHandler(man);AnnotationHandler annotationHandler = new AnnotationHandler();IPerson iPerson = (IPerson) // 通過此方法獲得動態代理對象Proxy.newProxyInstance(IPerson.class.getClassLoader(),newClass[] {IPerson.class, IAnimal.class}, annotationHandler);iPerson.say();
通過「getProxyClass0」來生成字節碼文件並且生成Class 對象。
privatestaticfinal Class<?>[] constructorParams = { InvocationHandler.class };publicstatic Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)throws IllegalArgumentException{ Class<?> cl = getProxyClass0(loader, intfs); //生成字節碼跟Class 對象 這是重點 ... final Constructor<?> cons = cl.getConstructor(constructorParams);// 獲得構造器if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run(){ cons.setAccessible(true);returnnull; } }); }return cons.newInstance(new Object[]{h}); //new 出一個實例}
Class文件生成:
privatestatic Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {if (interfaces.length > 65535) {thrownew IllegalArgumentException("interface limit exceeded"); }// If the proxy class defined by the given loader implementing// the given interfaces exists, this will simply return the cached copy;// otherwise, it will create the proxy class via the ProxyClassFactoryreturn proxyClassCache.get(loader, interfaces); }---public V get(K key, P parameter) { Objects.requireNonNull(parameter); expungeStaleEntries(); Object cacheKey = CacheKey.valueOf(key, refQueue);// lazily install the 2nd level valuesMap for the particular cacheKey ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);if (valuesMap == null) { ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>());if (oldValuesMap != null) { valuesMap = oldValuesMap; } }// 上面的方法是先看到我們一個緩存中是否存在已生成的Class 對象。// create subKey and retrieve the possible Supplier<V> stored by that// subKey from valuesMap// 通過下面的apply 生成我們的代理類, Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); Supplier<V> supplier = valuesMap.get(subKey); Factory factory = null;-ProxyClassFactory 類下面的 apply 是動態代理的核心裏面會遍歷然後判斷我們的接口,/* * Choose a name for the proxy class to generate. */long num = nextUniqueNumber.getAndIncrement(); // 生成隨機數字 String proxyName = proxyPkg + proxyClassNamePrefix(其實=$proxy) + num;/* * Generate the specified proxy class. 生成二進位的代理類字節數組 */byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags);return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length)// 通過字節碼拿到 Class 對象, 這是調用native 方法。
生成的字節碼數組文件我們可以導出到本地,然後通過Java反編譯工具可以看到重寫的方法其實調用的是this.h.invoke(),其中h就是InvocationHandler的一個實例,
模擬實現AOP功能:
package com.sowhat.demo.aop;import org.springframework.aop.framework.AopProxy;import java.io.Serializable;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.Arrays;publicclassMyJdkDynamicAopProxyimplementsAopProxy, InvocationHandler, Serializable {//目標對象private Object target;/** * 接收目標對象 * 返回動態代理對象 * * @return */public Object bind(Object target) {this.target = target;//調用getProxyreturnthis.getProxy(MyJdkDynamicAopProxy.class.getClassLoader()); // 這裡就是個隨便的類加載器 將我們動態創建的類加載到JVM }/** * 業務代碼的執行 * 日誌的輸出 當我們調用函數當時候會自動調用invoke * * @param proxy * @param method * @param args * @throws Throwable */@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method.getName() + "的參數是" + Arrays.toString(args)); Object result = method.invoke(target, args); System.out.println(method.getName() + "的結果是" + result);return result; }@Overridepublic Object getProxy() {returnnull; }@Overridepublic Object getProxy(ClassLoader classLoader) {return Proxy.newProxyInstance(classLoader, target.getClass().getInterfaces(), this); }}
相比來說,只要了解了動態代理機制 AOP也就很簡單了。