依赖注入和控制反转

问题

紧密耦合的代码难以测试,难以复用,难以理解,但是耦合是必须的

解决

通过DI,对象的依赖关系将由系统中负责协调各对象的第三方插件在创建对象时设定。对象无需自行创建或管理他们的依赖关系(IOC和DI是同一个概念的不同角度描述)

来自IoC 之 2.1 IoC基础 ——跟我学Spring3

IOC

IOC (Inversion of Control),即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,IOC意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。

  • 谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IOC是有专门一个容器来创建这些对象,即由IOC容器来控制对象的创建;谁控制谁?当然是IOC容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括文件等)。
  • 为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

传统javaSE程序设计:

依赖注入

IOC:

传统设计

IOC能做什么:IOC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IOC/DI思想中,应用程序就变成被动的了,被动的等待IOC容器来创建并注入它所需要的资源了。

IOC很好的体现了面向对象设计法则之一 – 好莱坞法则:“别找我们,我们找你”;即由IOC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

DI

DI(Dependency Injection),即“依赖注入”是组件之间依赖关系由容器在运行期决定。形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

  • 谁依赖于谁:当然是应用程序依赖于IOC容器;
  • 为什么需要依赖:应用程序需要IOC容器来提供对象需要的外部资源;
  • 谁注入谁:很明显是IOC容器注入应用程序某个对象,应用程序依赖的对象;
  • 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
参考博客

1:IOC和DI
2:Martin Fowler依赖注入(中文版)

我的理解

IOC是从对象的角度出发,将自己需要的类交给IOC容器,相对于传统的Java开发来说由对象自身控制转变为容器控制,所以是控制反转,而DI是另一个角度的说法。而IOC的目的最终还是降低耦合性,将组件的配置与使用分离开。

例:有两个包A,B分别由两个程序员编写,在B中有一个Person接口 ,在A中定义:Person person = null 这是A只依赖于B,因为没有实际赋值,而Person person = new Student() 就把Student实例注入到对象person中,所以通过new关键字创建对象就是最常见的依赖注入方法,这是传统Java开发中的方法;而IOC将A需要的依赖提交给IOC容器,容器再将Student注入到A,由IOC容器控制.

IOC是一种开发思想,DI是一种开发实现

控制的什么被反转了?获得依赖对象的方式反转了。

实现方式

构造器注入

将被依赖对象通过构造函数的参数注入给依赖对象,并且在初始化对象的时候注入。

  • 优点:
    对象初始化完成后便可获得可使用的对象。
  • 缺点:
    当需要注入的对象很多时,构造器参数列表将会很长;
    不够灵活。若有多种注入方式,每种方式只需注入指定几个依赖,那么就需要提供多个重载的构造函数,麻烦。

setter方法注入

通过调用成员变量提供的setter函数将被依赖对象注入给依赖类。

  • 优点:
    灵活。可以选择性地注入需要的对象。
  • 缺点:
    依赖对象初始化完成后由于尚未注入被依赖对象,因此还不能使用。

接口注入

依赖类必须要实现指定的接口,然后实现该接口中的一个函数,该函数就是用于依赖注入。该函数的参数就是要注入的对象。

  • 优点
    接口注入中,接口的名字、函数的名字都不重要,只要保证函数的参数是要注入的对象类型即可。
  • 缺点:
    侵入性太强,不建议使用。

Xml装配bean

Spring有三种装配方式,优先使用隐式的Bean发现机制和自动装配,其次使用在Java中进行装配,最后再使用在XML中进行装配。

在 Spring 实例化 Bean 的过程中,首先会调用默认的构造方法实例化 Bean 对象,然后通过 Java 的反射机制调用 setXxx() 方法进行属性的注入。因此,设值注入要求一个 Bean 的对应类必须满足以下两点要求。

  • 必须提供一个默认的无参构造方法。
  • 必须为需要注入的属性提供对应的 setter 方法。

使用设值注入时,在 Spring 配置文件中,需要使用<bean> 元素的子元素 <property> 元素为每个属性注入值。而使用构造注入时,在配置文件中,主要使用 <constructor-arg> 标签定义构造方法的参数,可以使用其 value 属性(或子元素)设置该参数的值。

隐式的Bean发现机制和自动装配

见后文注解装配和自动装配

在 Java 的接口和类中实现配置

在Spring的Java配置类中对SpringBean进行配置

