Spring AOP与IoC

Spring框架简介

Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。简单来说,Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。

​  spring的基本框架主要包含六大模块:DAO、ORM、AOP、JEE、WEB、CORE

img

Spring DAO:Spring提供了对JDBC的操作支持:JdbcTemplate模板工具类 。

Spring ORM:Spring可以与ORM框架整合。例如Spring整合Hibernate框架,其中Spring还提供HibernateDaoSupport工具类,简化了Hibernate的操作 。

Spring WEB:Spring提供了对Struts、Springmvc的支持,支持WEB开发。与此同时Spring自身也提供了基于MVC的解决方案 。

Spring AOP:Spring提供面向切面的编程,可以给某一层提供事务管理,例如在Service层添加事物控制 。

Spring JEE:J2EE开发规范的支持,例如EJB 。

Spring Core:提供IOC容器对象的创建和处理依赖对象关系 。

IoC

概述

IOC(Inversion of Control),就是具有依赖注入功能的容器,是可以创建对象的容器,IOC容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。通常new一个实例,控制权由程序员控制,即“控制反转”,是指new实例工作不由程序员来做而是交给Spring容器来做。。在Spring中BeanFactory是IOC容器的实际代表者。另外一种说法叫DI(Dependency Injection),即依赖注入。在容器创建对象后,处理对象的依赖关系。它并不是一种技术实现,而是一种设计思想。在任何一个有实际开发意义的程序项目中,我们会使用很多类来描述它们特有的功能,并且通过类与类之间的相互协作来完成特定的业务逻辑。这个时候,每个类都需要负责管理与自己有交互的类的引用和依赖,代码将会变的异常难以维护和极度的高耦合。而IOC的出现正是用来解决这个问题,我们通过IOC将这些相互依赖对象的创建、协调工作交给Spring容器去处理,每个对象只需要关注其自身的业务逻辑关系就可以了。在这样的角度上来看,获得依赖的对象的方式,进行了反转,变成了由spring容器控制对象如何获取外部资源(包括其他对象和文件资料等等)。

IoC思想

在传统实现中,我们都是通过应用程序自己来管理依赖的创建,例如下代码.

1
2
3
4
5
6
7
public class Person {
// 由Person自己管理Food类的创建
public void eat() {
Food food = new Chicken();
System.out.println("I am eating " + food.getName() + "...");
}
}

IoC则是通过一个第三方容器来管理并维护这些被依赖对象,应用程序只需要接收并使用IoC容器注入的对象而不需要关注其他事情.

1
2
3
4
5
6
7
8
9
10
11
public class Person {
private Food food;
// 通过set注入
public void setFood(Food food) {
this.food = food;
}
// Person不需要关注Food,只管使用即可
public void eat() {
System.out.println("I am eating " + this.food.getName() + "...");
}
}

控制反转其实就是对象控制权的转移,应用程序将对象的控制权转移给了第三方容器并通过它来管理这些被依赖对象,完成了应用程序与被依赖对象的解耦.

注入方式

  • set注入方式:实现特定属性的public set()方法,来让IoC容器调用注入所依赖类型的对象.
  • 静态工厂注入方式: 实现特定接口以供IoC容器注入所依赖类型的对象.
  • 构造方法注入方式 : 实现特定参数的构造函数,在创建对象时来让IoC容器注入所依赖类型的对象.
  • 基于注解的方式: 通过Java的注解机制来让IoC容器注入所依赖类型的对象,例如Spring框架中的@Autowired.

set注入方式

控制层

1
2
3
4
5
private OrderServiceImp orderService;

public void setOrderService(OrderServiceImp orderService) {
this.orderService = orderService;
}

Spring配置XML文件:其中配置声明OrderAction类存在属性orderService。程式运行时候,会将已经实例化的orderService对象调用setOrderService方式注入。

1
2
3
4
<bean name="orderAction" class="com.pec.action.OrderAction">
<property name="orderService" ref="orderService"></property>
</bean>
<bean name="orderService" class="com.pec.service.imp.OrderServiceImp"></bean>

构造器注入方式

控制层

1
2
3
4
5
private OrderServiceImp orderService;

