Spring IOC 和 AOP

Spring 的 IOC 容器是 Spring 的核心,Spring AOP 是 Spring 框架的重要组成部分。

IOC

1. IOC 是什么?

  • IOC (Inversion Of Control)控制反转,包含了两个方面:控制和反转
    • 控制:当前对象对内部成员的控制权。
    • 反转:这种控制权不由当前对象管理了,由其他(类,第三方容器)来管理。
  • IOC 的意思是控件反转也就是由容器控制程序之间的关系,这也是 Spring 的优点所在,把控件权交给了外部容器,之前的写法,由程序代码直接操控,而现在控制权由应用代码中转到了外部容器,控制权的转移是所谓反转。换句话说之前用 new 的方式获取对象,现在由 Spring 给你,至于怎么给你就是 DI 了。
  • IOC 容器是 Spring 用来实现 IOC 的载体, IOC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。

2. IOC 实现原理

  • 创建 xml 配置文件,配置要创建的对象类。
  • 通过反射创建实例。
  • 获取需要注入的接口实现类并将其赋值给该接口。

    3. 优点

  • 解耦合,开发更方便组织分工

  • 高层不依赖于底层(依赖倒置)
  • 让应用更容易测试
  • 因为把对象生成放在了 XML 里定义,所以当我们需要换一个实现子类将会变成很简单(一般这样的对象都是实现于某种接口的),只要修改 XML 就可以了,这样我们甚至可以实现对象的热插拨

AOP

1. AOP是什么?

  • AOP(Aspect Oriented Programming )称为面向切面编程,扩展功能不是修改源代码实现,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2 的拦截器设计就是基于 AOP 的思想,是个比较经典的例子。
  • 面向切面编程(aop)是对面向对象编程(oop)的补充。
  • 面向切面编程提供声明式事务管理。
  • AOP就是典型的代理模式的体现。

2. AOP 实现原理

  • 动态代理(利用反射和动态编译将代理模式变成动态的)
  • JDK 的动态代理
    • JDK 内置的 Proxy 动态代理可以在运行时动态生成字节码,而没必要针对每个类编写代理类
    • JDK Proxy 返回动态代理类,是目标类所实现接口的另一个实现版本,它实现了对目标类的代理(如同 UserDAOProxy 与 UserDAOImp 的关系)
  • cglib动态代理
    • CGLibProxy 返回的动态代理类,则是目标代理类的一个子类(代理类扩展了 UserDaoImpl 类)
    • cglib 继承被代理的类,重写方法,织入通知,动态生成字节码并运行
  • 两种实现的区别
    • JDK 动态代理只能对实现了接口的类生成代理,而不能针对类
    • cglib 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法因为是继承,所以该类或方法最好不要声明成 final
    • JDK 代理是不需要以来第三方的库,只要 JDK 环境就可以进行代理
    • cglib 必须依赖于 cglib 的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法,是一种继承

3. 优点

  • 各个步骤之间的良好隔离性
  • 源代码无关性
  • 松耦合
  • 易扩展
  • 代码复用

依赖注入

DI 是什么?

DI(Dependency Injection) ,即依赖注入,是 Spring 中实现 IOC 的方式。所谓依赖注入,就是把底层类作为参数传入上层类,实现上层类对下层类的控制。DI 依赖注入,向类里面属性注入值 ,依赖注入不能单独存在,需要在 IOC 基础上完成操作。

3种注入方式(使用注解)

  • field 注入,简单易用,但可能会出现依赖循环,且无法适用于 IOC 容器以外的环境。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Controller
    public class FooController {
    @Autowired
    //@Inject
    private FooService fooService;

    //简单的使用例子,下同
    public List<Foo> listFoo() {
    return fooService.list();
    }
    }
  • 使用 setter 方法注入,比构造器注入轻量,另外 setter 方式能让类在之后重新配置或重新注入。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Controller
    public class FooController {

    private FooService fooService;

    //使用方式上同,略
    @Autowired
    public void setFooService(FooService fooService) {
    this.fooService = fooService;
    }
    }
  • 使用构造器注入,Spring 官方建议使用构造器注入,因为其能保证组件不可变,并且确保需要的依赖不为空。此外,构造器注入的依赖总是能够在返回客户端(组件)代码的时候保证完全初始化的状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Controller
    public class FooController {

    private final FooService fooService;

    @Autowired
    public FooController(FooService fooService) {
    this.fooService = fooService;
    }

    //使用方式上同,略
    }

