JavaSpring

1. 静态代理,JDK动态代理,CGLIB动态代理

AOP主要是基于代理实现,其基本原理就是静态代理,JDK动态代理和CGLIB动态代理。

静态代理

静态代理主要是指自己通过代码实现的代理

//抽象角色
public interface Star {
    void confer();//面谈
    void signContract();//签合同
    void bookTicket();//订票
    void sing();//唱歌
    void collectMoney();//收钱
}

//真实角色
public class RealStar implements Star {
    @Override
    public void confer() {
        System.out.println("realstar.confer()");
    }

    @Override
    public void signContract() {
        System.out.println("realstar.signContract()");
    }

    @Override
    public void bookTicket() {
        System.out.println("realstar.bookTicker()");
    }

    @Override
    public void sing() {
        System.out.println("realstar.sing()");
    }

    @Override
    public void collectMoney() {
        System.out.println("realstar.collectMoney()");
    }
}

//代理角色
public class ProxyStar implements Star {

    private Star star;//代理的对象

    public ProxyStar(Star star) {
        this.star = star;
    }

    @Override
    public void confer() {
        System.out.println("ProxyStar.confer");
    }

    @Override
    public void signContract() {
        System.out.println("ProxyStar.signContract");
    }

    @Override
    public void bookTicket() {
        System.out.println("ProxyStar.bookTicket");
    }

    @Override
    public void sing() {
        //这里需要真实角色的功能
        star.sing();
    }

    @Override
    public void collectMoney() {
        System.out.println("ProxyStar.collectMoney");
    }
}

//客户端调用
public class Client {
    public static void main(String[] args) {
        Star realStar = new RealStar();
        Star proxyStar = new ProxyStar(realStar);

        proxyStar.confer();
        proxyStar.signContract();
        proxyStar.bookTicket();
        proxyStar.sing();//此时调用真实角色
        proxyStar.collectMoney();
    }
}

JDK动态代理

//抽象角色
public interface Star {
    void confer();//面谈
    void signContract();//签合同
    void bookTicket();//订票
    void sing();//唱歌
    void collectMoney();//收钱
}

//真实角色
public class RealStar implements Star {
    @Override
    public void confer() {
        System.out.println("realstar.confer()");
    }

    @Override
    public void signContract() {
        System.out.println("realstar.signContract()");
    }

    @Override
    public void bookTicket() {
        System.out.println("realstar.bookTicker()");
    }

    @Override
    public void sing() {
        System.out.println("realstar.sing()");
    }

    @Override
    public void collectMoney() {
        System.out.println("realstar.collectMoney()");
    }
}

//动态代理处理器
public class StarHandler implements InvocationHandler {

    Star realStar;

    public StarHandler(Star realStar) {
        this.realStar = realStar;
    }

