面試阿里,美團,京東都會被問到的Spring ,從基礎到源碼幫你全搞定

1 前言

  1. Spring是一個輕量級開源框架,它是爲了解決企業應用開發的複雜性而建立的。框架的主要優點之一就是其分層架構,分層架構容許使用者選擇使用哪個組件,同時爲 J2EE 應用程序開發提供集成的框架。
  2. Spring是衆多優秀設計模式的組合(工廠、單例、代理、適配器、包裝器、觀察者、模板、策略)。
  3. Spring的用途不只限於服務器端的開發。從簡單性、可測試性和鬆耦合的角度而言,任何Java應用均可以從Spring中受益。
  4. Spring並未替代現有框架產品,而是將衆多框架進行有機整合,簡化企業級開發,俗稱"膠水框架"。

2 Spring架構組成

Spring架構由諸多模塊組成,可分類爲java

  • 核心技術:依賴注入,事件,資源,i18n,驗證,數據綁定,類型轉換,SpEL,AOP。
  • 測試:模擬對象,TestContext框架,Spring MVC測試,WebTestClient。
  • 數據訪問:事務,DAO支持,JDBC,ORM,封送XML。
  • Spring MVC和 Spring WebFlux Web框架。
  • 集成:遠程處理,JMS,JCA,JMX,電子郵件,任務,調度,緩存。
  • 語言:Kotlin,Groovy,動態語言。
  • List item

Spring架構組成以下圖
面試阿里,美團,京東都會被問到的Spring ,從基礎到源碼幫你全搞定web

3 Spring環境搭建

3.1 pom.xml中引入Spring經常使用依賴

<?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 
          http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.qf</groupId>
    <artifactId>hello-spring</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- Spring經常使用依賴 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>
    </dependencies>
</project>

3.2 建立Spring配置文件

命名無限制,約定俗成命名有:spring-context.xml、applicationContext.xml、beans.xml面試

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

</beans>

4 Spring工廠編碼(入門程序)

4.1 定義目標Bean類型

public class MyClass{
    public void show(){
        System.out.println("HelloWorld");
    }
}

4.2spring-context.xml中的< beans >內部配置bean

在spring-context.xml中配置MyClass的bean後,當項目啓動時,spring容器會自動建立MyClass實例,這個實例名字叫mcspring

<!-- 配置實例(id:「惟一標識」  class="須要被建立的目標對象全限定名") -->
<bean id="mc" class="com.qf.spring.part1.factory.MyClass" />

測試代碼express

public class TestFactory{
    /**
     * 程序中的對象都交由Spring的ApplicationContext工廠進行建立。
     */
    public static void main(String[] args){
        //1\. 讀取配置文件中所需建立的bean對象,並得到工廠對象
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-context.xml");
        //2\. 經過id獲取bean對象
        MyClass mc = (MyClass) ctx.getBean("mc");
        //3\. 使用對象
        mc.show();
    }
}

5 IoC(Inversion of Control )控制反轉

控制反轉是Spring框架的核心,所謂控制反轉就是應用自己不負責依賴對象的建立及維護,依賴對象的建立及維護是由外部容器負責的, 這樣控制權就由應用轉移到了外部容器,控制權的轉移就是所謂反轉。這樣就由以前的本身建立依賴對象,變爲由spring容器建立。(變主動爲被動,即反轉)。控制反轉解決了具備依賴關係的組件之間的強耦合,使得項目形態更加穩健。apache

5.1 項目中強耦合問題

public class UserDAOImpl implements UserDAO{....}
public class UserServiceImpl implements UserService {
    // 經過傳統的new方式強耦合了UserDAOImpl!!!,使得UserServiceImpl變得不穩健!!
    private UserDAO userDAO= new UserDAOImpl();
    @Override
    public User queryUser() {
        return userDAO.queryUser();
    }
    ....
}

5.2 解決方案

// 不引用任何一個具體的組件(實現類),在須要其餘組件的位置預留存取值入口(set/get)
public class UserServiceImpl implements UserService {
    // !!!再也不耦合任何DAO實現!!!,消除不穩健因素!!
    private UserDAO userDAO;
    // 爲userDAO定義set/get,容許userDAO屬性接收spring賦值
    //Getters And Setters
    @Override
    public User queryUser() {
        return userDAO.queryUser();
    }
    ....
}