Spring IOC 初始化过程

  1. Resource 资源定位。这个 Resouce 指的是 BeanDefinition 的资源定位。这个过程就是容器找数据的过程,就像水桶装水需要先找到水一样。
  2. BeanDefinition 的载入过程。这个载入过程是把用户定义好的 Bean 表示成 IOC 容器内部的数据结构,而这个容器内部的数据结构就是 BeanDefition。

  3. 向 IOC 容器注册这些 BeanDefinition 的过程,这个过程就是将前面的 BeanDefition 保存到 HashMap 中的过程。

Spring AOP 的使用

AOP(Aspect Oriented Programming )称为面向切面编程,扩展功能不是修改源代码实现,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待, Struts2 的拦截器设计就是基于 AOP 的思想,是个比较经典的例子。

  • Joinpoint(连接点):类里面可以被增强的方法,这些方法称为连接点
  • Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义
  • Advice(通知/增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
  • Aspect(切面):是切入点和通知(引介)的结合
  • Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或 Field
  • Target(目标对象):代理的目标对象(要增强的类)
  • Weaving(织入):是把增强应用到目标的过程,把 advice 应用到 target 的过程
  • Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类

Spring 的 AOP 常用的是拦截器

一般拦截器都是实现 HandlerInterceptor,其中有三个方法 preHandle、postHandle、afterCompletion。

  1. preHandle:执行 controller 之前执行
  2. postHandle:执行完 controller,return modelAndView 之前执行,主要操作 modelAndView 的值
  3. afterCompletion:controller 返回后执行

Spring 的事务管理

事务管理可以帮助我们保证数据的一致性,对应企业的实际应用很重要。

Spring 的事务机制包括声明式事务和编程式事务。

  • 编程式事务管理:Spring 推荐使用 TransactionTemplate,实际开发中使用声明式事务较多。
  • 声明式事务管理:将我们从复杂的事务处理中解脱出来,获取连接,关闭连接、事务提交、回滚、异常处理等这些操作都不用我们处理了,Spring 都会帮我们处理。

声明式事务管理使用了 AOP 面向切面编程实现的,本质就是在目标方法执行前后进行拦截。在目标方法执行前加入或创建一个事务,在执行方法执行后,根据实际情况选择提交或是回滚事务。

如何使用?

Spring 事务管理主要包括3个接口,Spring 的事务主要是由它们(PlatformTransactionManager,TransactionDefinition,TransactionStatus)三个共同完成的。

  1. PlatformTransactionManager:事务管理器,主要用于平台相关事务的管理。

    主要有三个方法:commit 事务提交;rollback 事务回滚;getTransaction 获取事务状态。

  2. TransactionDefinition:事务定义信息,用来定义事务相关的属性,给事务管理器 PlatformTransactionManager 使用。

    该接口有四个主要方法:

    • getIsolationLevel:获取隔离级别;
    • getPropagationBehavior:获取传播行为;
    • getTimeout:获取超时时间;
    • isReadOnly:是否只读(保存、更新、删除时属性变为false–可读写,查询时为true–只读)

    事务管理器能够根据这个返回值进行优化,这些事务的配置信息,都可以通过配置文件进行配置。

  3. TransactionStatus:事务具体运行状态,事务管理过程中,每个时间点事务的状态信息。

    一些方法:

    • hasSavepoint():返回这个事务内部是否包含一个保存点
    • isCompleted():返回该事务是否已完成,也就是说,是否已经提交或回滚
    • isNewTransaction():判断当前事务是否是一个新事务

声明式事务的优缺点

  • 优点:不需要在业务逻辑代码中编写事务相关代码,只需要在配置文件配置或使用注解(@Transaction),这种方式没有侵入性。
  • 缺点:声明式事务的最细粒度作用于方法上,如果像代码块也有事务需求,只能变通下,将代码块变为方法。

Spring 事务隔离级别及传播行为

传播行为

事务的第一个方面是传播行为。传播行为定义关于客户端和被调用方法的事务边界。Spring 定义了7中传播行为。

支持当前事务的情况:

  • TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)