public OrderAction(OrderServiceImp orderService) {
this.orderService = orderService;
}

XML

1
2
3
4
<bean name="orderAction" class="com.pec.action.OrderAction">
<constructor-arg ref="orderService"></constructor-arg>
</bean>
<bean name="orderService" class="com.pec.service.imp.OrderServiceImp"></bean>

基于注解的方式(推荐)

控制层

1
2
@Autowired   //@Resource
private OrderServiceImp orderService;

服务层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Service("orderService")
public class OrderServiceImp implements IOrderService {

@Autowired
private JavaOrderMDaoImp javaOrderMDao;

@Autowired
private JavaOrderDDaoImp javaOrderDDao;

@Override
public List<JavaOrderMList> findOrderM(OrderSearch search) {
return javaOrderMDao.findJavaOrderM(search);
}

@Override
public List<JavaOrderDList> findOrderD(OrderSearch search) {
return javaOrderDDao.findJavaOrderD(search);
}

}

DAO

1
2
3
4
5
@Repository("javaOrderMDao")
public class JavaOrderMDaoImp extends BaseHibernateDAO<JavaOrderM, Serializable> implements IJavaOrderMDao {...}

@Repository("javaOrderDDao")
public class JavaOrderDDaoImp extendsBaseHibernateDAO<JavaOrderD, Serializable> implements IJavaOrderDDao {...}

⑴ 持久层DAO层注解Repository中规定了名称,在Service层中声明名称必须一致。

⑵ 服务层Service层注解Service中规定了名称,在控制层中声明的名称必须一致。

⑶ 注解方式注入依赖注解:

1
2
3
4
5
6
7
@Component         把对象加入ioc容器,对象引用名称是类名,第一个字母小写
@Component(“name”) 把指定名称的对象,加入ioc容器
@Repository 主要用于标识加入容器的对象是一个持久层的组件(类)
@Service 主要用于标识加入容器的对象是一个业务逻辑层的组件
@Controller 主要用于标识加入容器的对象是一个控制层的组件
@Resource 注入属性(DI), 会从容器中找对象注入到@Resource修饰的对象上
@Autowired 注入属性(DI), 会从容器中找对象注入到@Autowired修饰的对象上

⑷ 注解可以简化配置,提升开发效率,但是也不利于后期维护。

AOP

概述

AOP(Aspect-OrientedProgramming),即“面向切面编程”。它是一种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想.用于切入到指定类指定方法的代码片段叫做切面,而切入到哪些类中的哪些方法叫做切入点. 往往被定义为促使软件系统实现关注点的分离的技术。系统是由许多不同的组件所组成的,每一个组件各负责一块特定功能。除了实现自身核心功能之外,这些组件还经常承担着额外的职责。例如日志、事务管理和安全这样的核心服务经常融入到自身具有核心业务逻辑的组件中去。这些系统服务经常被称为横切关注点,因为它们会跨越系统的多个组件。

AOP名词解释

切面(Aspect):其实就是共有功能的实现。如日志切面、权限切面、事务切面等。在实际应用中通常是一个存放共有功能实现的普通Java类,之所以能被AOP容器识别成切面,是在配置中指定的。

通知(Advice):是切面的具体实现。以目标方法为参照点,根据放置的地方不同,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)与环绕通知(Around)5种。在实际应用中通常是切面类中的一个方法,具体属于哪类通知,同样是在配置中指定的。

连接点(Joinpoint):就是程序在运行过程中能够插入切面的地点。例如,方法调用、异常抛出或字段修改等,但Spring只支持方法级的连接点。

切入点(Pointcut):用于定义通知应该切入到哪些连接点上。不同的通知通常需要切入到不同的连接点上,这种精准的匹配是由切入点的正则表达式来定义的。

目标对象(Target):就是那些即将切入切面的对象,也就是那些被通知的对象。这些对象中已经只剩下干干净净的核心业务逻辑代码了,所有的共有功能代码等待AOP容器的切入。

代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象的功能等于目标对象的核心业务逻辑功能加上共有功能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。