在spring配置文件中配置UserDAO和UserService對應的bean編程

<bean id="userDAO" class="com.qf.spring.part1.injection.UserDaoImpl"></bean>
<!-- UserServiceImpl組件 -->
<bean id="userService" class="com.qf.spring.part1.injection.UserServiceImpl">
    <!-- 由spring爲userDAO屬性賦值,值爲id="userDAO"的bean -->
    <property name="userDAO" ref="userDAO"/>
</bean>

6 DI(Dependency Injection)依賴注入

6.1 概念

在Spring建立對象的同時,爲其屬性賦值,稱之爲依賴注入,注入方式主要有如下2種設計模式

  • 構造函數注入
  • Setter方法注入

6.2 Setter方法注入

建立對象時,Spring工廠會經過Setter方法爲對象的屬性賦值。緩存

6.2.1 定義目標Bean類型

public class User {
    private Integer id;
    private String password;
    private String sex;
    private Integer age;
    private Date bornDate;
    private String[] hobbys;
    private Set<String> phones;
    private List<String> names;
    private Map<String,String> countries;
    private Properties files;
    //Getters And Setters
}

6.2.2 基本類型 + 字符串類型 + 日期類型

<bean id="u1" class="com.qf.spring.part1.injection.User">
    <!--base field-->
    <property name="id" value="1001" />
    <property name="password" value="123456" />
    <property name="sex" value="male" />
    <property name="age" value="20" />
    <property name="bornDate" value="1990/1/1" /><!--注意格式"/"-->
</bean>

6.2.3 容器類型(list,set,map,Properties)

<bean id="u1" class="com.qf.spring.part1.injection.User">   
    <!--Array-->
    <property name="hobbys">
        <array>
            <value>Run</value>
            <value>Swim</value>
            <value>Climb</value>
        </array>
    </property>

    <!--Set-->
    <property name="phones">
        <set>
            <value>13777777777</value>
            <value>13888888888</value>
            <value>13999999999</value>
        </set>
    </property>

    <!--List-->
    <property name="names">
        <list>
            <value>tom</value>
            <value>jack</value>
            <value>marry</value>
        </list>
    </property>

    <!--Map-->
    <property name="countries">
        <map>
            <entry key="CN" value="China" />
            <entry key="US" value="America" />
            <entry key="KR" value="Korea" />
        </map>
    </property>

    <!--Properties-->
    <property name="files">
        <props>
            <prop key="first">One</prop>
            <prop key="second">Two</prop>
            <prop key="third">Three</prop>
        </props>
    </property>
</bean>

6.2.4 自定義類型

<!--次要bean,被做爲屬性-->
<bean id="addr" class="com.qf.spring.part1.injection.Address">
    <property name="position" value="北京市海淀區" />
    <property name="zipCode" value="100001" />
</bean>

<!--主要bean,操做的主體-->
<bean id="u2" class="com.qf.spring.part1.injection.User">
    <property name="address" ref="addr" /><!--address屬性引用addr對象-->
</bean>

6.3 構造注入

建立對象時,Spring工廠會經過構造方法爲對象的屬性賦值。服務器

6.3.1 定義目標Bean類型

public class Student {
    private Integer id;
    private String name;
    private String sex;
    private Integer age;

    //Constructors
    public Student(Integer id , String name , String sex , Integer age){
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.age = age;
    }
}

6.3.2 注入

<!--構造注入-->
<bean id="u3" class="com.qf.zcg.spring.day1.t2.ioc.Student">
    <constructor-arg name="id" value="1234" /> <!-- 除標籤名稱有變化,其餘均和Set注入一致 -->
    <constructor-arg name="name" value="tom" />
    <constructor-arg name="age" value="20" />
    <constructor-arg name="sex" value="male" />
</bean>

7 Spring工廠特性

7.1 餓漢式建立優點

工廠建立以後,會將Spring配置文件中的全部對象都建立完成(餓漢式),提升程序運行效率,避免屢次IO,減小對象建立時間。(概念接近鏈接池,一次性建立好,使用時直接獲取)

7.2 生命週期方法

  • 自定義初始化方法:添加「init-method」屬性,Spring則會在建立對象以後,調用此方法。
  • 自定義銷燬方法:添加「destroy-method」屬性,Spring則會在銷燬對象以前,調用此方法。
  • 銷燬:工廠的close()方法被調用以後,Spring會毀掉全部已建立的單例對象。
  • 分類:Singleton對象由Spring容器銷燬、Prototype對象由JVM銷燬。