不支持当前事务的情况:

  • TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。

其他情况:

  • TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED。

隔离级别

隔离级别 含义
ISOLATION_DEFAULT 使用后端数据库默认的隔离级别。
ISOLATION_READ_UNCOMMITTED 允许读取尚未提交的更改。可能导致脏读、幻影读或不可重复读。
ISOLATION_READ_COMMITTED 允许从已经提交的并发事务读取。可防止脏读,但幻影读和不可重复读仍可能会发生。
ISOLATION_REPEATABLE_READ 对相同字段的多次读取的结果是一致的,除非数据被当前事务本身改变。可防止脏读和不可重复读,但幻影读仍可能发生。
ISOLATION_SERIALIZABLE 完全服从 ACID 的隔离级别,确保不发生脏读、不可重复读和幻影读。这在所有隔离级别中也是最慢的,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的。

Spring 中的 Bean

如何创建 Spring 容器?

容器是 Spring 的核心,在基于 Spring 的应用里,应用对象生存于 Spring 容器中。容器负责创建对象,装配它们,配置它们并管理它们的整个生命周期,从生存到死亡(类似从 new 到 finalize() )。Spring 可以归为两种不同的类型:

  • bean 工厂(由 org.springframework.beans.factory.BeanFactory 接口定义)是最简单的容器,提供基本的 DI 功能。
  • 应用上下文(由 org.springframework.context.ApplicationContext 接口定义)基于 BeanFactory 构建,并提供应用框架级别的服务,例如从属性文件解析文本信息以及发布应用事件给感兴趣的事件监听者。

一般来说,bean 工厂太低级了,应用上下文的使用更为广泛。

使用应用上下文

常见五种类型:

  • AnnotationConfigApplicationContext:从一个或多个基于 java 的配置类中加载 Spring 应用上下文

  • AnnotationConfigWebApplicationContext:从一个或多个基于 java 的配置类中加载Spring Web 应用上下文

  • ClassPathXmlApplicationContext:从路径下的一个或多个 XML 配置文件中加载上下文定义,把应用上下文的定义文件作为类资源

  • FileSystemXmlApplicationContext:从文件系统下的一个或多个 XML 配置文件中加载上下文定义

  • XmlWebApplicationContext:从 Web 应用下的一个或多个 XML 配置文件中加载上下文定义

ApplicationContext 上下文的生命周期

  1. 实例化一个 Bean,也就是我们通常说的 new;
  2. 按照 Spring 上下文对实例化的 Bean 进行配置,也就是 IOC 注入
  3. 如果这个 Bean 实现了 BeanNameAware 接口,会调用它实现的 setBeanName(String beanId) 方法,此处传递的是 Spring 配置文件中 Bean 的 ID
  4. 如果这个 Bean 实现了 BeanFactoryAware 接口,会调用它实现的 setBeanFactory() ,传递的是 Spring 工厂本身(可以用这个方法获取到其他 Bean)
  5. 如果这个 Bean 实现了 ApplicationContextAware 接口,会调用 setApplicationContext(ApplicationContext) 方法,传入 Spring 上下文,该方式同样可以实现步骤4,但比4更好,因为 ApplicationContext 是 BeanFactory 的子接口,有更多的实现方法
  6. 如果这个 Bean 关联了 BeanPostProcessor 接口,将会调用postProcessBeforeInitialization(Object obj, String s) 方法,BeanPostProcessor 经常被用作是 Bean 内容的更改,并且由于这个是在 Bean 初始化结束时调用 After 方法,也可用于内存或缓存技术
  7. 如果这个 Bean 在 Spring 配置文件中配置了 init-method 属性会自动调用其配置的初始化方法
  8. 如果这个 Bean 关联了 BeanPostProcessor 接口,将会调用 postAfterInitialization(Object obj, String s) 方法;