    @Override
    /**
     *
     * @param proxy 代理类对象
     * @param method 被代理对象的方法
     * @param args 方法的参数
     * @return
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object object = null;
        System.out.println("真正的方法执行前。。。");
        System.out.println("面谈,签合同,预付款,订票");
        if(method.getName().equals("sing")){
            object = method.invoke(realStar, args);//激活调用方法
        }
        System.out.println("真正的方法执行后");
        System.out.println("收尾款");
        return object;
    }
}

//客户端调用
public class Client {
    public static void main(String[] args) {
        Star realStar = new RealStar();
        StarHandler handler = new StarHandler(realStar);

        Star proxy = (Star) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Star.class},handler);

        //会调用invoke方法
        proxy.bookTicket();
        proxy.sing();

    }
}

通过Proxy生成的代理类内部会有一个handler属性,调用方法时,实际上都会传到handler中的invoke方法,将当前类,当前方法和变量传入到invoke中。

这种方法对于不同的实现类来说,可以用同一个代理类来实现代理,实现“一次编写到处代理”的效果,但是这种方法的缺点是要求被代理的类一定要是实现了某个接口的。

CGLIB库代理

CGLIB是一个字节码增强库,为AOP提供了底层支持,可以直接对实现类进行操作而非接口。使用前需要先导入cglib的包。

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CGlibAgent implements MethodInterceptor {

    private Object proxy;
    public Object getInstance(Object proxy) {
        this.proxy = proxy;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.proxy.getClass());
        //回调方法
        enhancer.setCallback(this);
        //创建代理对象
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("调用方法前...");
        //真正调用
        Object ret = methodProxy.invokeSuper(o, objects);
        System.out.println("调用方法后...");
        return ret;
    }

    public static void main(String[] args) {
        CGlibAgent cGlibAgent = new CGlibAgent();
        RealStar star = (RealStar) cGlibAgent.getInstance(new RealStar());
        star.sing();
    }
}

AOP思想的实现一般都是基于代理模式,在JAVA中一般采用JDK动态代理,但是JDK动态代理只能代理接口而不能代理类,因此,Spring AOP会通过以下方式进行切换:

  • 如果目标对象的实现类实现了接口,Spring AOP将会采用JDK动态代理来生成AOP代理类
  • 如果目标对象的实现类没有实现接口,Spring AOP将会采用CGLIB来生成AOP代理类

2. Spring AOP相关概念

AOP是面向切面编程的一种设计思想,可以分离系统的业务逻辑和系统服务(日志,安全等)

springAop概念

主要有一下几个相关术语:

  • 连接点(Joinpoint):指能够被拦截的点,在spring中,这些点指的都是方法,因为spring只支持方法类型的连接点,相当于目标对象类中的所有方法(可以被切入的点)
  • 切入点(Pointcut):指我们要对哪些连接点进行拦截和定义(已经切入的点)
  • 通知/增强(Advice):指拦截到连接点之后要做的事情,也就是通知,可以分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
  • 引介(Introduction):引介是一种特殊的通知,在不修改类代码的情况下,Introduction可在运行期为类动态的添加一些方法和Field
  • 切面(Aspect):切入点和通知(引介)的结点
  • 目标对象(Target):代理的目标对象
  • 代理(Proxy):一个类被AOP织入增强后,就产生一个结果代理类。
  • 织入(Weaving):把增强的功能添加到目标对象来创建新的代理对象的过程,spring采用动态代理织入。

通知主要有以下类型:

  • 前置通知:在目标方法之前调用,前置通知@Before注解进行标注,并可直接传入切点表达式的值,该通知在目标函数执行前执行,注意JoinPoint,是Spring提供的静态变量,通过joinPoint参数,可以获取目标对象的信息,如类名称,方法参数,方法名称等,该参数是可选的。
@Before("execution(...)")
public void before(JoinPoint joinPoint){
    System.out.println("...");
}
  • 后置通知:在目标方法之后调用,有两种,一种是如果出现异常就不调用,通过@AfterReturning注解进行标注,该函数在目标函数执行完成后执行,并可以获取到目标函数最终的返回值returnVal,当目标函数没有返回值时,returnVal将返回null,必须通过returning = “returnVal”注明参数的名称而且必须与通知函数的参数名称相同。请注意,在任何通知中这些参数都是可选的,需要使用时直接填写即可,不需要使用时,可以完成不用声明出来
@AfterReturning(value="execution(...)",returning = "returnVal")
public void AfterReturning(JoinPoint joinPoint,Object returnVal){
   System.out.println("我是后置通知...returnVal="+returnVal);
}

一种是无论是否出现异常都调用,通过@After调用:

@After("execution(...)")
public void after(JoinPoint joinPoint) {
    System.out.println("最终通知....");
}
  • 环绕通知:在目标方法之前、之后调用。通过@Around调用,环绕通知既可以在目标方法前执行也可在目标方法之后执行,更重要的是环绕通知可以控制目标方法是否指向执行,但即使如此,应该尽量以最简单的方式满足需求,在仅需在目标方法前执行时,应该采用前置通知而非环绕通知。案例代码如下第一个参数必须是ProceedingJoinPoint,通过该对象的proceed()方法来执行目标函数,proceed()的返回值就是环绕通知的返回值。同样的,ProceedingJoinPoint对象也是可以获取目标对象的信息,如类名称,方法参数,方法名称等等
@Around("execution(...)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("我是环绕通知前....");
    //执行目标函数
    Object obj= (Object) joinPoint.proceed();
    System.out.println("我是环绕通知后....");
    return obj;
}
  • 异常通知:出现异常则通知,通过@AfterThrowing调用,该通知只有在异常时才会被触发,并由throwing来声明一个接收异常信息的变量,同样异常通知也用于Joinpoint参数,需要时加上即可.
@AfterThrowing(value="execution(....)",throwing = "e")
public void afterThrowable(Throwable e){
  System.out.println("出现异常:msg="+e.getMessage());
}

使用AspectJ方式使用AOP

AspectJ是一个AOP框架,它能够对java代码进行AOP编译(一般在编译期进行),让java代码具有AspectJ的AOP功能(当然需要特殊的编译器),AspectJ是目前实现AOP框架中最成熟,功能最丰富的语言,AspectJ与java程序完全兼容,几乎是无缝关联,其实AspectJ单独就是一门语言,它需要专门的编译器(ajc编译器)。

Spring采用动态代理技术的实现原理来构建Spring AOP的内部机制(动态织入),这是与AspectJ(静态织入)最根本的区别。Spring 只是使用了与 AspectJ 5 一样的注解,但仍然没有使用 AspectJ 的编译器,底层依是动态代理技术的实现,因此并不依赖于 AspectJ 的编译器。

依赖:

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.4</version>
        </dependency>

目标对象的接口和实现类:

public interface UserService {
    void save();
    void delete();
    void update();
    void select();
}

public class UserServiceImpl implements UserService {
    @Override
    public void save() {
        System.out.println("保存用户");
    }

    @Override
    public void delete() {
        System.out.println("删除用户");
    }

    @Override
    public void update() {
        System.out.println("更新用户");
    }

    @Override
    public void select() {
        System.out.println("查询用户");
    }
}

切面类(通知类):

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

@Aspect
public class TransactionAdvice {

    //声明一个切入点,方便后面的调用
    @Pointcut("execution(* com.xm.service.*.*(..))")
    public void pointcut(){}

    //前置通知;
    // @Before("execution(* com.xm.service.UserServiceImpl.*(..))")
    @Before("TransactionAdvice.pointcut()")
    public void before(){
        System.out.println("前置通知");
    }

    //后置通知
    @After("TransactionAdvice.pointcut()")
    public void after(){
        System.out.println("后置通知(无论是否出现异常)");
    }

    //后置通知
    @AfterReturning("TransactionAdvice.pointcut()")
    public void afterReturning(){
        System.out.println("后置通知(出现异常不调用)");
    }

    //异常通知
    @AfterThrowing("TransactionAdvice.pointcut()")
    public void afterException(){
        System.out.println("异常通知");
    }

    //环绕通知
    @Around("TransactionAdvice.pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("环绕:调用目标方法之前");
        Object proceed = point.proceed();
        System.out.println("环绕:调用目标方法之后");
        return proceed;
    }
}

spring配置文件:

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

    <aop:aspectj-autoproxy/>

    <bean name="userService" class="com.xm.spring.UserServiceImpl"/>
    <bean name="myAspect" class="com.xm.spring.TransactionAdvice"/>
</beans>

测试:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AopTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        UserService userService = context.getBean(UserService.class);
        userService.save();
    }
}

Spring AOP 和AspectJ AOP 区别:

  1. Spring AOP 属于运行时增强,而AspectJ是编译时增强,Spring AOP 是基于代理(Proxying),而AspectJ基于字节码操作(Bytecode Manipulation)
  2. Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,如果切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比Spring AOP 快很多。

3. Spring IOC概念

IOC即Inversion of Control,控制反转,软件系统在没有引入IOC容器之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。软件系统在引入IOC容器之后,这种情形就完全改变了,由于IOC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。

未引入IOC容器

引入OIC容器

通过前后的对比,可以看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。

IOC是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理。IoC再其他语言也有,并非 Spirng 特有。 IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map中存放的是各种对象。

DI即Dependency Injection,是控制反转的一种实现方式。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。

所以,依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦。

Spring IoC的初始化过程:

SpringIOC初始化过程

4. Spring 事务管理

Spring事务管理的接口有三个:

  • PlatformTransactionManager:平台事务管理器
  • TransactionDefinition:事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)
  • TransactionStatus:事务运行状态

PlatformTransactionManager接口

Spring并不直接管理事务,而是提供了多种事务管理器 ,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。 Spring事务管理器的接口是: org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。

public interface PlatformTransactionManager extends TransactionManager {

    //根据指定的传播行为,返回当前活动的事务或创建一个新事务
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
            throws TransactionException;

    //使用事务目前的状态提交事务
    void commit(TransactionStatus status) throws TransactionException;

    //对执行的事务进行回调
    void rollback(TransactionStatus status) throws TransactionException;

}

TransactionDefinition接口

事务管理器接口PlatformTransactionManager通过getTransaction(TransactionDefinition definition)方法来得到一个事务,这个方法里面的参数是TransactionDefinition类,该类定义了一些基本的事务属性。

事务属性主要包含以下几个方面:

事务属性

接口内容如下:

public interface TransactionDefinition {

    //事务传播行为:
    /**
     * 支持当前事务,如果不存在,就新建一个
     */
    int PROPAGATION_REQUIRED = 0;

    /**
     * 支持当前事务,如果不存在,就不使用事务
     */
    int PROPAGATION_SUPPORTS = 1;

    /**
     * 支持当前事务,如果不存在就抛出异常
     */
    int PROPAGATION_MANDATORY = 2;

    /**
     * 如果有事务存在,挂起当前事务,创建一个新的事务
     */
    int PROPAGATION_REQUIRES_NEW = 3;

    /**
     * 以非事务方式运行,如果有事务存在,挂起当前事务
     */
    int PROPAGATION_NOT_SUPPORTED = 4;

    /**
     * 以非事务方式运行,如果有事务存在,就抛出异常
     */
    int PROPAGATION_NEVER = 5;

    /**
     * 如果有事务存在,则嵌套事务执行
     * 内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,
     * 才能引起内部事务的提交,嵌套的子事务不能单独提交。
     * 一个事务中可以包括多个保存点,每一个嵌套子事务。
     * 另外,外部事务的回滚也会导致嵌套子事务的回滚。
     */
    int PROPAGATION_NESTED = 6;

