Spring入门教程 学习笔记 IOC控制反转DI依赖注入AOP面向切面编程详细解析

Spring

Spring框架是以简化Java EE应用程序的开发为目标而创建的

DI和AOP: 基于注解+反射+动态代理,见《java编程的逻辑》p573、p584

用Java实现AOP

面向切面编程 Aspect Oriented Programming

在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

Spring是借助了JDK proxy和CGlib两种技术实现AOP的。JDK dynamic proxy提供了一种灵活的方式来Hook(AOP编程与插件化的具体实现用到了动态代理的Hook机制。)一个方法并执行指定的操作,使用JDK proxy实现AOP必须先提供一个相关的接口以及该接口的实现类, 而使用CGlib则不需要基于接口。

按照以下的步骤建立一个精简的代码模板便能满足这样的需求:

  1. 创建一个抽象类,用于将aop应用于目标对象上。
  2. 创建名为BeforeHandler 和 AfterHandler的两个aop。前者在方法执行之前工作,而后者则在方法执行结束后工作。
  3. 创建一个代理类,使所有的aop handler和目标对象只需作为参数传入,就能创建一个hook。
  4. 加入你自己的业务逻辑或者横切关注点。
  5. 最后,通过传入相关的参数创建代理对象(proxy object)。

用途

将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

核心概念

1、横切关注点
对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点

2、切面(aspect)
类是对物体特征的抽象,切面就是对横切关注点的抽象

@Retention(RUNTIME)
@Target(TYPE)
public @interface Aspect{
    Class<?>[] value();
}

3、连接点(joinpoint)
被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器

注:该注解定义:

@Target(Field, Method, Construcor)
@Retention(RetentionPolicy:RUNTIME)
public @interface joinpoint{
}

4、切入点(pointcut)
对连接点进行拦截的定义

5、通知(advice)
所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类

6、目标对象
代理的目标对象

7、织入(weave)
将切面应用到目标对象并导致代理对象创建的过程

8、引入(introduction)
在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段

控制反转IOC

Inversion of Control的缩写,多数书籍翻译成“控制反转”, 也叫依赖注入Dependency Injection。IOC是一种目的,DI是一种手段。为了达到控制反转的目的,即程序员不需要自己new对象,对象由容器控制这个目的,Spring框架提供了依赖注入的手段,哪里需要对象,就在哪里注入。

借助于“第三方”实现具有依赖关系的对象之间的解耦。实现IOC容器,对于系统开发而言,参与开发的每一成员只要实现自己的类就可以了而不必考虑其他对象的依赖关系.

依赖注入: 所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。比如A依赖一个对象B(一般依赖的是接口),在运行前,A并不知道自己依赖的B的具体实现是怎么样的,IOC容器会在适当的时候为A创建一个合适的B对象,并且注入,这就是依赖注入。

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

好处优点

服务者: A需要B的数据, 不必A自己去拿(自己写调用), A命令IOC, 由IOC服务帮助A从B那里取过来.

Spring入门教程 学习笔记 IOC控制反转DI依赖注入AOP面向切面编程详细解析

由于引进了中间位置的“第三方”,也就是IOC容器,使得A、B、C、D这4个对象没有了耦合关系,齿轮之间的传动全部依靠“第三方”了,全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成“粘合剂”的由来。

软件系统在没有引入IOC容器之前,如图1所示,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。

软件系统在引入IOC容器之后,这种情形就完全改变了,如图3所示,由于IOC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。

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

原理

IOC中最基本的技术就是“反射(Reflection)”编程.

自己动手实现一个简单的 IOC

IOC

教程

在待调用的A类中写好getter和setter方法, 主调的B类创建Spring对象(调用Spring配置xml文件), 调用Spring的方法(or Spring配置文件已经初始化), 由Spring来进行调用和对象创建(传统OOP方法为B类自己调用使用new方法创建A类对象)

Spring入门教程 学习笔记 IOC控制反转DI依赖注入AOP面向切面编程详细解析

B类中可以直接使用Spring提供的句柄来进行方法调用, 操作A类对象

ApplicationContext context = new ClassPathXmlApplicationContext(new String[] { "applicationContext.xml" });
Category c = (Category) context.getBean("c");//从Spring配置文件读取, 不自己用new方法创建
c.setName("hell");
System.out.println(c.getName());

javaBean

JavaBeans是Java中一种特殊的类,可以将多个对象封装到一个对象中。特点是可序列化,提供无参构造器,提供getter方法和setter方法访问对象的属性。名称中的“Bean”是用于Java的可重用软件组件的惯用叫法。

MVC模式

Spring入门教程 学习笔记 IOC控制反转DI依赖注入AOP面向切面编程详细解析

注解

注解 or 配置文件, 都是在告知Spring谁是bean, 完成初始化, 设置IOT的代理托管.