7.3 生命週期註解

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@PostConstruct //初始化 
public void init(){
    System.out.println("init method executed");
}

@PreDestroy //銷燬
public void destroy(){
    System.out.println("destroy method executed");
}

7.4 生命週期階段

單例bean:singleton

隨工廠啓動建立 ==》 構造方法 ==》 set方法(注入值) ==》 init(初始化) ==》 構建完成 ==》隨工廠關閉銷燬

多例bean:prototype

被使用時建立 ==》 構造方法 ==》 set方法(注入值) ==》 init(初始化) ==》 構建完成 ==》JVM垃圾回收銷燬

8 代理設計模式

8.1 概念

將核心功能與輔助功能(事務、日誌、性能監控代碼)分離,達到核心業務功能更純粹、輔助業務功能可複用。
面試阿里,美團,京東都會被問到的Spring ,從基礎到源碼幫你全搞定

8.2 靜態代理設計模式

經過代理類的對象,爲原始類的對象(目標類的對象)添加輔助功能,更容易更換代理實現類、利於維護。

  • 代理類 = 實現原始類相同接口 + 添加輔助功能 + 調用原始類的業務方法。
  • 靜態代理的問題
    • 代理類數量過多,不利於項目的管理。
    • 多個代理類的輔助功能代碼冗餘,修改時,維護性差。

8.3 動態代理設計模式

8.3.1 JDK動態代理實現(基於接口)

//目標
final OrderService os = new OrderServiceImpl();
//額外功能
InvocationHandler handler = new InvocationHandler(){//1.設置回調函數(額外功能代碼)
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable {
        System.out.println("start...");
        method.invoke(os, args);
         System.out.println("end...");
        return null;
    }
};
//2.建立動態代理類
Object proxyObj = Proxy.newProxyInstance(ClassLoader , Interfaces , InvocationHandler);

8.3.2 CGlib動態代理實現(基於繼承)

final OrderService os = new OrderServiceImpl();
Enhancer cnh = new Enhancer();//1.建立字節碼曾強對象
enh.setSuperclass(os.getClass());//2.設置父類(等價於實現原始類接口)
enh.setCallback(new InvocationHandler(){//3.設置回調函數(額外功能代碼)
    @Override
    public Object invoke(Object proxy , Method method, Object[] args) throws Throwable{
        System.out.println("start...");
        Object ret = method.invoke(os,args);
        System.out.println("end...");
        return ret;
    }
});
OrderService proxy = (OrderService)enh.create();//4.建立動態代理類
proxy,createOrder();

9 面向切面編程

9.1 概念

AOP(Aspect Oriented Programming),即面向切面編程,利用一種稱爲"橫切"的技術,剖開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其命名爲"Aspect",即切面。所謂"切面",簡單說就是那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減小系統的重複代碼,下降模塊之間的耦合度,並有利於將來的可操做性和可維護性。

通俗的概念來說,所謂的面向切面編程就是針對被代理對象的方法在某個特定的執行時機(方法調用以前、方法調用以後、方法拋出異常),作出一些額外的橫向的處理。好處在於:1. 能夠在不修改原有代碼基礎上橫向擴展咱們的內容;2. 將一些方法中的通用邏輯進行統一化的處理。

OOP(面向對象編程)和AOP(面向切面編程)的區別:oop是對類縱向的擴展;aop是橫向的擴展。

9.2 AOP開發術語

  • 鏈接點(Joinpoint):鏈接點是程序類中客觀存在的方法,可被Spring攔截並切入內容。
  • 切入點(Pointcut):被切入鏈接點。
  • 通知、加強(Advice):能夠爲切入點添加額外功能,分爲:前置通知、後置通知、異常通知、環繞通知等。
  • 目標對象(Target):代理的目標對象
  • 織入(Weaving):把通知應用到具體的類,進而建立新的代理類的過程。
  • 代理(Proxy):被AOP織入通知後,產生的結果類。
  • 切面(Aspect):由切點和通知組成,將橫切邏輯織入切面所指定的鏈接點中。

9.3 做用

Spring的AOP編程便是經過動態代理類爲原始類的方法添加輔助功能。

9.4 環境搭建

引入AOP相關依賴

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.1.6.RELEASE</version>
</dependency>