    //隔离级别: 
    /**
     * 使用数据库默认级别
     */
    int ISOLATION_DEFAULT = -1;

    /**
     * 未提交读
     */
    int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;

    /**
     * 提交读
     */
    int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;

    /**
     * 可重复读
     */
    int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;

    /**
     * 串行化
     */
    int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;


    /**
     * 超时时间,-1为不超时,单位为秒
     */
    int TIMEOUT_DEFAULT = -1;


    /**
     * 返回传播方式
     */
    int getPropagationBehavior();

    /**
     * 返回隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
     */
    int getIsolationLevel();

    /**
     * 返回超时时间
     */
    int getTimeout();

    /**
     * 设置是否是只读事务
     * 事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。
     * 所谓事务性资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。
     * 如果确定只对事务性资源进行只读操作,那么可以将事务标志为只读的,以提高事务处理的性能。
     */
    boolean isReadOnly();

    /**
     * 返回该事务的名称
     */
    @Nullable
    String getName();

}

TransactionStatus接口

TransactionStatus接口用来记录事务的状态,该接口定义了一组方法,用来获取或判断事务的相应状态信息.

PlatformTransactionManager.getTransaction(…)方法返回一个TransactionStatus对象。返回的TransactionStatus对象可能代表一个新的或已经存在的事务(如果在当前调用堆栈有一个符合条件的事务)。

public interface TransactionStatus{
    boolean isNewTransaction(); // 是否是新的事务
    boolean hasSavepoint(); // 是否有恢复点
    void setRollbackOnly();  // 设置为只回滚
    boolean isRollbackOnly(); // 是否为只回滚
    boolean isCompleted(); // 是否已完成
} 

5. Spring一些重要的模块

  • Spring Core:基础,Spring其他所有的功能都需要依赖于该类库,主要提供IoC依赖注入功能
  • Spring Aspects:该模块为与AspectJ的集成提供支持
  • Spring AOP:提供了面向切面的编程实现
  • Spring JDBC:Java数据库连接
  • Spring JMS:Java消息服务
  • Spring ORM:用于支持Hibernate等ORM工具
  • Spring Web:为创建Web应用程序提供支持
  • Spring Test:提供了对JUnit和TestNG测试的支持

6. @RestController和@Controller

@Controller其实是@Component注解的一个更加明确的注解,定义在控制层。主要作用是把普通对象实例化到Spring容器。

@Controller返回一个页面,单独使用@Controller不加@ResponseBody的话一般使用在要返回一个视图的情况,这种情况属于比较传统的Spring MVC的应用,对应于前后端不分离的情况。

@RestController返回JSON或者XML形式数据,但@RestController只返回对象,对象数据直接以JSONXML形式写入 HTTP 响应(Response)中,这种情况属于 RESTful Web服务,这也是目前日常开发所接触的最常用的情况(前后端分离)。

@Controller+@ResponseBody返回JSON或XML形式数据,相当于@RestController

7. Spring中Bean的作用域有哪些?

  • singleton:唯一bean实例,Spring中的bean默认都是单例的。
    当一个 bean 的作用域为singleton,那么Spring IoC容器中只会存在一个共享的 bean 实例,并且所有对 bean 的请求,只要id与该 bean 定义相匹配,则只会返回bean的同一实例。singleton是单例类型(对应于单例模式),就是在创建起容器时就同时自动创建了一个bean的对象,不管是否使用,但可以指定Bean节点的lazy-init=true来延迟初始化bean,这时候,只有在第一次获取bean时才会初始化bean,即第一次请求该bean时才初始化。每次获取到的对象都是同一个对象。

  • prototype:每次请求都会创建一个新的bean的实例
    当一个bean的作用域为prototype,表示一个 bean 定义对应多个对象实例。prototype作用域的 bean 会导致在每次对该 bean 请求(将其注入到另一个 bean 中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的 bean 实例。prototype是原型类型,它在创建容器的时候并没有实例化,而是当获取bean的时候才会去创建一个对象,而且每次获取到的对象都不是同一个对象。根据经验,对有状态的 bean 应该使用prototype作用域,而对无状态的 bean 则应该使用singleton作用域。

  • request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效

  • session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效
    可以根据需要放心的更改所创建实例的内部状态,而别的 HTTP session 中根据userPreferences创建的实例,将不会看到这些特定于某个 HTTP session 的状态变化。当HTTP session最终被废弃的时候,在该HTTP session作用域内的bean也会被废弃掉。

  • global-session:全局session作用域,仅仅在基于portlet的Web应用才有意义,在Spring5已经取消了。Portlet 是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像 servlet 一样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话

默认情况下,bean都是单例的,无论是否去主动获取或注入bean对象,Spring上下文一加载就会创建bean对象,无论注入多少次,拿到的都是同一个对象。可以通过在xml文件bean元素中添加scope属性或者使用@scope注解。

<bean id="notepad" class="com.xm.demo.NotePad" scope="prototype"></bean>
@Scope(“prototype”)

@Scope(scopeName = “prototype”)

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

8. Spring中单例bean的线程安全问题?

单例bean存在线程安全问题,主要是因为多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。

常见的有两种解决方法:

  1. 在Bean对象中尽量避免定义可变的成员变量
  2. 在类中定义一个ThreadLocal成员变量,将需要的可变成员保存在ThreadLocal中。(推荐方式)

9. @Component和@Bean的区别是什么?

两者的目的是一样的,都是注册bean到Spring容器中。主要区别为:

  1. 作用对象不同:@Component注解用于类,表明一个类会作为组件类,并告知Spring要为这个类创建bean;而@Bean注解作用于方法,告诉Spring这个方法将会返回一个对象,这个对象要注册为Spring应用上下文中的bean。通常方法体中包含了最终产生bean实例的逻辑。
  2. @Component通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(可以使用@ComponentScan注解定义要扫描的路径从中找出标识了需要装配的类自动装配到Spring的bean容器中);@Bean注解通常是在标有该注解的方法中定义产生这个bean@Bean告诉了Spring这是某个类的实例,当需要用它的时候需要进行注入
  3. @Bean注解比Component注解的自定义更强,而且很多地方只能通过@Bean注解,例如当引用第三方库的类需要装配到Spring容器时,无法在已经定义好的类中增加Component注解,只能通过使用@Bean来实现。

10. 将一个类声明为Spring的bean的注解有哪些?

一般使用@Autowired注解自动装配 bean,要想把类标识成可用于@Autowired注解自动装配的 bean 的类,采用以下注解可实现:

  • @Component:通用的注解,可标注任意类为 Spring 组件。如果一个Bean不知道属于哪个层,可以使用@Component注解标注。
  • @Repository: 对应持久层即 Dao 层,主要用于数据库相关操作。
  • @Service: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao层。
  • @Controller: 对应 Spring MVC 控制层,主要用户接受用户请求并调用Service 层返回数据给前端页面。

11. Spring中bean的生命周期

Initialization 和 destroy

有时需要在Bean属性值set好之后和Bean销毁之前做一些事情,比如检查Bean中某个属性是否被正常的设置好值了。Spring框架提供了多种方法让我们可以在Spring Bean的生命周期中执行initialzationpre-destroy方法。

实现InitializingBeanDisposableBean接口

这两个接口都只包含一个方法,通过实现InitializingBean接口的afterPropertiesSet()方法可以在Bean属性值设置好之后做一些操作;实现DisposableBean接口的destroy()方法可以在销毁Bean之前做一些操作。

public class GiraffeService implements InitializingBean,DisposableBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("执行InitializingBean接口的afterPropertiesSet方法");
    }
    @Override
    public void destroy() throws Exception {
        System.out.println("执行DisposableBean接口的destroy方法");
    }
}