注意:以上工作完成以后就可以用这个 Bean 了,那这个 Bean 是一个 single 的,所以一般情况下我们调用同一个 ID 的 Bean 会是在内容地址相同的实例

  1. 当 Bean 不再需要时,会经过清理阶段,如果 Bean 实现了 DisposableBean 接口,会调用其实现的 destroy 方法

  2. 最后,如果这个 Bean 的 Spring 配置中配置了 destroy-method 属性,会自动调用其配置的销毁方法

以上10步骤可以作为面试或者笔试的模板,另外这里描述的是应用 Spring 上下文 Bean 的生命周期,如果应用 Spring 的工厂也就是 BeanFactory 的话去掉第5步就Ok了。

Bean 生命周期

  1. Spring 对 Bean 进行实例化
  2. Spring 将值和 Bean 的引用注入进 Bean 对应的属性中
  3. 如果 Bean 实现了 BeanNameAware 接口,Spring 将 Bean 的 ID 传递给 setBeanName() 方法
  4. 如果 Bean 实现了 BeanFactoryAware 接口,Spring 将调用 setBeanFactory(BeanFactory bf) 方法并把 BeanFactory 容器实例作为参数传入
    • 实现 BeanFactoryAware 主要目的是为了获取 Spring 容器,如 Bean 通过 Spring 容器发布事件等
  5. 如果 Bean 实现了 ApplicationContextAwaer 接口,Spring 容器将调用 setApplicationContext(ApplicationContext ctx) 方法,将 bean 所在的应用上下文的引用传入进来
    • 作用与 BeanFactory 类似都是为了获取 Spring 容器,不同的是 Spring 容器在调用 setApplicationContext 方法时会把它自己作为 setApplicationContext 的参数传入,而 Spring 容器在调用 setBeanFactory 前需要程序员自己指定(注入) setBeanFactory 里的 BeanFactory 参数
  6. 如果 Bean 实现了 BeanPostProcessor 接口,Spring 将调用它们的 postProcessBeforeInitialization() 预初始化方法
    • 作用是在 Bean 实例创建成功后对进行增强处理,如对 Bean 进行修改,增加某个功能
  7. 如果 Bean 实现了 InitializingBean 接口,Spring 将调用它们的 afterPropertiesSet() 方法,作用与在配置文件中对 Bean 使用 init-method 声明初始化的作用一样,都是在 Bean 的全部属性设置成功后执行的初始化方法
  8. 如果 Bean 实现了 BeanPostProcessor 接口,Spring 将调用它们的 postProcessAfterInitialization() 后初始化方法
    • 作用与6的一样,只不过6是在 Bean 初始化前执行的,而这个是在 Bean 初始化后执行的,时机不同
  9. 经过以上的工作后,Bean 将一直驻留在应用上下文中给应用使用,直到应用上下文被销毁
  10. 如果 Bean 实现了 DispostbleBean 接口,Spring 将调用它的 destory 方法,作用与在配置文件中对 Bean 使用 destory-method 属性的作用一样,都是在 Bean 实例销毁前执行的方法。

Bean 实例化的三种方式

  • 使用类的无参构造创建(此种方式用的最多)
  • 使用静态工厂创建对象
  • 使用实例工厂创建对象

Bean 的作用域

类型 说明
单例(Singleton) 在整个应用中,只创建 bean 的一个实例。(默认)
原型(Prototype) 每次注入或者通过 Spring 应用上下文获取的时候,都会创建一个新的 bean 的实例。
会话(Session) 在 Web 应用中,为每个会话创建一个 bean 实例。
请求(Request) 在 Web 应用中,为每个请求创建一个 bean 实例。

BeanFactory 和 FactoryBean 的区别

  • BeanFactory 是个 Factory,也就是 IOC 容器或对象工厂,在 Spring 中,所有的 Bean 都是由 BeanFactory (也就是 IOC 容器)来进行管理的,提供了实例化对象和拿对象的功能。
  • FactoryBean 是个 Bean,这个 Bean 不是简单的 Bean,而是一个能生产或者修饰对象生成的工厂 Bean,它的实现与设计模式中的工厂模式和修饰器模式类似。

BeanFactory 和 ApplicationContext 的区别