使用 @Bean 注解将方法返回的实例对象添加到上下文中
在@Bean返回的实例对象中可以通过构造器注入传入相关依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
@ComponentScan("com.jimisun")
public class WebConfig {

@Bean
public User user() {
return new User(myArticle());
}

@Bean
public MyArticle myArticle() {
return new MyArticle();
}

}

在 XML 文件中显式配置

通过构造函数注入

  • 无参构造函数注入 — 默认
  • 有参构造函数注入 — 需要自己设置
1
2
3
4
5
6
7
8
9
10
11
<bean id="account" class="com.gxa.pojo.Account">
<!--
name : 属性名称
value :赋值
type : 属性类型
index :参数索引位置
-->
<constructor-arg index="0" type="int" value="123"></constructor-arg>
<!--<constructor-arg type="java.lang.String" value="FFDSS"></constructor-arg>-->
<constructor-arg type="double" value="1000"></constructor-arg>
</bean>

通过setter方法注入

1
2
3
4
<bean id="userServiceImpl" class="com.gxa.service.impl.UserServiceImpl">
<property name="dao" ref="userDaoImpl"></property>
</bean>
<bean id="userDaoImpl" class="com.gxa.dao.impl.UserDaoImpl"></bean>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Service
public class UserServiceImpl {
private UserDaoImpl dao;
public void setDao(UserDaoImpl dao) {
this.dao = dao;
}
public void findUser(){
System.out.println("================");
dao.findUser();
}
}

// Dao
public class UserDaoImpl {
public UserDaoImpl() {
super();
System.out.println("====dao层中的无参构造器==");
}

public void findUser(){
System.out.println("=======dao层查询数据=======");
}
}

Spring概述

Spring 是一个开源框架,是为了解决企业应用程序开发复杂性而创建的。
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。
Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式

Spring组件

组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:

spring组件

  • 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转 (IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
  • Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
  • Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
  • Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
  • Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
  • Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
  • Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。

Spring 框架的功能可以用在任何 J2EE 服务器中,大多数功能也适用于不受管理的环境。Spring 的核心要点是:支持不绑定到特定 J2EE 服务的可重用业务和数据访问对象。毫无疑问,这样的对象可以在不同 J2EE 环境 (Web 或 EJB)、独立应用程序、测试环境之间重用。

DI使相互协作的软件组合保持松散耦合,面向切面编程允许你把遍布应用各处的功能分离出来形成可重用的组件,保证pojo的简单性

Spring容器

Spring有两个核心接口:BeanFactory和ApplicationContext,其中ApplicationContext是BeanFactory的子接口。它们都可以代表Spring容器。Spring容器是生成Bean实例的工厂,并管理容器中的Bean。Bean是Spring管理的基本单元。

Spring BeanFactory 容器

这是一个最简单的容器,它主要的功能是为依赖注入 (DI) 提供支持,主要目的是向后兼容已经存在的和那些 Spring 整合在一起的第三方框架。
在 Spring 中,有大量对 BeanFactory 接口的实现。其中,最常被使用的是 XmlBeanFactory 类。这个容器从一个 XML 文件中读取配置元数据,由这些元数据来生成一个被配置化的系统或者应用。
在资源宝贵的移动设备或者基于 applet 的应用当中, BeanFactory 会被优先选择。否则,一般使用的是 ApplicationContext,除非你有更好的理由选择 BeanFactory。

Spring ApplicationContext 容器

Application Context 是 spring 中较高级的容器。和 BeanFactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。
另外,它增加了企业所需要的功能,比如,从属性文件中解析文本信息和将事件传递给所指定的监听器。
ApplicationContext 包含 BeanFactory 所有的功能,一般情况下,相对于 BeanFactory,ApplicationContext 会更加优秀。

最常被使用的 ApplicationContext 接口实现

FileSystemXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径。
ClassPathXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。
WebXmlApplicationContext:该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。

SpringBean生命周期

一.生命周期流程图

spring生命周期1
spirng生命周期2

若容器注册了以上各种接口,程序那么将会按照以上的流程进行。

二.各种接口方法分类

Bean的完整生命周期经历了各种方法调用,这些方法可以划分为以下几类:

  • Bean自身的方法:这个包括了Bean本身调用的方法和通过配置文件中<bean>的init-method和destroy-method指定的方法
  • Bean级生命周期接口方法:这个包括了BeanNameAware、BeanFactoryAware、InitializingBean和DiposableBean这些接口的方法
  • 容器级生命周期接口方法:这个包括了InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为“后处理器”。
  • 工厂后处理器接口方法:这个包括了AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer等等非常有用的工厂后处理器  接口的方法。工厂后处理器也是容器级的。在应用上下文装配配置文件之后立即调用。

自动装配

Spring 容器可以在不使用<constructor-arg><property>元素的情况下自动装配相互协作的 bean 之间的关系,这有助于减少编写一个大的基于 Spring 的应用程序的 XML 配置的数量。

模式

  • no
    这是默认的设置,它意味着没有自动装配,你应该使用显式的bean引用来连线。你不用为了连线做特殊的事。

  • byName
    由属性名自动装配。Spring 容器看到在 XML 配置文件中 bean 的自动装配的属性设置为 byName。然后尝试匹配,并且将它的属性与在配置文件中被定义为相同名称的 beans 的属性进行连接。

    1
    2
    3
    4
    5
    6
    7
    public class User{
    private Role myRole;
    }
    public class Role {
    private String id;
    private String name;
    }
    1
    2
    3
    4
    5
    6
    <bean id="myRole" class="com.viewscenes.netsupervisor.entity.Role">
    <property name="id" value="1001"></property>
    <property name="name" value="管理员"></property>
    </bean>

    <bean id="user" class="com.viewscenes.netsupervisor.entity.User" autowire="byName"></bean>
  • byType
    由属性数据类型自动装配。Spring 容器看到在 XML 配置文件中 bean 的自动装配的属性设置为 byType。然后如果它的类型匹配配置文件中的一个确切的 bean 名称,它将尝试匹配和连接属性的类型。如果存在不止一个这样的 bean,则一个致命的异常将会被抛出。

    1
    2
    3
    4
    5
    6
    <bean class="com.viewscenes.netsupervisor.entity.Role">
    <property name="id" value="1001"></property>
    <property name="name" value="管理员"></property>
    </bean>

    <bean id="user" class="com.viewscenes.netsupervisor.entity.User" autowire="byType"></bean>

    如果使用byType,Role Bean的ID都可以省去。

  • constructor

    类似于 byType,但该类型适用于构造函数参数类型。如果在容器中没有一个构造函数参数类型的 bean,则一个致命错误将会发生。

    1
    2
    3
    4
    5
    6
    7
    public class User{
    private Role role;

    public User(Role role) {
    this.role = role;
    }
    }
    1
    <bean id="user" class="com.viewscenes.netsupervisor.entity.User" <autowire="constructor"></bean>
  • autodetect
    Spring首先尝试通过 constructor 使用自动装配来连接,如果它不执行,Spring 尝试通过 byType 来自动装配。过时方法,Spring3.0之后不再支持

  • 默认自动装配
    默认情况下,default-autowire属性被设置为none,标示所有的Bean都不使用自动装配,除非Bean上配置了autowire属性。如果你需要为所有的Bean配置相同的autowire属性,有个办法可以简化这一操作。在根元素Beans上增加属性default-autowire=”byType”

@Autowired

  1. 强制性
    默认情况下,它具有强制契约特性,其所标注的属性必须是可装配的。如果没有Bean可以装配到Autowired所标注的属性或参数中,那么你会看到NoSuchBeanDefinitionException的异常信息。

    如果我们不确定属性是否可以装配,可以这样来使用Autowired。
    @Autowired(required=false)

  2. 装配策略

    • 默认按照类型装配
      关键点是findAutowireCandidates这个方法
    • 按照名称装配
      如果查到多个实例,determineAutowireCandidate方法就是关键。它来确定一个合适的Bean返回。其中一部分就是按照Bean的名称来匹配。

    @Autowired默认使用byType来装配属性,如果匹配到类型的多个实例,再通过byName来确定Bean。

  3. 主和优先级
    通过byType可能会找到多个实例的Bean。然后再通过byName来确定一个合适的Bean,如果通过名称也确定不了呢?
    还是determineAutowireCandidate这个方法,它还有两种方式来确定。

    • Primary
      它的作用是看Bean上是否包含@Primary注解,如果包含就返回。当然了,你不能把多个Bean都设置为@Primary,不然你会得到NoUniqueBeanDefinitionException这个异常。
    • Priority
      你也可以在Bean上配置@Priority注解,它有个int类型的属性value,可以配置优先级大小。数字越小的,就被优先匹配。同样的,你也不能把多个Bean的优先级配置成相同大小的数值,否则NoUniqueBeanDefinitionException异常照样出来找你。
      最后,有一点需要注意。Priority的包在javax.annotation.Priority;,如果想使用它还要引入一个坐标。
1
2
3
4
5
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.2</version>
</dependency>

优缺点

  • 优点
    变量方式注入非常简洁,没有任何多余代码,非常有效的提高了java的简洁性。即使再多几个依赖一样能解决掉这个问题。

  • 缺点

    1. 你不能使用属性注入的方式构建不可变对象。
    2. 你的类和依赖容器强耦合,不能在容器外使用。
    3. 你的类不能绕过反射(例如单元测试的时候)进行实例化,必须通过依赖容器才能实例化。
    4. 实际的依赖被隐藏在外面,不是在构造方法或者其它方法里面反射的。
    5. 一个类经常会有超过10个的依赖。如果使用构造方法的方式注入的话,构造方法会有10个参数,明显有点蠢。但是如果使用属性注入的话就没有这样的限制。但是一个类有很多的依赖,是一个危险的标志,因为很有可能这个类完成了超过一件事,违背了单一职责原则。

总结

变量方式注入应该尽量避免,使用set方式注入或者构造器注入,这两种方式的选择就要看这个类是强制依赖的话就用构造器方式,选择依赖的话就用set方法注入。

注解装配

配置

从 Spring 2.5 开始就可以使用注解来配置依赖注入。而不是采用 XML 来描述一个 bean 连线,你可以使用相关类,方法或字段声明的注解,将 bean 配置移动到组件类本身。

1
2
3
4
5
6
<!--
使用注解之前,我们要先导入4+2+aop的jar包
同时引入约束 beans+context
-->
<!--组件扫描:Spring容器会扫描这个包里所有类,从类的注解信息中获取Bean的信息-->
<context:component-scan base-package="xqm”/>

注解方式装配Bean

1
2
@Component取代<bean class="">
@Component(“id”) 取代 <bean id="" class="">

除了@Component外,Spring提供了三个功能和@Component等效的注解。
它们一般用于web项目,对DAO,service,web层进行注解,所以也称为Bean的衍生注解。

1
2
3
@Repository :dao层
@Service:service层
@Controller:web层

装配总结

  1. 被注解的java类当做Bean实例,Bean实例的名称默认是Bean类的首字母小写,其他部分不变。@Service也可以自定义Bean名称,但是必须是唯一的!
  2. 尽量使用对应组件注解的类替换@Component注解,在spring未来的版本中,@Controller,@Service,@Repository会携带更多语义。并且便于开发和维护!

其余常用注解

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

二.@Component vs @Configuration and @Bean
Bean注解主要用于方法上,有点类似于工厂方法,当使用了@Bean注解,我们可以连续使用多种定义bean时用到的注解,譬如用@Qualifier注解定义工厂方法的名称,用@Scope注解定义该bean的作用域范围,譬如是singleton还是prototype等。

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

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

三.spring MVC模块注解

  1. web模块
    @Controller :表明该类会作为与前端作交互的控制层组件,通过服务接口定义的提供访问应用程序的一种行为,解释用户的输入,将其转换成一个模型然后将试图呈献给用户。
    @RequestMapping : 这个注解用于将url映射到整个处理类或者特定的处理请求的方法。可以只用通配符!
    @RequestMapping 既可以作用在类级别,也可以作用在方法级别。当它定义在类级别时,标明该控制器处理所有的请求都被映射到 /favsoft 路径下。@RequestMapping中可以使用 method 属性标记其所接受的方法类型,如果不指定方法类型的话,可以使用 HTTP GET/POST 方法请求数据,但是一旦指定方法类型,就只能使用该类型获取数据。
    @RequestParam :将请求的参数绑定到方法中的参数上,有required参数,默认情况下,required=true,也就是改参数必须要传。如果改参数可以传可不传,可以配置required=false。
    @PathVariable : 该注解用于方法修饰方法参数,会将修饰的方法参数变为可供使用的uri变量(可用于动态绑定)。
    @PathVariable中的参数可以是任意的简单类型,如int, long, Date等等。Spring会自动将其转换成合适的类型或者抛出 TypeMismatchException异常。当然,我们也可以注册支持额外的数据类型。
    @PathVariable支持使用正则表达式,这就决定了它的超强大属性,它能在路径模板中使用占位符,可以设定特定的前缀匹配,后缀匹配等自定义格式。
    @RequestBody : @RequestBody是指方法参数应该被绑定到HTTP请求Body,常用于接收json数据
    @ResponseBody : @ResponseBody与@RequestBody类似,它的作用是将返回类型直接输入到HTTP response body中。常用于返回json数据
    @RestController :控制器实现了REST的API,只为服务于JSON,XML或其它自定义的类型内容,@RestController用来创建REST类型的控制器,与@Controller类型。@RestController就是这样一种类型,它避免了你重复的写@RequestMapping与@ResponseBody。
    @ModelAttribute :@ModelAttribute可以作用在方法或方法参数上,当它作用在方法上时,标明该方法的目的是添加一个或多个模型属性(model attributes)。该方法支持与@RequestMapping一样的参数类型,但并不能直接映射成请求。控制器中的@ModelAttribute方法会在@RequestMapping方法调用之前而调用。

  2. 事务注解
    在处理dao层或service层的事务操作时,譬如删除失败时的回滚操作。使用**@Transactional** 作为注解

切面编程

概念

AOP 即 Aspect Oriented Program 面向切面编程
面向切面编程的思想里面,把功能分为核心业务功能,和周边功能。周边功能在 Spring 的面向切面编程AOP思想里,即被定义为切面
在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能 “编织” 在一起,这就叫AOP

目的

将横切关注点和业务逻辑分离。AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性

DI有助于应用对象之间的解耦,而AOP可以实现横切关注点与它们所影响的对象之间的解耦。

术语

  • 切面,Aspect:关注点—所有功能总称
  • 连接点,join point:程序执行过程中的某一点
  • 建议,Advice:AOP框架在特定的连接点上执行的动作
  • 切入点,Pointcut:一系列连接点的集合
  • 目标对象,Target Object:要被代理的对象
  • AOP代理,AOP Proxy:AOP框架创建的对象,Spring中的AOP代理包含JDK动态代理和CGLIB代理,前者为接口代理(实现接口的目标代理),后者为类代理
  • 引入,Introduction:简单说就是AOP中的方法

Spring对AOP的支持

Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:

  1. 默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了
  2. 当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB

AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:

  1. 定义普通业务组件
  2. 定义切入点,一个切入点可能横切多个业务组件
  3. 定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作
    所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。

基于注解配置的AOP

  1. 导入相关的jar包

    1
    2
    3
    4
    spring-aop-3.2.5.RELEASE.jar   【spring3.2源码】
    aopalliance.jar 【spring2.5源码/lib/aopalliance】
    aspectjweaver.jar【spring2.5源码/lib/aspectj】或【aspectj-1.8.2\lib】
    aspectjrt.jar【spring2.5源码/lib/aspectj】或【aspectj-1.8.2\lib】
    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjtools</artifactId>
    <version>1.8.13</version>
    </dependency>
  2. 配置文件applicationContext.xml
    注意:
    1、先引入aop命名空间,和schema
    2、再配置Aop的动态代理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?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:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    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/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 使用注解时要开启注解扫描 要扫描的包 -->
    <context:component-scan base-package="cn.itcast.e_aop_anno"></context:component-scan>

    <!-- 开启aop注解方式 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    </beans>
  3. 使用注解
    注解

    1
    2
    3
    4
    5
    6
    7
    @Aspect 指定一个类为切面类
    @Pointcut("execution(* cn.itcast.e_aop_anno.*.*(..))") 指定切入点表达式
    @Before("pointCut_()") 前置通知: 目标方法之前执行
    @After("pointCut_()")后置通知:目标方法之后执行(始终执行)
    @AfterReturning("pointCut_()")返回后通知: 执行方法结束前执行(异常不执行)
    @AfterThrowing("pointCut_()")异常通知: 出现异常时候执行
    @Around("pointCut_()")环绕通知: 环绕目标方法执行
  4. 示例
    AOP代理类LogAop.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
* 用Spring创建AOP类
* 在创建AOP时应该加入@Aspect @Component
* AOP默认是面向接口的编程
* 在AOP中支持CGLIB方和JDK方式
* @author admin
*/
@Aspect
@Component
public class LogAop {
// 指定切入点表达式: 拦截哪些方法; 即为哪些类生成代理对象
// 解释@Pointcut("execution(* cn.itcast.e_aop_anno.*.*(..))")
// @Pointcut("execution(* (切入点表达式固定写法)cn.itcast.e_aop_anno(表示包).类名(可以用*表示包下所有的类).方法名(可以用*表示类下所有的方法)(参数)表示参数可以用..
@Pointcut("execution(* cn.itcast.e_aop_anno.*.*(..))")
public void pointCut_(){
}

// @Before("execution(* cn.itcast.e_aop_anno.*.*(..))")每个方法需要写相同的引用,所以将相同的部分抽取到一个空的方法中pointCut_(),
// 前置通知 : 在执行目标方法之前执行
@Before("pointCut_()")
public void begin(){
System.out.println("开始事务/异常");
}

// 后置/最终通知:在执行目标方法之后执行 【无论是否出现异常最终都会执行】
@After("pointCut_()")
public void after(){
System.out.println("提交事务/关闭");
}

// 返回后通知: 在调用目标方法结束后执行 【出现异常不执行】
@AfterReturning("pointCut_()")
public void afterReturning() {
System.out.println("afterReturning()");
}

// 异常通知: 当目标方法执行异常时候执行此关注点代码
@AfterThrowing("pointCut_()")
public void afterThrowing(){
System.out.println("afterThrowing()");
}

// 环绕通知:环绕目标方式执行
@Around("pointCut_()")
public void around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕前....");
pjp.proceed(); // 执行目标方法
System.out.println("环绕后....");
}

}
1
2
3
4
// 接口
public interface IUserDao {
void save();
}