这种方法比较简单,但是不建议使用,因为这样会将Bean的实现和Spring框架耦合在一起。

在bean的配置文件中指定init-methoddestroy-method方法

Spring允许我们在创建自己的init方法和destroy方法,只要在Bean的配置文件中指定init-methoddestroy-method的值就可以在Bean初始化时和销毁之前执行一些操作。

public class GiraffeService {
    //通过<bean>的destroy-method属性指定的销毁方法
    public void destroyMethod() throws Exception {
        System.out.println("执行配置的destroy-method");
    }
    //通过<bean>的init-method属性指定的初始化方法
    public void initMethod() throws Exception {
        System.out.println("执行配置的init-method");
    }
}

配置文件中的配置:

<bean name="giraffeService" class="com.giraffe.spring.service.GiraffeService" init-method="initMethod" destroy-method="destroyMethod">
</bean>

需要注意的是自定义的init-methodpost-method方法可以抛异常但是不能有参数。

这种方式比较推荐,因为可以自己创建方法,无需将Bean的实现直接依赖于spring的框架。

使用@PostConstruct@PreDestroy注解

除了xml配置的方式,Spring 也支持用@PostConstruct@PreDestroy注解来指定initdestroy方法。这两个注解均在javax.annotation包中。为了注解可以生效,需要在配置文件中定义org.springframework.context.annotation.CommonAnnotationBeanPostProcessorcontext:annotation-config

public class GiraffeService {
    @PostConstruct
    public void initPostConstruct(){
        System.out.println("执行PostConstruct注解标注的方法");
    }
    @PreDestroy
    public void preDestroy(){
        System.out.println("执行preDestroy注解标注的方法");
    }
}

配置文件:

<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" />

实现*Aware接口 在Bean中使用Spring框架的一些对象

有些时候需要在Bean的初始化中使用Spring框架自身的一些对象来执行一些操作,比如获取ServletContext的一些参数,获取ApplicationContext中的BeanDefinition的名字,获取Bean在容器中的名字等等。为了让Bean可以获取到框架自身的一些对象,Spring提供了一组名为*Aware的接口。

这些接口均继承于org.springframework.beans.factory.Aware标记接口,并提供一个将由 Bean 实现的set*方法,Spring通过基于setter的依赖注入方式使相应的对象可以被Bean使用。

  • ApplicationContextAware:获得ApplicationContext对象,可以用来获取所有Bean definition的名字。
  • BeanFactoryAware:获得BeanFactory对象,可以用来检测Bean的作用域。
  • BeanNameAware:获得Bean在配置文件中定义的名字。
  • ResourceLoaderAware:获得ResourceLoader对象,可以获得classpath中某个文件。
  • ServletContextAware:在一个MVC应用中可以获取ServletContext对象,可以读取context中的参数。
  • ServletConfigAware:在一个MVC应用中可以获取ServletConfig对象,可以读取config中的参数。
public class GiraffeService implements   ApplicationContextAware,
        ApplicationEventPublisherAware, BeanClassLoaderAware, BeanFactoryAware,
        BeanNameAware, EnvironmentAware, ImportAware, ResourceLoaderAware{
         @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        System.out.println("执行setBeanClassLoader,ClassLoader Name = " + classLoader.getClass().getName());
    }
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("执行setBeanFactory,setBeanFactory:: giraffe bean singleton=" +  beanFactory.isSingleton("giraffeService"));
    }
    @Override
    public void setBeanName(String s) {
        System.out.println("执行setBeanName:: Bean Name defined in context="
                + s);
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("执行setApplicationContext:: Bean Definition Names="
                + Arrays.toString(applicationContext.getBeanDefinitionNames()));
    }
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        System.out.println("执行setApplicationEventPublisher");
    }
    @Override
    public void setEnvironment(Environment environment) {
        System.out.println("执行setEnvironment");
    }
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        Resource resource = resourceLoader.getResource("classpath:spring-beans.xml");
        System.out.println("执行setResourceLoader:: Resource File Name="
                + resource.getFilename());
    }
    @Override
    public void setImportMetadata(AnnotationMetadata annotationMetadata) {
        System.out.println("执行setImportMetadata");
    }
}

BeanPostProcessor

上面的*Aware接口是针对某个实现这些接口的Bean定制初始化的过程,Spring同样可以针对容器中的所有Bean,或者某些Bean定制初始化过程,只需提供一个实现BeanPostProcessor接口的类即可。该接口中包含两个方法,postProcessBeforeInitializationpostProcessAfterInitializationpostProcessBeforeInitialization方法会在容器中的Bean初始化之前执行,postProcessAfterInitialization方法在容器中的Bean初始化之后执行。

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("执行BeanPostProcessor的postProcessBeforeInitialization方法,beanName=" + beanName);
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("执行BeanPostProcessor的postProcessAfterInitialization方法,beanName=" + beanName);
        return bean;
    }
}

要将BeanPostProcessor的Bean像其他Bean一样定义在配置文件中

<bean class="com.giraffe.spring.service.CustomerBeanPostProcessor"/>

具体过程

  • Bean容器找到配置文件中Spring Bean的定义
  • Bean容器利用Java Reflection API创建一个Bean实例
  • 如果涉及到一些属性值利用,set()方法设置一些属性值
  • 如果Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean的名字
  • 如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例
  • 如果Bean实现了BeanFactoryAware接口,调用setBeanFactory方法,传入Factory对象的实例
  • 与上面类似,如果实现了其他*.Aware接口,就调用响应的方法
  • 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessBeforeInitialization()方法
  • 如果Bean实现了InitializingBean接口,执行afterPropertiesSet()方法
  • 如果Bean在配置文件中的定义包含了init-method属性,执行指定的方法
  • 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessAfterInitialization()方法。
  • 当要销毁Bean的时候,如果bean实现了DisposableBean接口,执行destroy()方法
  • 当要销毁Bean的时候,如果Bean在配置文件中的定义包含了destroy-method属性,执行指定的方法

spring Bean的生命周期

很多时候并不会真的去实现上面所说的那些接口,下面去掉这些接口,针对bean的单例和非单例来描述bean的生命周期:

单例管理的对象

scope="singleton",即默认情况下,会在启动容器时(即实例化容器时)时实例化。但我们可以指定 Bean 节点的 lazy-init="true" 来延迟初始化 bean,这时候,只有在第一次获取bean时才会初始化 bean,即第一次请求该bean时才初始化。如下配置:

<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" lazy-init="true"/> 