区别 BeanFactory ApplicationContext
功能 BeanFactory 是 Spring 里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能。BeanFactory 需要在代码中通过手工调用 addBeanPostProcessor() 方法进行注册。 ApplicationContext 会利用 Java 反射机制自动识别出配置文件中定义的 BeanPostProcessor、 InstantiationAwareBeanPostProcessor 和 BeanFactoryPostProcessor 后置器,并自动将它们注册到应用上下文中。
装载 Bean BeanFactory 在初始化容器的时候并未实例化 Bean,直到第一次访问某个 Bean 时才实例化目标 Bean。 ApplicationContext 在初始化应用上下文的时候就实例化所有单实例的 Bean。

我们该用 BeanFactory 还是 ApplicationContent ?

BeanFactory 延迟实例化的优点:

应用启动的时候占用资源很少,对资源要求较高的应用,比较有优势,而且通过 Bean 工厂创建的 bean 生命周期会简单一些。

缺点:速度会相对来说慢一些,而且有可能会出现空指针异常的错误。

ApplicationContext 不延迟实例化的优点:

  • 所有的 Bean 在启动的时候都加载,系统运行的速度快。
  • 在启动的时候所有的 Bean 都加载了,我们就能在系统启动的时候,尽早的发现系统中的配置问题。
  • 建议 web 应用,在启动的时候就把所有的 Bean 都加载了。

缺点:把费时的操作放到系统启动中完成,所有的对象都可以预加载,缺点就是消耗服务器的内存。

ApplicationContext 的其他特点

除了提供 BeanFactory 所支持的所有功能外,ApplicationContext 还有额外的功能

  • 默认初始化所有的 Singleton,也可以通过配置取消预初始化。
  • 继承 MessageSource,因此支持国际化。
  • 资源访问,比如访问 URL 和文件(ResourceLoader)。
  • 事件机制,(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的 web 层。
  • 同时加载多个配置文件。
  • 消息发送、响应机制(ApplicationEventPublisher)。
  • 以声明式方式启动并创建 Spring 容器。

由于 ApplicationContext 会预先初始化所有的 Singleton Bean,于是在系统创建前期会有较大的系统开销,但一旦 ApplicationContext 初始化完成,程序后面获取 Singleton Bean 实例时候将有较好的性能。

也可以为 bean 设置 lazy-init 属性为 true,即 Spring 容器将不会预先初始化该 bean。

Spring 中的单例 bean 的线程安全问题了解吗?

大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例 Bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。

常见的有两种解决办法:

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

Spring中 autowire 和 resourse 关键字的区别

@Resource 和 @Autowired 都是做 bean 的注入时使用,其实 @Resource 并不是 Spring 的注解,它的包是 javax.annotation.Resource,需要导入,但是 Spring 支持该注解的注入。

共同点

两者都可以写在字段和 setter 方法上。两者如果都写在字段上,那么就不需要再写 setter 方法。

不同点

@Autowired

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

只按照 byType 注入。

1
2
3
4
5
6
7
8
9
10
public class TestServiceImpl {
// 下面两种@Autowired只要使用一种即可
@Autowired
private UserDao userDao; // 用于字段上

@Autowired
public void setUserDao(UserDao userDao) { // 用于属性的方法上
this.userDao = userDao;
}
}

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

1
2
3
4
5
public class TestServiceImpl {
@Autowired
@Qualifier("userDao")
private UserDao userDao;
}

@Resource

@Resource 默认按照 byName 自动注入,由 J2EE 提供,需要导入包javax.annotation.Resource。

@Resource 有两个重要的属性:name 和 type,而 Spring 将@Resource 注解的 name 属性解析为 bean 的名字,而 type 属性则解析为 bean 的类型。所以,如果使用 name 属性,则使用 byName 的自动注入策略,而使用 type 属性时则使用 byType 自动注入策略。如果既不制定 name 也不制定 type 属性,这时将通过反射机制使用 byName 自动注入策略。

1
2
3
4
5
6
7
8
9
10
public class TestServiceImpl {
// 下面两种@Resource只要使用一种即可
@Resource(name="userDao")
private UserDao userDao; // 用于字段上

@Resource(name="userDao")
public void setUserDao(UserDao userDao) { // 用于属性的setter方法上
this.userDao = userDao;
}
}

注:最好是将 @Resource 放在 setter 方法上,因为这样更符合面向对象的思想,通过 set、get 去操作属性,而不是直接去操作属性。

@Resource 装配顺序:

  1. 如果同时指定了 name 和 type,则从 Spring 上下文中找到唯一匹配的 bean 进行装配,找不到则抛出异常。
  2. 如果指定了 name,则从上下文中查找名称(id)匹配的 bean 进行装配,找不到则抛出异常。
  3. 如果指定了 type,则从上下文中找到类似匹配的唯一 bean 进行装配,找不到或是找到多个,都会抛出异常。
  4. 如果既没有指定 name,又没有指定 type,则自动按照 byName 方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。

@Resource 的作用相当于 @Autowired,只不过 @Autowired 按照 byType 自动注入。@Resource 注解的使用性更为灵活,可指定名称,也可以指定类型 ;@Autowired 注解进行装配容易抛出异常,特别是装配的 bean 类型有多个的时候,而解决的办法是需要在增加 @Qualifier 进行限定。

Spring 常用注解

一、组件类注解

注解 作用
@Component 标准一个普通的 Spring Bean 类
@Repository 标注一个 DAO 组件类
@Service 标注一个业务逻辑组件类
@Controller 标注一个控制器组件类

这些都是注解在平时的开发过程中出镜率极高,@Component、@Repository、@Service、@Controller 实质上属于同一类注解,用法相同,功能相同,区别在于标识组件的类型。@Component 可以代替 @Repository、@Service、@Controller,因为这三个注解是被 @Component 标注的。如下代码:

1
2
3
4
5
6
7
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
String value() default "";
}