目标对象类一:实现接口

1
2
3
4
5
6
7
8
9
10
11
/**
* 目标对象
*/
@Component // 加入容器
public class UserDao implements IUserDao{

@Override
public void save() {
System.out.println("-----核心业务:保存!!!------");
}
}

目标对象类二:没有实现接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 目标对象*/
@Component // 加入容器
@Scope("prototype")
public class OrderDao{

public void save() {
System.out.println("-----核心业务:保存!!!------");
}
}
测试类
public class App {

ApplicationContext ac =
new ClassPathXmlApplicationContext("cn/itcast/e_aop_anno/bean.xml");

// 目标对象有实现接口,spring会自动选择“JDK代理”
@Test
public void testApp() {
IUserDao userDao = (IUserDao) ac.getBean("userDao");
System.out.println(userDao.getClass());//$Proxy001
userDao.save();
}

// 目标对象没有实现接口, spring会用“cglib代理”
@Test
public void testCglib() {
OrderDao orderDao = (OrderDao) ac.getBean("orderDao");
System.out.println(orderDao.getClass());
orderDao.save();
}
输出结果:

    开始事务/异常

    -----核心业务:保存!!!------

    提交事务/关闭

基于配置xml配置文件的方式

  1. 导入相关的jar包

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjtools</artifactId>
    <version>1.8.13</version>
    </dependency>
  2. aop命名空间

