关于java:面试阿里美团京东都会被问到的Spring-从基础到源码帮你全搞定

3次阅读

共计 14852 个字符,预计需要花费 38 分钟才能阅读完成。

1 前言

  1. Spring 是一个轻量级开源框架,它是为了解决企业应用开发的复杂性而创立的。框架的次要劣势之一就是其分层架构,分层架构容许使用者抉择应用哪一个组件,同时为 J2EE 利用程序开发提供集成的框架。
  2. Spring 是泛滥优良设计模式的组合(工厂、单例、代理、适配器、包装器、观察者、模板、策略)。
  3. Spring 的用处不仅限于服务器端的开发。从简略性、可测试性和松耦合的角度而言,任何 Java 利用都能够从 Spring 中受害。
  4. Spring 并未代替现有框架产品,而是将泛滥框架进行有机整合,简化企业级开发,俗称 ” 胶水框架 ”。

2 Spring 架构组成

Spring 架构由诸多模块组成,可分类为

  • 核心技术:依赖注入,事件,资源,i18n,验证,数据绑定,类型转换,SpEL,AOP。
  • 测试:模仿对象,TestContext 框架,Spring MVC 测试,WebTestClient。
  • 数据拜访:事务,DAO 反对,JDBC,ORM,封送 XML。
  • Spring MVC 和 Spring WebFlux Web 框架。
  • 集成:近程解决,JMS,JCA,JMX,电子邮件,工作,调度,缓存。
  • 语言:Kotlin,Groovy,动静语言。
  • List item

Spring 架构组成如下图

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 实例,这个实例名字叫 mc

<!-- 配置实例(id:“惟一标识”class="须要被创立的指标对象全限定名")-->
<bean id="mc" class="com.qf.spring.part1.factory.MyClass" />

测试代码

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 容器创立。(变被动为被动,即反转)。管制反转解决了具备依赖关系的组件之间的强耦合,使得我的项目状态更加持重。

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 概念

将外围性能与辅助性能(事务、日志、性能监控代码)拆散,达到外围业务性能更纯正、辅助业务性能可复用。

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>

<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(SpringJUnit4ClassRunner.class) // 由 SpringJUnit4ClassRunner 启动测试
@ContextConfiguration(“classpath:applicationContext.xml”) //spring 的配置文件地位
public class SpringTest{// 以后测试类也会被纳入工厂中,所以其中属性能够注入

@Autowired // 注入要测试的组件
@Qualifier("userDAO")
private UserDAO userDAO;

@Test
public void test(){
    // 测试应用 userDAO
    userDAO.queryUser();
    ....
}

}

## 最初
感激你看到这里,看完有什么的不懂的能够在评论区问我,感觉文章对你有帮忙的话记得给我点个赞,每天都会分享 java 相干技术文章或行业资讯,欢送大家关注和转发文章!
正文完
 0