spring-context.xml引入AOP命名空間

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       ">
</beans>

9.5 開發流程

定義原始類

package com.qf.aaron.aop.basic;

public interface UserService {
    public void save();
}
package com.qf.aaron.aop.basic;

public class UserServiceImpl implements UserService {
    public void save() {
        System.out.println("save method executed...");
    }
}

定義通知類(添加額外功能

package com.qf.aaron.aop.basic;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

public class MyAdvice implements MethodBeforeAdvice { //實現前置通知接口
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("before advice executed...");
    }
}

定義bean標籤

<!--原始對象-->
<bean id="us" class="com.qf.aaron.aop.basic.UserServiceImpl" />

<!--輔助對象-->
<bean id="myAdvice" class="com.qf.aaron.aop.basic.MyAdvice" />

定義切入點(PointCut)

造成切面(Aspect)

<aop:config>
    <!--切點-->
    <aop:pointcut id="myPointCut" expression="execution(* save())" />
</aop:config>

```java
<aop:config>
    <!--組裝切面 -->
    <aop:advisor advice-ref="myAdvice" pointcut-ref="myPointCut" />
</aop:config>

9.6 通知類

可定義的通知類有6種,能夠按需求選擇通知類。

前置通知:MethodBeforeAdvice

後置通知:AfterAdvice

後置通知:AfterReturningAdvice //有異常不執行,方法會因異常而結束,無返回值

異常通知:ThrowsAdvice

環繞通知:MethodInterceptor

9.7 JDK動態代理和CGLIB動態代理的選擇

  • spring底層,包含了jdk代理和cglib代理兩種動態代理生成機制
  • 基本規則是:目標業務類若是有接口則用JDK代理,沒有接口則用CGLib代理

可是spring中默認開啓JDK動態代理,當須要使用CGLIB動態代理時,須要在spring配置文件中配置。

<!--  使用cglib的方式實現aop -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>

10 基於aspectJ的AOP實現

編寫aspectJ通知代碼:

@Aspect
public class AspectJAdvisor {

    // 環繞通知
    @Around("execution(* org.example.service.impl.*.*(..))")
    public Object timer(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("前置通知");
        Object o = pjp.proceed();
        System.out.println("【後置通知】");
        return o;
    }

    // 後置通知
    @After("execution(* org.example.service.impl.*.*(..))")
    public void after(JoinPoint jp) throws Throwable{
        System.out.println("====After method invokded====");
    }

    // 前置通知
    @Before("execution(* org.example.service.impl.*.*(..))")
    public void before(JoinPoint jp){
        System.out.println("====before method invoked====");
    }
    // 正常返回的通知
    @AfterReturning("execution(* org.example.service.impl.*.*(..))")
    public void afterReturning(JoinPoint jp){
        System.out.println("====after value return====");
    }
    // 拋出異常後的通知,方法的異常必須與代理類拋出的異常一致,throwing的值要與異常的形參名保持一致
    @AfterThrowing(value = "execution(* org.example.service.impl.*.*(..))", throwing="npe")
    public void afterThrowException(JoinPoint jp, NullPointerException npe){
        System.out.println("====after exception throwing====");
    }
}

配置

<bean id="userService" class="org.example.service.impl.UserServiceImpl"></bean>

<bean id="throwsAdvisor" class="org.example.advisor.AspectJAdvisor"></bean>

<!--- aspectJ是使用cglib來實現動態代理的 -->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>

11 基於註解開發

11.1 聲明bean

用於替換自建類型組件的 <bean…>標籤;能夠更快速的聲明bean

  • @Service 業務類專用
    @Repository dao實現類專用
    @Controller web層專用
  • @Component 通用
  • @Scope 用戶控制bean的建立模式
// @Service說明 此類是一個業務類,須要將此類歸入工廠  等價替換掉 <bean class="xxx.UserServiceImpl">
// @Service默認beanId == 首字母小寫的類名"userServiceImpl"
// @Service("userService") 自定義beanId爲"userService"
@Service //聲明bean,且id="userServiceImpl"
@Scope("singleton") //聲明建立模式,默認爲單例模式 ;@Scope("prototype")便可設置爲多例模式
public class UserServiceImpl implements UserService {
    ...   
}

11.2 注入(DI)

用於完成bean中屬性值的注入

  • @Autowired 基於類型自動注入
  • @Resource 基於名稱自動注入
  • @Qualifier(「userDAO」) 限定要自動注入的bean的id,通常和@Autowired聯用
  • @Value 注入簡單類型數據 (jdk8種+String)
@Service
public class UserServiceImpl implements UserService {

    @Autowired //注入類型爲UserDAO的bean
    @Qualifier("userDAO2") //若是有多個類型爲UserDAO的bean,能夠用此註解從中挑選一個
    private UserDAO userDAO;
}
@Service
public class UserServiceImpl implements UserService {

    @Resource("userDAO3") //注入id=「userDAO3」的bean
    private UserDAO userDAO;
    /*
    @Resource //注入id=「userDAO」的bean
    private UserDAO userDAO;
    */
}
public class XX{
    @Value("100") //注入數字
    private Integer id;
    @Value("shine") //注入String
    private String name;
}

11.3 事務控制

用於控制事務切入

  • @Transactional
  • 工廠配置中的 <tx:advice… 和 <aop:config… 能夠省略 !!
//類中的每一個方法都切入事務(有本身的事務控制的方法除外)
@Transactional(isolation=Isolation.READ_COMMITTED,propagation=Propagation.REQUIRED,readOnly=false,rollbackFor=Exception.class,timeout = -1)
public class UserServiceImpl implements UserService {
    ...
    //該方法本身的事務控制,僅對此方法有效
    @Transactional(propagation=Propagation.SUPPORTS)
    public List<User> queryAll() {
        return userDao.queryAll();
    }
    public void save(User user){
        userDao.save(user);
    }
}

11.4 註解所需配置

<!-- 告知spring,哪些包中 有被註解的類、方法、屬性 -->
<!-- <context:component-scan base-package="com.qf.a,com.xx.b"></context:component-scan> -->
<context:component-scan base-package="com.qf"></context:component-scan>

<!-- 告知spring,@Transactional在定製事務時,基於txManager=DataSourceTransactionManager -->
<tx:annotation-driven transaction-manager="txManager"/>

11.5 AOP開發

11.5.1 註解使用

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect // 聲明此類是一個切面類:會包含切入點(pointcut)和通知(advice)
@Component //聲明組件,進入工廠
public class MyAspect {
    // 定義切入點
    @Pointcut("execution(* com.qf.spring.service.UserServiceImpl.*(..))")
    public void pc(){}

    @Before("pc()") // 前置通知
    public void mybefore(JoinPoint a) {
        System.out.println("target:"+a.getTarget());
        System.out.println("args:"+a.getArgs());
        System.out.println("method's name:"+a.getSignature().getName());
        System.out.println("before~~~~");
    }

    @AfterReturning(value="pc()",returning="ret") // 後置通知
    public void myAfterReturning(JoinPoint a,Object ret){
        System.out.println("after~~~~:"+ret);
    }

    @Around("pc()") // 環繞通知
    public Object myInterceptor(ProceedingJoinPoint p) throws Throwable {
        System.out.println("interceptor1~~~~");
        Object ret = p.proceed();
        System.out.println("interceptor2~~~~");
        return ret;
    }

    @AfterThrowing(value="pc()",throwing="ex") // 異常通知
    public void myThrows(JoinPoint jp,Exception ex){
        System.out.println("throws");
        System.out.println("===="+ex.getMessage());
    }
}

11.5.2 配置

<!-- 添加以下配置,啓用aop註解 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

12 Spring單元測試

12.1 導入依賴

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>4.3.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

12.2 測試編碼

能夠免去工廠的建立過程;

能夠直接將要測試的組件注入到測試類。

@RunWith(SpringJUnit4Cla***unner.class) //由SpringJUnit4Cla***unner啓動測試
@ContextConfiguration("classpath:applicationContext.xml") //spring的配置文件位置
public class SpringTest{//當前測試類也會被歸入工廠中,因此其中屬性能夠注入

    @Autowired // 注入要測試的組件
    @Qualifier("userDAO")
    private UserDAO userDAO;

    @Test
    public void test(){
        // 測試使用userDAO
        userDAO.queryUser();
        ....
    }
}

最後

感謝你看到這裏,看完有什麼的不懂的能夠在評論區問我,以爲文章對你有幫助的話記得給我點個贊,天天都會分享java相關技術文章或行業資訊,歡迎你們關注和轉發文章!

相關文章
相關標籤/搜索