  3. aop配置
    配置切面类 (重复执行代码形成的类)
    aop配置 拦截哪些方法 / 拦截到方法后应用通知代码

applicationContext.xml的Aop配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- dao 实例 在这里配置后就不用在类中使用注解 -->
<bean id="userDao" class="cn.itcast.f_aop_xml.UserDao"></bean>
<bean id="orderDao" class="cn.itcast.f_aop_xml.OrderDao"></bean>

<!-- 切面类 -->
<bean id="aop" class="cn.itcast.f_aop_xml.Aop"></bean>

<!-- Aop配置 -->
<aop:config>
<!-- 定义一个切入点表达式: 拦截哪些方法 -->
<aop:pointcut expression="execution(* cn.itcast.f_aop_xml.*.*(..))" id="pt"/>
<!-- 切面 -->
<aop:aspect ref="aop">
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="pt"/>
<!-- 前置通知: 在目标方法调用前执行 -->
<aop:before method="begin" pointcut-ref="pt"/>
<!-- 后置通知: -->
<aop:after method="after" pointcut-ref="pt"/>
<!-- 返回后通知 -->
<aop:after-returning method="afterReturning" pointcut-ref="pt"/>
<!-- 异常通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pt"/>

</aop:aspect>
</aop:config>
</beans>
HTTP协议