@Autowired 注解

Spring 2.5 引入了 @Autowired 注释,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 通过 @Autowired的使用来消除 set ,get方法。

class Category{
    private int id; 
    public int getId() {return id;}
	...
}
class Product{
	private String name;
	@Autowired  //位置1(添加到此处可将Setter方法删除)
	private Category category;
    ...
    @Autowired  //or 位置2(添加到任一位置即可)
    public void setCategory(Category category){ this.category=category;}
    public Category getCategory(){return this.category;}
}
Category p= (Product) context.getBean("p");
System.out.println(p.getCategory().getName());//直接调用, 已经自动注解

c和p是两个不同类的对象, Product类的Category类调用不需要自己去处理, @Autowired注解自动处理.

 	<context:annotation-config/>
    <bean name="c" class="com.how2java.pojo.Category">
        <property name="name" value="category 1" />
    </bean>    
    <bean name="p" class="com.how2java.pojo.Product">
        <property name="name" value="product1" />
<!--         <property name="category" ref="c" /> -->  
    </bean>

如果不使用Autowired, 需要把上面applicationContext.xml中 添加ref=c行 (注释行).

问题解决: Spring @Autowired注解添加不成功

代码正确, 但是在System.out.println(p.getCategory().getName());时报错: NullPointer. 需要在applicationContext.xml中之前添加一句: <context:annotation-config/>

用法2

多个属性自动装配在同一个函数完成, 函数使用@Autowired注解.

public class Boss {     
    private Car car;     
    private Office office;     
      
    @Autowired    
    public Boss(Car car ,Office office){     
        this.car = car;     
        this.office = office ;     
    }   

@Resource注解

c是配置文件中的bean.name, 可以直接通过此注解调用

import javax.annotation.Resource;
public class Product { 
    private String name;
    @Resource(name="c")
    private Category category;

@Autowired 和 @Resource

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

@Resource 是JDK1.6支持的注解默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名,按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。

他们的主要区别就是@Autowired是默认按照类型装配的 @Resource默认是按照名称装配的
byName 通过参数名 自动装配,如果一个bean的name 和另外一个bean的 property 相同,就自动装配。
byType 通过参数的数据类型自动自动装配,如果一个bean的数据类型和另外一个bean的property属性的数据类型兼容,就自动装配

@Component注解

为Product加上**@Component(“p”)注解**,即表明此类是bean; 另外,因为配置从applicationContext.xml中移出来了,所以属性初始化放在属性声明上进行了。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("p")//注意添加bean.name
public class Product {
    private String name="product 1";  //初始化
    @Autowired
    private Category category;

AOP的注解

@Aspect //表示这是一个切面

@Around

@Around(value = "execution(* com.how2java.service.ProductService.*(..))")
//表示对com.how2java.service.ProductService 这个类中的所有方法进行切面操作

AOP

AOP 即 Aspect Oriented Program 面向切面编程
首先,在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能。
所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务
所谓的周边功能,比如性能统计,日志,事务管理等等

周边功能在Spring的面向切面编程AOP思想里,即被定义为切面

在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发
然后把切面功能和核心业务功能 “编织” 在一起,这就叫AOP

要执行的核心方法拦截, 在核心方法前后添加日志等通用周边功能(周边功能单独在一个Aspect.java文件, Spring配置文件中aop:config 调用之).

<bean id="loggerAspect" class="com.how2java.aspect.LoggerAspect"/>   
	<aop:config>
		<aop:pointcut id="loggerCutpoint" 
			expression=
			"execution(* com.how2java.service.ProductService.*(..)) "/>
			
		<aop:aspect id="logAspect" ref="loggerAspect">
			<aop:around pointcut-ref="loggerCutpoint" method="log"/>
		</aop:aspect>
	</aop:config>

切入点, 监听指定方法, 该方法被调用时拦截之, 在其前后执行Aspect功能.

XML方式配置AOP 改造为注解方式

@Aspect //表示这是一个切面
@Component //表示这是一个bean,由Spring进行管理
public class LoggerAspect {    
    @Around(value = "execution(* com.how2java.service.ProductService.*(..))")
    //表示对com.how2java.service.ProductService 这个类中的所有方法进行切面操作
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("start log:" + joinPoint.getSignature().getName());
        Object object = joinPoint.proceed();
        System.out.println("end log:" + joinPoint.getSignature().getName());
        return object;
    }
}

配置文件中, 去掉原有的bean和aspect信息,添加如下3行

<context:component-scan base-package="com.how2java.aspect"/>
<context:component-scan base-package="com.how2java.service"/>
//扫描包com.how2java.aspect和com.how2java.service,定位业务类和切面类 
<aop:aspectj-autoproxy/>    	//找到被注解了的切面类,进行切面配置