织入(Weaving):将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译期、类装载期及运行期,当然不同的发生点有着不同的前提条件。譬如发生在编译期的话,就要求有一个支持这种AOP实现的特殊编译器;发生在类装载期,就要求有一个支持AOP实现的特殊类装载器;只有发生在运行期,则可直接通过Java语言的反射机制与动态代理机制来动态实现。

案例分析

OOP中,我们使用封装的特性来将不同职责的代码抽象到不同的类中.但是在分散代码的同时,也增加了代码的重复性.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class A {
public void something () {
// 业务逻辑...
recordLog();
}
private void recordLog() {
// 记录日志...
}
}
public class B {
public void something () {
// 业务逻辑...
recordLog();
}
private void recordLog() {
// 记录日志...
}
}

使用AOP分离外围业务代码

我们使用AspectJ,它是一个AOP框架,扩展了Java语言,并定义了AOP语法(通过它实现的编译器).

使用AspectJ需要先安装并将lib中aspectjrt.jar添加进入classpath.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* AOP解决了代码的重复并将这些外围业务代码抽离到一个切面中,我们可以动态地将切面切入到切入点.
*/
public class Something {
public void say() {
System.out.println("Say something...");
}
public static void main(String[] args) {
Something something = new Something();
something.say();
}
}
public aspect SomethingAspect {
/**
* 切入点,切入到Something.say()
*/
pointcut recordLog():call(* com.sun.sylvanas.application.hello_aop.Something.say(..));
/**
* 在方法执行后执行
*/
after():recordLog() {
System.out.println("[AFTER] Record log...");
}
}

事务管理

Spring使用AOP配置事务管理由三个部分组成,分别是DataSourceTransactionManager代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制这部分。DataSource、TransactionManager这两部分只是会根据数据访问方式有所变化,比如使用hibernate进行数据访问时,DataSource实际为SessionFactory,TransactionManager的实现为HibernateTransactionManager。

http://ov1nop9io.bkt.clouddn.com/644566-20160905080625207-2085618106.png

spring事务配置的五种方式:每个Bean都有一个代理、所有Bean共享一个代理基类、使用拦截器、使用tx标签配置的拦截器、全注解

1、使用tx标签配置的拦截器

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<!--4、配置hibernate属性 -->
<!--引入db.properties属性文件 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:db.properties"></property>
</bean>
<!-- 配置数据源,连接池使用c3p0,详细信息参见hibernate官方文档"基础配置章节" -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close" dependency-check="none">
<property name="driverClass">
<value>${datasource.driverClassName}</value>
</property>
<property name="jdbcUrl">
<value>${datasource.url}</value>
</property>
<property name="user">
<value>${datasource.username}</value>
</property>
<property name="password">
<value>${datasource.password}</value>
</property>
<property name="acquireIncrement">
<!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
<value>${c3p0.acquireIncrement}</value>
</property>
<property name="initialPoolSize">
<!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
<value>${c3p0.initialPoolSize}</value>
</property>
<property name="minPoolSize">
<!--连接池中保留的最小连接数。 -->
<value>${c3p0.minPoolSize}</value>
</property>
<property name="maxPoolSize">
<!--连接池中保留的最大连接数。Default: 15 -->
<value>${c3p0.maxPoolSize}</value>
</property>
<property name="maxIdleTime">
<!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<value>${c3p0.maxIdleTime}</value>
</property>
<property name="idleConnectionTestPeriod">
<!--每60秒检查所有连接池中的空闲连接。Default: 0 -->
<value>${c3p0.idleConnectionTestPeriod}</value>
</property>
<property name="maxStatements">
<!-- JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。
如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0 -->
<value>${c3p0.maxStatements}</value>
</property>
<property name="numHelperThreads">
<!-- C3P0是异步操作的,缓慢的JDBC操作通过帮助进程完成。扩展这些操作可以有效的提升性能, 通过多线程实现多个操作同时被执行。Default:
3 -->
<value>${c3p0.numHelperThreads}</value>
</property>
</bean>