如果想对所有的默认单例 bean 都应用延迟初始化,可以在根节点 beans 设置 default-lazy-init 属性为true。

非单例管理的对象

scope="prototype" 时,容器也会延迟初始化 bean,Spring 读取 xml 文件的时候,并不会立刻创建对象,而是在第一次请求该 bean 时才初始化(如调用getBean方法时)。在第一次请求每一个prototypebean 时,Spring容器都会调用其构造器创建这个对象,然后调用 init-method 属性值中所指定的方法。对象销毁的时候,当容器关闭时,destroy 方法不会被调用。对于prototype 作用域的 bean,有一点非常重要,那就是 Spring 不能对一个prototype bean 的整个生命周期负责:容器在初始化、配置、装饰或者是装配完一个prototype实例后,将它交给客户端,随后就对该prototype实例不闻不问了。

不管何种作用域,容器都会调用所有对象的初始化生命周期回调方法。但对 prototype 而言,任何配置好的析构生命周期回调方法都将不会被调用。清除 prototype 作用域的对象并释放任何 prototype bean 所持有的昂贵资源,都是客户端代码的职责(让 Spring 容器释放被 prototype 作用域 bean 占用资源的一种可行方式是,通过使用 bean 的后置处理器,该处理器持有要被清除的 bean 的引用)。谈及 prototype 作用域的 bean 时,在某些方面可以将 Spring 容器的角色看作是 Java new操作的替代者,任何迟于该时间点的生命周期事宜都得交由客户端来处理。

Spring 容器可以管理 singleton 作用域下 bean 的生命周期,在此作用域下,Spring 能够精确地知道 bean 何时被创建,何时初始化完成,以及何时被销毁。而对于 prototype 作用域的 bean,Spring 只负责创建,当容器创建了 bean 的实例后,bean 的实例就交给了客户端的代码管理,Spring 容器将不再跟踪其生命周期,并且不会管理那些被配置成 prototype 作用域的 bean 的生命周期。

12. Spring MVC工作原理

Spring MVC工作原理

  1. 客户端(浏览器)发送的请求都会被前端控制器(中央控制器)DispatcherServlet捕获
  2. 中央控制器(DispatcherServlet)根据请求信息调用处理器映射器HandleMapping找到对应的处理器(Handler)返回给中央控制器DispatcherServletHandlerMapping接口处理请求的映射HandlerMapping接口的实现类:
  • SimpleUrlHandlerMapping类通过配置文件把URL映射到Controller类
  • DefaultAnnotationHandlerMapping类通过注解把URL映射到Controller类
  1. 中央控制器DispatcherServlet根据返回的处理器Handler调用处理器适配器HandleAdapter,处理器适配器HandlerAdapter经过适配调用具体的Controller,并将得到的ModelAndView返回给中央控制器DispatcherServletModel是返回的数据对象,View是个逻辑上的ViewHandlerAdapter接口的主要实现类是:
  • AnnotationMethodHandlerAdapter:通过注解,把请求URL映射到Controller类的方法上。
  1. 中央控制器将DispatcherServlet将结果传递给视图解析器ViewResloverViewReslover主要实现类是:
  • UrlBasedViewResolver类:通过配置文件,把一个视图名交给到一个View来处理。
  1. 视图解析器ViewReslover根据逻辑View查找实际的View
  2. 中央控制器DispatcherServlet把返回的Model传给view(视图渲染)
  3. View返回给请求者(浏览器)

DispatcherServlet源码

package org.springframework.web.servlet;
 
@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {
 
    public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
    public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";
    public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";
    public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";
    public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";
    public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver";
    public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator";
    public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";
    public static final String FLASH_MAP_MANAGER_BEAN_NAME = "flashMapManager";
    public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.class.getName() + ".CONTEXT";
    public static final String LOCALE_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".LOCALE_RESOLVER";
    public static final String THEME_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_RESOLVER";
    public static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_SOURCE";
    public static final String INPUT_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + ".INPUT_FLASH_MAP";
    public static final String OUTPUT_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + ".OUTPUT_FLASH_MAP";
    public static final String FLASH_MAP_MANAGER_ATTRIBUTE = DispatcherServlet.class.getName() + ".FLASH_MAP_MANAGER";
    public static final String EXCEPTION_ATTRIBUTE = DispatcherServlet.class.getName() + ".EXCEPTION";
    public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";
    private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
    protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);
    private static final Properties defaultStrategies;
    static {
        try {
            ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        }
        catch (IOException ex) {
            throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
        }
    }
 
    /** Detect all HandlerMappings or just expect "handlerMapping" bean? */
    private boolean detectAllHandlerMappings = true;
 
    /** Detect all HandlerAdapters or just expect "handlerAdapter" bean? */
    private boolean detectAllHandlerAdapters = true;
 
    /** Detect all HandlerExceptionResolvers or just expect "handlerExceptionResolver" bean? */
    private boolean detectAllHandlerExceptionResolvers = true;
 
    /** Detect all ViewResolvers or just expect "viewResolver" bean? */
    private boolean detectAllViewResolvers = true;
 
    /** Throw a NoHandlerFoundException if no Handler was found to process this request? **/
    private boolean throwExceptionIfNoHandlerFound = false;
 
    /** Perform cleanup of request attributes after include request? */
    private boolean cleanupAfterInclude = true;
 
    /** MultipartResolver used by this servlet */
    private MultipartResolver multipartResolver;
 
    /** LocaleResolver used by this servlet */
    private LocaleResolver localeResolver;
 
    /** ThemeResolver used by this servlet */
    private ThemeResolver themeResolver;
 
    /** List of HandlerMappings used by this servlet */
    private List<HandlerMapping> handlerMappings;
 
    /** List of HandlerAdapters used by this servlet */
    private List<HandlerAdapter> handlerAdapters;
 
    /** List of HandlerExceptionResolvers used by this servlet */
    private List<HandlerExceptionResolver> handlerExceptionResolvers;
 
    /** RequestToViewNameTranslator used by this servlet */
    private RequestToViewNameTranslator viewNameTranslator;
 
    private FlashMapManager flashMapManager;
 
    /** List of ViewResolvers used by this servlet */
    private List<ViewResolver> viewResolvers;
 
    public DispatcherServlet() {
        super();
    }
 
    public DispatcherServlet(WebApplicationContext webApplicationContext) {
        super(webApplicationContext);
    }
    @Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }
 
    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }
}

DispatcherServlet类中的属性beans有:

  • HandlerMapping:用于handlers映射请求和一系列对于拦截器的前处理和后处理,大部分用@Controller注解
  • HandlerAdapter:帮助DispatcherServlet处理映射请求处理程序的适配器,而不用考虑实际调用的是哪个处理程序
  • ViewResolver:根据实际配置解析实际的View类型
  • ThemeResolver:解决Web应用程序可以使用的主题,例如提供个性化布局
  • MultipartResolver:解析多部分请求,以支持从HTML表单上传文件
  • FlashMapManager:存储并检索可用于将一个请求属性传递到另一个请求的inputoutputFlashMap,通常用于重定向

13. Spring框架中用到了哪些设计模式

工厂设计模式