注意点

  1. 被注解的 java 类当做 Bean 实例,Bean 实例的名称默认是 Bean 类的首字母小写,其他部分不变。@Service 也可以自定义 Bean 名称,但是必须是唯一的!
  2. 尽量使用对应组件注解的类替换 @Component 注解,在 Spring 未来的版本中,@Controller,@Service,@Repository 会携带更多语义。并且便于开发和维护!
  3. 指定了某些类可作为 Spring Bean 类使用后,最好还需要让 Spring 搜索指定路径,在 Spring 配置文件加入如下配置:
1
2
<!-- 自动扫描指定包及其子包下的所有Bean类 -->
<context:component-scan base-package="org.springframework.*"/>

二、装配bean时常用的注解

注解 作用
@Autowired 属于 Spring 的 org.springframework.beans.factory.annotation 包下,可用于为类的属性、构造器、方法进行注值
@Resource 不属于 Spring 的注解,而是来自于 JSR-250 位于 javax.annotation 包下,使用该 annotation 为目标 bean 指定协作者 Bean
@PostConstruct 实现初始化之前的操作
@PreDestroy 实现销毁 bean 之前进行的操作

注意点

使用 @Resource 也要注意添加配置文件到 Spring,如果没有配置 component-scan

1
2
<context:component-scan> 
<!--<context:component-scan>的使用,是默认激活<context:annotation-config>功能-->

则一定要配置 annotation-config

1
<context:annotation-config/>

三、@Component vs @Configuration and @Bean

@Component vs @Configuration (类级)

Spring 的官方团队说 @Component 可以替代 @Configuration 注解,事实上我们看源码也可以发现看到,如下:

1
2
3
4
5
6
7
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component //看这里!!!
public @interface Configuration {
String value() default "";
...

虽然说可以替代但是两个注解之间还是有区别的!

@Configuration 中所有带 @Bean 注解的方法都会被动态代理,因此调用该方法返回的都是同一个实例。@Configuration 本质上还是 @Component,因此 <context:component-scan/> 或者 @ComponentScan 都能处理 @Configuration 注解的类。

@Configuration 注解的 bean 都已经变成了增强的类。示例:

@Bean 注解方法执行策略

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class MyBeanConfig {
@Bean
public Country country(){
return new Country();
}

@Bean
public UserInfo userInfo(){
return new UserInfo(country());
}
}

直接调用 country() 方法返回的是同一个实例,因为注解是 @Configuration 增强版本类,但是如果是变成 @Component 之后,此时返回的就不是一个实例了,每次都会创建一个实例。下例其实 new 了两次 Country:

1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class MyBeanConfig {
@Bean
public Country country(){
return new Country();
}

@Bean
public UserInfo userInfo(){
return new UserInfo(country());
}
}

不过有一招可以让 @Component 也保证使用同一个实例——那就是用 @Autowired 来注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class MyBeanConfig {
@Autowired
private Country country;

@Bean
public Country country(){
return new Country();
}

@Bean
public UserInfo userInfo(){
return new UserInfo(country);
}
}

@Configuration 标记的类必须符合下面的要求:

  • 配置类必须以类的形式提供(不能是工厂方法返回的实例),允许通过生成子类在运行时增强(cglib 动态代理)。
  • 配置类不能是 final 类(没法动态代理)。
  • 配置注解通常为了通过 @Bean 注解生成 Spring 容器管理的类,
  • 配置类必须是非本地的(即不能在方法中声明,不能是 private)。
  • 任何嵌套配置类都必须声明为 static。
  • @Bean 方法可能不会反过来创建进一步的配置类(也就是返回的 bean 如果带有 @Configuration,也不会被特殊处理,只会作为普通的 bean)。

@Bean (方法级)

@Bean 注解主要用于告诉方法,产生一个 Bean 对象,然后这个 Bean 对象交给 Spring 管理。产生这个 Bean 对象的方法 Spring 只会调用一次,随后这个 Spring 将会将这个 Bean 对象放在自己的 IOC 容器中。

当使用了 @Bean 注解,我们可以连续使用多种定义 bean 时用到的注解,譬如用 @Qualifier 注解定义工厂方法的名称,用 @Scope 注解定义该 bean 的作用域范围,譬如是 singleton 还是 prototype 等。

Spring 中新的 Java 配置支持的核心就是 @Configuration 注解的类。这些类主要包括 @Bean 注解的方法来为 Spring 的 IOC 容器管理的对象定义实例,配置和初始化逻辑。

使用 @Configuration 来注解类表示类可以被 Spring 的 IOC 容器所使用,作为 bean 定义的资源。

1
2
3
4
5
6
7
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}

这和 Spring 的 XML 文件中的非常类似。

1
2
3
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

@Bean 注解扮演了和元素相同的角色。

四、Spring MVC模块注解

web模块常用到的注解

  • @Controller :表明该类会作为与前端作交互的控制层组件,通过服务接口定义的提供访问应用程序的一种行为,解释用户的输入,将其转换成一个模型然后将试图呈献给用户。Spring MVC 使用 @Controller 定义控制器,它还允许自动检测定义在类路径下的组件(配置文件中配置扫描路径)并自动注册。
  • @RequestMapping : 这个注解用于将 url 映射到整个处理类或者特定的处理请求的方法。可以只用通配符!既可以作用在类级别,也可以作用在方法级别。可以使用 value 属性指定具体路径,也可以用 method 属性标记所接受的请求类型。
  • @RequestParam :将请求的参数绑定到方法中的参数上,有 required 参数,默认情况下,required = true,也就是该参数必须要传。如果该参数可以传可不传,可以配置required = false。
  • @PathVariable : 该注解用于方法修饰方法参数,会将修饰的方法参数变为可供使用的 uri 变量(可用于动态绑定)。
  • @RequestBody : 可以将请求体中的 JSON 字符串绑定到相应的 bean 上,当然,也可以将其分别绑定到对应的字符串上。

  • @ResponseBody : @ResponseBody 与 @RequestBody 类似,它的作用是将返回类型直接输入到 HTTP response body 中。@ResponseBody 在输出 JSON 格式的数据时,会经常用到。

  • @RestController :控制器实现了 REST 的 API,只为服务于 JSON,XML 或其它自定义的类型内容,@RestController 用来创建 REST 类型的控制器。@RestController = @Controller + @ResponseBody。

五、Spring 事务模块注解

@Transactional

在处理 dao 层或 service 层的事务操作时,譬如删除失败时的回滚操作。使用 @Transactional 作为注解,但是需要在配置文件激活。