<!--配置 sessionFactory -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource">
</property>
<!-- hibernate的设置 -->
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql"> ${hibernate.show_sql} </prop>
<prop key="hibernate.jdbc.fetch_size">${hibernate.jdbc.fetch_size}</prop>
<prop key="hibernate.jdbc.batch_size">${hibernate.jdbc.batch_size}</prop>
<prop key="hibernate.connection.release_mode">${hibernate.connection.release_mode}</prop>
<prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
<prop key="hibernate.connection.SetBigStringTryClob">true</prop>
</props>
</property>
<!-- anotation注解扫描实体类 -->
<property name="packagesToScan">
<list>
<value>com.pec.model</value>
</list>
</property>
</bean>

<!--5、Spring 配置声明式事物 -->

<!-- 配置事务 -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>

<!-- 配置事务范围 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="false" propagation="NOT_SUPPORTED" />
<tx:method name="find*" read-only="false" propagation="NOT_SUPPORTED" />
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="create*" propagation="REQUIRED" />
<tx:method name="anscy*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>

<!-- 定义切面 -->
<aop:config proxy-target-class="true">
<aop:pointcut id="pointcut" expression="execution(* com.pec.service..*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" />
</aop:config>

⑴ pointcut中的三个”“中,第一个代表返回值,第二代表service下子包,第三个代表方法名,“(..)”代表方法参数。

⑵ 此时配置的切点在Service层,方法命名需要按照以上advice通知点开头命名。

⑶ 按照规定命名方法,会受到Spring事务的管控,保持操作的一致性。例如向数据库插入100条数据,前面99条记录都正常执行直至第100条出现错误,则事务管控会回滚到执行前的初始状态。

2、使用Bean代理

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"
default-autowire="default">

<!-- <bean name="userManager" class="com.tgb.manager.UserManagerImpl"></bean>
<bean name="userController" class="com.tgb.web.UserController">
<property name="userManager" ref="userManager"></property>
</bean> -->
<context:component-scan base-package="com.tgb.dao" />
<context:component-scan base-package="com.tgb.entity" />
<context:component-scan base-package="com.tgb.manager" />
<context:component-scan base-package="com.tgb.web" />

<!-- 引入init.properties中属性 -->
<bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:config/spring/jdbc.properties</value>
</list>
</property>
</bean>

<!-- 配置数据源,连接池使用c3p0,详细信息参见hibernate官方文档"基础配置章节" -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<property name="driverClass" value="${datasource.driverClassName}"></property>
<property name="jdbcUrl" value="${datasource.url}"></property>
<property name="user" value="${datasource.username}"></property>
<property name="password" value="${datasource.password}"></property>
</bean>

<!-- 配置SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
${hibernate.dialect}
</prop>
<prop key="hibernate.show_sql">
${hibernate.show_sql}
</prop>
<prop key="hibernate.jdbc.fetch_size">
${hibernate.jdbc.fetch_size}
</prop>
<prop key="hibernate.jdbc.batch_size">
${hibernate.jdbc.batch_size}
</prop>
<prop key="hibernate.connection.release_mode">
${hibernate.connection.release_mode}
</prop>
<prop key="hibernate.format_sql">
${hibernate.format_sql}
</prop>
<prop key="hibernate.connection.SetBigStringTryClob">true</prop>
</props>
</property>
<!-- anotation注解扫描实体类 -->
<property name="annotatedClasses">
<list>
<value>com.tgb.entity.User</value>
</list>
</property>
</bean>

<!-- 配置一个事务管理器 将事务与Hibernate关联-->
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>

<!-- 配置事务范围,使用代理的方式 -->
<bean id="transactionProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true">
<!-- 为事务代理bean注入事务管理器-->
<property name="transactionManager" >
<ref bean="transactionManager"/>
</property>
<!--设置事务属性范围-->
<property name="transactionAttributes">
<props>
<prop key="add*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="get">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="update*">PROPAGATION_REQUIRED,-myException</prop>
<prop key="del*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>

<bean id="userDao" class="com.tgb.dao.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>

<bean id="userManagerBase" class="com.tgb.manager.UserManagerImpl">
<property name="userDao" ref="userDao"></property>
</bean>

<!-- 此处为代理 -->
<bean name="userManager" parent="transactionProxy">
<property name="target" ref="userManagerBase"></property>
</bean>

</beans>