Spring使用工厂模式可以通过BeanFactory或者ApplicationContext创建bean对象。

两者对比/BeanFactoryApplicationContext 的区别

  • BeanFactory:延迟注入(使用到某个bean的时候才会注入),相比于ApplicationContext来说会占用更少的内存,程序启动速度更快
  • ApplicationContext:容器启动的时候,不管用没用到,一次性创建所有bean。BeanFactory仅提供了最基本的依赖注入支持,ApplicationContext扩展了BeanFactory,除了有BeanFactory的功能,还有额外的更多功能,所以一般使用ApplicationContext

ApplicationContext的三个实现类:

  1. ClassPathXmlApplication:把上下文文件当成类路径资源
  2. FileSystemXmlApplication:从文件系统中的XML文件载入上下文定义信息
  3. XmlWebApplicationContext:从Web系统中的XML文件载入上下文定义信息
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class App {
    public static void main(String[] args) {
        ApplicationContext context = new FileSystemXmlApplicationContext(
                "C:/work/IOC Containers/springframework.applicationcontext/src/main/resources/bean-factory-config.xml");

        HelloApplicationContext obj = (HelloApplicationContext) context.getBean("helloApplicationContext");
        obj.getMsg();
    }
}

单例设计模式

Spring 中 bean 的默认作用域就是singleton(单例)的。

Spring实现单例的方式:

  • xml:bean id="userService" class="com.xm.service.UserService" scope="singleton" />
  • 注解:@Scope(value="singleton")

Spring通过ConcurrentHashMap实现单例注册表的特殊方式实现单例模式。核心代码如下:

// 通过 ConcurrentHashMap(线程安全) 实现单例注册表
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "'beanName' must not be null");
        synchronized (this.singletonObjects) {
            // 检查缓存中是否存在实例  
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                //...省略了很多代码
                try {
                    singletonObject = singletonFactory.getObject();
                }
                //...省略了很多代码
                // 如果实例对象在不存在,我们注册到单例注册表中。
                addSingleton(beanName, singletonObject);
            }
            return (singletonObject != NULL_OBJECT ? singletonObject : null);
        }
    }
    //将对象添加到单例注册表
    protected void addSingleton(String beanName, Object singletonObject) {
            synchronized (this.singletonObjects) {
                this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));

            }
        }
}

代理设计模式

主要运用在Spring AOP中

模版方法

Spring 中jdbcTemplatehibernateTemplate等以Template结尾的对数据库操作的类,它们就使用到了模板模式。一般情况下,我们都是使用继承的方式来实现模板模式,但是 Spring 并没有使用这种方式,而是使用Callback模式与模板方法模式配合,既达到了代码复用的效果,同时增加了灵活性。

观察者模式

Spring 事件驱动模型就是观察者模式很经典的一个应用。Spring 事件驱动模型非常有用,在很多场景都可以解耦我们的代码。比如我们每次添加商品的时候都需要重新更新商品索引,这个时候就可以利用观察者模式来解决这个问题。

Spring事件驱动模型中的三种角色

事件角色

ApplicationEventorg.springframework.context包下)充当事件的角色,这是一个抽象类,它继承了java.util.EventObject并实现了java.io.Serializable接口。

Spring 中默认存在以下事件,他们都是对ApplicationContextEvent的实现(继承自ApplicationEvent

  • ContextStartedEventApplicationContext启动后触发的事件
  • ContextStoppedEventApplicationContext停止后触发的事件
  • ContextRefreshedEventApplicationContext初始化或者刷新完成后触发的事件
  • ContextClosedEventApplicationContext关闭后触发的事件

事件监听者角色

ApplicationListener充当了事件监听者角色,它是一个接口,里面只定义了一个onApplicationEvent()方法来处理ApplicationEvent

ApplicationListener接口类源码如下,可以看出接口定义看出接口中的事件只要实现了ApplicationEvent就可以了。所以,在 Spring中只要实现ApplicationListener接口实现onApplicationEvent()方法即可完成监听事件

package org.springframework.context;
import java.util.EventListener;
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E var1);
}

事件发布者角色

ApplicationEventPublisher充当了事件的发布者,它也是一个接口:

@FunctionalInterface
public interface ApplicationEventPublisher {
    default void publishEvent(ApplicationEvent event) {
        this.publishEvent((Object)event);
    }

    void publishEvent(Object var1);
}

ApplicationEventPublisher接口的publishEvent()这个方法在AbstractApplicationContext类中被实现,实际上事件真正是通过ApplicationEventMuticaster来广播出去的。

流程总结

  1. 定义一个事件:实现一个继承自ApplicationEvent,并且写相应的构造函数;
  2. 定义一个事件监听者:实现ApplicationListener接口,重写onApplicationEvent()方法
  3. 使用事件发布者发布消息:可以通过ApplicationEventPublisherpublishEvent()方法发布消息
// 定义一个事件,继承自ApplicationEvent并且写相应的构造函数
public class DemoEvent extends ApplicationEvent {

    private static final long serialVersionUID = 6529995111447704468L;

    private String message;

    /**
     * @param source 最初发生事件或者与事件相关联的对象
     */
    public DemoEvent(Object source,String message) {
        super(source);
        this.message = message;
    }

    public String getMessage(){
        return this.message;
    }
}

// 定义一个事件监听者,实现ApplicationListener接口,重写 onApplicationEvent() 方法;
@Component
public class DemoListener implements ApplicationListener<DemoEvent> {

    @Override
    public void onApplicationEvent(DemoEvent event) {
        String msg = event.getMessage();
        System.out.println("接收到消息:"+msg);
    }
}

// 发布事件,可以通过ApplicationEventPublisher  的 publishEvent() 方法发布消息。
@Component
public class DemoPublisher  {
    @Autowired
    ApplicationContext applicationContext;

    public void publish(String message) {
        //发布事件
        applicationContext.publishEvent(new DemoEvent(this,message));
    }
}

当调用DemoPublisherpublish()方法的时候,比如demoPublisher.publish("你好"),控制台就会打印出:“接收到的信息是:你好” 。

适配器模式

Spring AOP中的适配器模式

Spring AOP的增强和通知(Advice)使用到了适配器模式,与之相关的接口是AdvisorAdapter,Advice常用的类型有:BeforeAdvice(目标方法调用前,前置通知)、AfterAdvice(目标方法调用后,后置通知)、AfterReturningAdvice(目标方法执行结束后,return之前)等等。每个类型Advice(通知)都有对应的拦截器:MethodBeforeAdviceInterceptorAfterReturningAdviceAdapterAfterReturningAdviceInterceptor

Spring预定义的通知要通过对应的适配器,适配成MethodInterceptor接口(方法拦截器)类型的对象(如:MethodBeforeAdviceInterceptor负责适配MethodBeforeAdvice)。

Spring MVC中的适配器模式

在Spring MVC中,DispatcherServlet根据请求信息调用HandlerMapping,解析请求对应的Handler,解析到对应的Handler(也就是Controller控制器)后,开始由HandlerAdapter适配器处理。

HandlerAdapter作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller作为需要适配的类。

为什么要在Spring MVC中使用适配器模式?