1
2
<!-- 开启注解方式声明事务 -->
<tx:annotation-driven transaction-manager="transactionManager" />

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class CompanyServiceImpl implements CompanyService {
@Autowired
private CompanyDAO companyDAO;

@Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class)
public int deleteByName(String name) {

int result = companyDAO.deleteByName(name);
return result;
}
...
}
属性 作用
readOnly 事务的读写属性,取 true 或者 false,true 为只读,默认为 false
rollbackFor 回滚策略,当遇到指定异常时回滚。譬如上例遇到异常就回滚
timeout 设置超时时间,单位为秒
isolation 设置事务隔离级别,枚举类型,一共五种

Spring 中用了哪些设计模式

Spring框架中使用到了大量的设计模式,下面列举了比较有代表性的:

  • 代理模式:在 AOP 和 remoting 中被用的比较多。
  • 单例模式:在 Spring 配置文件中定义的 bean 默认为单例模式。
  • 模板方法:用来解决代码重复的问题。比如: RestTemplate, JmsTemplate, JpaTemplate。
  • 工厂模式:BeanFactory 用来创建对象的实例。
  • 适配器:Spring AOP
  • 装饰器:Spring data hashmapper
  • 观察者:Spring 时间驱动模型
  • 回调:Spring ResourceLoaderAware 回调接口

Spring MVC的工作原理

  1. 客户端的所有请求都交给前端控制器 DispatcherServlet 来处理,它会负责调用系统的其他模块来真正处理用户的请求。 

  2. DispatcherServlet 把请求转发到 HandlerMapping 处理映射器。 

  3. 找到具体映射之后,生成具体的对象或者拦截对象返回给 DispatcherServlet。

  4. DispatcherServlet 请求 HandlerAdapter 适配器执行 Handler。

  5. Handler(controller) 执行、调用处理器相应功能处理方法。

  6. 处理请求完毕后,返回 ModelAndView 给 DispatcherServlet。

  7. DispatcherServlet 把 ModelAndView 交给 ViewResolver 视图解析器解析。

  8. ViewResolver 视图解析器返回 view 给 DispatcherServlet。

  9. DispatcherServlet 根据 view 进行渲染。(把 Model 填进视图)

  10. 返回响应给用户。

组件及作用

  1. 前端控制器 (DispatcherServlet)

    接收请求,响应结果,相当于转发器,中央处理器。负责调用系统的其他模块来真正处理用户的请求。 

    有了 DispatcherServlet 减少了其他组件之间的耦合度

  2. 处理器映射器 (HandlerMapping)

    作用:根据请求的 url 查找 Handler

  3. 处理器 (Handler)

    注意:编写 Handler 时按照 HandlerAdapter 的要求去做,这样适配器才可以去正确执行 Handler

  4. 处理器适配器 (HandlerAdapter)

    作用:按照特定规则(HandlerAdapter要求的规则)执行 Handler。

  5. 视图解析器 (ViewResolver)

    作用:进行视图解析,根据逻辑视图解析成真正的视图 (View)

  6. 视图 (View)

    View 是一个接口实现类支持不同的 View 类型(jsp、pdf、图片、json字符串、XML、HTML等等)

注意:只需要程序员开发,处理器和视图。

Spring 注解的优点

  • 可以充分利用 Java 的反射机制获取类结构信息,这些信息可以有效减少配置的工作。如使用 JPA 注释配置 ORM 映射时,我们就不需要指定 PO 的属性名、类型等信息,如果关系表字段和 PO 属性名、类型都一致,您甚至无需编写任务属性映射信息——因为这些信息都可以通过 Java 反射机制获取。
  • 注释和 Java 代码位于一个文件中,而 XML 配置采用独立的配置文件,大多数配置信息在程序开发完成后都不会调整,如果配置信息和 Java 代码放在一起,有助于增强程序的内聚性。而采用独立的 XML 配置文件,程序员在编写一个功能时,往往需要在程序文件和配置文件中不停切换,这种思维上的不连贯会降低开发效率。
  • 编译期校验,错误的注解在编译期间就会报错。注解在java代码中,从而避免了额外的文件维护工作。注解被编译成java字节码,消耗的内存小,读取速度快,往往比xml配置文件解析快几个数量级,利用测试和维护。

Spring AOP 和 AspectJ AOP 有什么区别?

Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理 (Proxying),而 AspectJ 基于字节码操作 (Bytecode Manipulation)。

Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,

如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。