Spring MVC中的Controller种类众多,不同类型的Controller通过不同的方法对请求进行处理。如果不利用适配器模式的话,DispatcherServlet直接获取对应类型的Controller,需要自行判断,像下面代码一样:

if(mappedHandler.getHandler() instanceof MultiActionController){  
   ((MultiActionController)mappedHandler.getHandler()).xxx  
}else if(mappedHandler.getHandler() instanceof XXX){  
    ...  
}else if(...){  
   ...  
} 

假如再增加一个 Controller类型就要在上面代码中再加入一行 判断语句,这种形式就使得程序难以维护,也违反了设计模式中的开闭原则 – 对扩展开放,对修改关闭。

装饰者模式

Spring 中配置DataSource的时候,DataSource可能是不同的数据库和数据源。我们能否根据客户的需求在少修改原有类的代码下动态切换不同的数据源?这个时候就要用到装饰者模式。Spring 中用到的包装器模式在类名上含有Wrapper或者Decorator。这些类基本上都是动态地给一个对象添加一些额外的职责。

14. @Transactional(rollbackFor = Exception.class)注解了解吗?

Exception分为运行时异常RuntimeException和非运行时异常。事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。

@Transactional注解作用于类上时,该类的所有public方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。

@Transactional注解中如果不配置rollbackFor属性,那么事务只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚。

15. Spring 自动装配(auto wire)的方式有哪些?

Spring 提供了5种自动装配的方式:

  1. no:默认的方式是不进行自动装配,通过手工设置ref属性来进行装配bean
<bean id="person" class="com.myapp.core.autowire.Person">
   <property name="book" ref="book" />
</bean>
  
<bean  id="book" class="com.myapp.core.autowire.Book"></bean>
  1. byName:通过参数名自动装配,如果一个 beanname 和另外一个 beannameproperty 相同,就自动装配。
<bean id="person" class="com.myapp.core.autowire.Person" autowire="byName">
</bean>

<bean  id="book" class="com.myapp.core.autowire.Book"></bean>
  1. byType:通过参数的数据类型自动装配,如果一个bean的数据类型和另外一个beanproperty属性的数据类型兼容,就自动装配。
<bean id="person" class="com.myapp.core.autowire.Person"  autowire="byType"/>
<bean  id="book" class="com.myapp.core.autowire.Book"></bean>
  1. construct:构造方法中的参数通过byType的形式,自动装配。
<bean id="person" class="com.myapp.core.autowire.Person"  autowire="constructor"/>
  
<bean  id="book" class="com.myapp.core.autowire.Book"></bean>
  1. autodetect:如果有默认的构造方法,通过construct的方式自动装配,否则使用byType的方式自动装配。
<bean id="person" class="com.myapp.core.autowire.Person"   autowire="autodetect" />
<bean  id="book" class="com.myapp.core.autowire.Book"></bean>

自动装配不容易看出bean之间的引用关系,增加了阅读的复杂度,一般还是采用默认的方式手工进行配置,或者采用annotation的方式进行配置。

16. 依赖注入的方式有哪些?

  • Set注入:通过声明setter方法,在xml文件配置类中的property值完成注入
  • 接口注入:对类中的成员接口成员变量用@Autowired修饰,而@Componnent修饰的是接口的实现类
  • 构造器的注入:
<bean id="cdPlayer" class="com.xm.demo.soundsystem.CDPlayer">
    <!--构造函数注入,通过ref指定具体注入的对象-->
    <constructor-arg ref="compactDisc1"/>
</bean>
  • 使用Autowired注解注入共同点

17. 使用 autowired 和 resource 区别?

@Autowired 为 Spring 提供的注解,需要导入包 org.springframework.beans.factory.annotation.Autowired;只按照 byType 注入。

在装配的位置使用 @Resource(name="id"),代替 @Autowired@Qualifier(“name”),其作用相当于两个的结果

@Autowired 注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许 null 值,可以设置它的 required 属性为 false 。如果我们想使用按照名称(byName)来装配,可以结合 @Qualifier 注解一起使用。

@Resource 默认按照 ByName 自动注入,由J2EE提供,需要导入包 javax.annotation.Resource@Resource 有两个重要的属性:nametype,而 Spring 将 @Resource 注解的 name 属性解析为 bean 的名字,而 type 属性则解析为 bean 的类型。所以,如果使用 name 属性,则使用 byName 的自动注入策略,而使用 type 属性时则使用 byType 自动注入策略。如果既不制定 name 也不制定 type 属性,这时将通过反射机制使用 byName 自动注入策略。

18. Spring MVC 运行机制以及运行的流程

  1. 用户发送请求时会先从DispatcherServletdoService方法开始,在该方法中会将 ApplicationContext,localeResolver,themeResolver 等对象添加到 request 中,紧接着就调用 doDispatch 方法。
  2. 进入该方法后首先会检查该请求是否是文件上传的请求(校验的规则是是否是 post 并且 contentType 是否以 multipart/ 为前缀),即调用 checkMultipart 方法,是的话就将 request 包装成 MultipartHttpServletRequet
  3. 然后调用 getHandler 方法来匹配每个 HandlerMapping 对象,如果匹配成功会返回这个 Handle 的处理链 HandlerExecutionChain 对象,在获取该对象的内部其实也获取我们自定定义的拦截器,并执行了其中的方法。
  4. 执行拦截器的 preHandle 方法,如果返回 false 执行 afterCompletion 方法并立即返回。
  5. 通过上述获取到了 HandlerExecutionChain 对象,通过该对象的 getHandler() 方法获得一个 object 通过 HandlerAdapter 进行封装得到 HandlerAdapter 对象。
  6. 该对象调用 handle 方法来执行 Controller 中的方法,该对象如果返回一个 ModelAndViewDispatcherServlet
  7. DispatcherServlet 借助 ViewResolver 完成逻辑视图名到真实视图对象的解析,得到 ViewDispatcherServlet 使用这个 ViewModelAndView 中的模型数据进行视图渲染。

19. Spring IOC 的加载过程

首先是容器的初始化

启动容器,实际上指的就是实例化ApplicationContext的这个动作。只是在不同情况下可能有不同的表现形式。

ApplicationContext context = new ClassPathXmlApplicationContext(applicationContext.xml”);

ClassPathXmlApplicationContext 的容器初始化我们大致分为下面几步:

  1. BeanDefinition 的 Resource 定位,这里的 Resource 定位是通过继承 ResourceLoader 获得的,ResourceLoader 代表了加载资源的一种方式,正是策略模式的实现
  2. Resource中解析、载入 BeanDefinition
  3. BeanDefinition 在IoC 容器中的注册

从构造函数入手:

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
        throws BeansException {

    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
        refresh();
    }
}

参数configLocations就是 XML 配置文件的 classpath

setConfigLocations(configLocations)这里就是把一些带有占位符的地址解析成实际的地址。

再之后就是 refresh,容器的初始化就在这里开始,这里取名为refresh,是因为容器启动之后,再调用 refresh()会刷新IoC 容器。

refresh 的工作流程大概有以下几点:

  • prepareRefresh() 创建容器前的准备工作
  • obtainFreshBeanFactory() 创建 BeanFactory
  • prepareBeanFactory(beanFactory) 对BeanFactory进行一些特征的设置工作
  • finishBeanFactoryInitialization(beanFactory) 初始化所有的 singleton beans(DI的入口)

prepareRefresh() 创建容器前的准备工作

protected void prepareRefresh() {
   // 记录启动时间,
   // 将 active 属性设置为 true,closed 属性设置为 false,它们都是 AtomicBoolean 类型
   this.startupDate = System.currentTimeMillis();
   this.closed.set(false);
   this.active.set(true);

   if (logger.isInfoEnabled()) {
      logger.info("Refreshing " + this);
   }

   // Initialize any placeholder property sources in the context environment
   initPropertySources();

   // 校验 xml 配置文件
   getEnvironment().validateRequiredProperties();

   this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();
}

obtainFreshBeanFactory() 创建 Bean 容器,加载并注册 Bean

这是 IOC 初始化里面最重要的部分,在这步完成以后,Bean 并没有完成初始化,实际的实例并没有被创建,只是完成了 BeanFactory 的实例化

    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        // 关闭旧的 BeanFactory (如果有),创建新的 BeanFactory,加载 Bean 定义、注册 Bean 等等,其实现看下一段代码
        refreshBeanFactory();
        // 返回上一步刚刚创建的BeanFactory
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (logger.isDebugEnabled()) {
            logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
        }
        return beanFactory;
    }

在关闭旧的 BeanFactory 并创建新的 BeanFactory 过程当中,会设置 BeanFactory 的两个重要属性,一个是是否允许 Bean 覆盖,是否允许循环使用,另外一个是加载 BeanDefinitionBeanFactory

    protected final void refreshBeanFactory() throws BeansException {
        // 如果 ApplicationContext 已经加载过 BeanFactory,销毁所有的Bean,关闭BeanFactory
        // 注意点:应用中BeanFactory是可以有多个的,这里可不是说全局是否有BeanFactory
        // 而是说当前的ApplicationContext有没有BeanFactory!
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            // 初始化一个 DefaultListableBeanFactory
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            // 用于 BeanFactory 的序列化,一般人应该用不到吧...
            beanFactory.setSerializationId(getId());
            // 下面是两个重点方法
            // 设置 BeanFactory 的两个重要属性
            // 是否允许 Bean 覆盖、是否允许循环引用 TODO 2.1
            customizeBeanFactory(beanFactory);
            
            // 加载BeanDefinition到BeanFactory  单独拉出来讲
            loadBeanDefinitions(beanFactory);
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

设置是否允许覆盖和循环使用

protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
    if (this.allowBeanDefinitionOverriding != null) {
        beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    if (this.allowCircularReferences != null) {
        beanFactory.setAllowCircularReferences(this.allowCircularReferences);
    }
}

BeanDefinition 的覆盖问题可能会有开发者碰到这个坑,就是在配置文件中定义 bean 时使用了相同的 idname,默认情况下,allowBeanDefinitionOverriding 属性为 null(Boolean类型),如果在同一配置文件中重复了,会抛错,但是如果不是同一配置文件中,会发生覆盖。

循环引用也很好理解:A 依赖 B,而 B 依赖 A。或 A 依赖 B,B 依赖 C,而 C 依赖 A。

默认情况下,Spring 允许循环依赖,当然如果你在 A 的构造方法中依赖 B,在 B 的构造方法中依赖 A 是不行的。

加载 BeanDefinition 到 BeanFactory

这里载入分为两大步:

  1. 一就是通过调用 XML 的解析器获取到 document 对象,完成通用 XML 解析;
  2. 二就是按照 Spring 的 Bean 规则进行解析。Spring 的 Bean 规则进行解析这个过程是 BeanDefinitionDocumentReader 来实现的,里面包含了各种Spring Bean 定义规则的处理。

prepareBeanFactory(beanFactory) 对 BeanFactory 进行一些特征设置工作

主要包括设置:当前 ApplicationContext 的类加载器、添加一个ApplicationContextAwareProcessor,主要针对实现了 Aware 接口的Bean等等

finishBeanFactoryInitialization(beanFactory) 实例化所有单例

这里会负责初始化所有的 singleton beans

Spring 会在这个阶段完成所有的 singleton beans 的实例化。

到目前为止,应该说 BeanFactory 已经创建完成,并且所有的实现了 BeanFactoryPostProcessor 接口的 Bean 都已经初始化并且其中的 postProcessBeanFactory(factory) 方法已经得到回调执行了。而且 Spring 已经“手动”注册了一些特殊的 Bean,如 environmentsystemProperties 等。

剩下的就是初始化 singleton beans 了,我们知道它们是单例的,如果没有设置懒加载,那么 Spring 会在接下来初始化所有的 singleton beans

第二大步就是依赖注入的过程

容器初始化中,finishBeanFactoryInitialization(beanFactory)实例化所有单例,调用了getBean() 方法来做 singleton bean 的实例化操作。这就是Spring IoC 依赖注入的入口。

    @Override
    public Object getBean(String name) throws BeansException {
        return doGetBean(name, null, null, false);
    }

doGetBean()

这一步主要完成以下功能:

  • beanName 解析转换
  • 检测手动注册 Bean
  • 双亲容器检测
  • 依赖初始化(递归)
  • 创建Bean createBean()

createBean()

  1. 开始是单例的话要先清除缓存;
  2. 实例化 bean,将 BeanDefinition 转换为 BeanWrapper
  3. 使用 MergedBeanDefinitionPostProcessorAutowired注解就是通过此方法实现类型的预解析;
  4. 解决循环依赖问题;
  5. 填充属性,将属性填充到 bean 实例中;
  6. 注册 DisposableBean
  7. 创建完成并返回

20. Spring,Spring MVC,Spring Boot 三者的联系和区别

Spring整体架构

总的来说 Spring 是使用基本的 JavaBean 代替 EJB,通过容器管理 JavaBean 的配置和声明周期,在此基础上实现了 AOP、IOC 的 Spring 核心功能,其他 web 框架组件在 AOP、IOC 的基础上工作,将 JavaBean 交给 Spring 来管理。简单来说,Spring 是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。

Spring MVC就是一个MVC框架,其实都是属于Spring,Spring MVC 需要有 Spring 的架包作为支撑才能跑起来。

Spring 可以说是一个管理 bean 的容器,也可以说是包括很多开源项目的总称,spring mvc 是其中一个开源项目,所以简单走个流程的话,http 请求一到,由容器(如:tomact)解析 http 搞成一个 request,通过映射关系(路径,方法,参数啊)被 spring mvc 一个分发器去找到可以处理这个请求的bean,那 tomcat 里面就由 spring 管理 bean 的一个池子(bean容器)里面找到,处理完了就把响应返回回去。

Spring Boot 不是一门新技术。从本质上来说,Spring Boot 就是 Spring,它做了一些对 Spring Bean 的默认配置。

参考内容

主要参考以来两篇博客以及相关博客推荐,因找的博客比较多,没注意记录,最后好多忘了在哪2333,如果有侵权,请及时联系我,非常抱歉。
https://github.com/Snailclimb/JavaGuide
https://github.com/CyC2018/CS-Notes