SpringAOP

Spring的核心思想就是IOC和AOP

spring的AOP(面向切面)

  1. SpringAOP 详解(aop思想,aop利用代理的想法,代理模式.以及Java提供的动态代理和CGLIB库提供的继承性代理)
  2. SpringAOP 的事务管理(如何使用spring管理事务。事务的传播性)

SpringAop详解

  1. Aop的核⼼思想叫做⾯向切⾯编程,它是⼀种⾯对横向业务流提出的⼀种解耦⽅案(何为aop,就好⽐法式⾯包.你想加点葡萄,花⽣仁什么的,你不可能因为你想吃什么,⽽重新做.你可以⽤到刀切成⼏块,在每块当中镶嵌进去)
  2. 可能会有⼈说,这已经改变的物理结构本⾝发⽣的变化,现实当中不好实现,但是软件开发中,我们作为上帝,那可以随时的组装和实现
  3. 如何实现?从这个例⼦来说,我们可以看到想⾃⼰想加什么就加什么,不想加的时候,又能随时⽅便的去掉.又不承担任何风险.现实当中,我们就要不断的去克隆和复制同⼀块物体,以保证风控程度是接近于0.所以在软件的设计开发当中,就是需要我们考虑的⼀个问题(如何解耦)
  4. 所以我们需要使⽤⼀种称作为代理的机制.

代理顾名思义便是帮XXX去相关的事,好比彩票你自己买不了,你需要要彩票的代理点帮你买

回过来,我们用专业的计算术语来解释,功能之间是否能够相互独立,能否保证代码不入侵,能达到同样的效果

代码当中体现,能不能尽量在不改动代码的情况下,只以添加类和删减类的插播方式,达到随用随取

代理模式的实现:

  1. 一共有3个人称出现.代理角色,真实角色,代理角色和抽象角色共同的行为方式。
  2. 代理角色有真实角色的行为引用

代码示例

1
2
3
4
5
6
7
8
/**
* 代理角色和真实角色共同的行为
* @author Yun
*
*/
public interface BuyCaiPiao {
void buy500w();
}
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 真实角色
* @author Yun
*
*/
public class RealRole implements BuyCaiPiao{
@Override
public void buy500w() {
// TODO Auto-generated method stub
System.out.println("自己买彩票");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 代理角色,代理角色需要有真实角色的引用
* @author Yun
*
*/
public class ProxyRole implements BuyCaiPiao {
private BuyCaiPiao bcp;
public ProxyRole(BuyCaiPiao bcp) {
// TODO Auto-generated constructor stub
this.bcp = bcp;
}
@Override
public void buy500w() {
// TODO Auto-generated method stub
bcp.buy500w();
}

}
1
2
3
4
5
6
7
8
9
10
11
12
public class TestProxy {
public static void main(String[] args) {
//构建真实角色
RealRole r = new RealRole();
//构建代理角色(传入需要被代理的角色)
ProxyRole p = new ProxyRole(r);
//代理角色执行 (这件事是发生在代理身上的,也就意味着风险的承担方是代理角色)
//从侧方面也可以看到(我们可以额外的在附加其它的动作,而这些都是发生在代理角色身上)
//从代码方面看来,是否是我们尽量做类的代码增或者减,尽量的去避免源对象的改变
p.buy500w();
}
}

代理模式的深入思考

  1. 从上面的代码看,可以发现,如果我们想代理其它的真实角色,那么不说我们需要不断的去扩充接口,扩充真实的代理角色
  2. 是否有一种方式,可以动态的实现真实的角色想要实现的动作(其实就是利用我们的反射机制)

jdk支持的动态代理方式(实现步骤)

  1. 同样的构造接口和真实的角色
1
2
3
4
5
6
7
8
9
/**
* 行为接口
* @author Yun
*
*/
public interface InterPerson {
void sayA();
void sayB(String s);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 真实的角色
* @author Yun
*
*/
public class RealPerson implements InterPerson{

@Override
public void sayA() {
// TODO Auto-generated method stub
System.out.println("sayA");
}

@Override
public void sayB(String s) {
// TODO Auto-generated method stub
System.out.println("sayB");
}

}

2.实现动态代理特征接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 动态代理接口
* @author Yun
*
*/
public class Myhanlder implements InvocationHandler{

//需要被代理的真实角色
private Object target;

public Myhanlder(Object obj) {
// TODO Auto-generated constructor stub
this.target = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
//执行真实角色拥有的方法 ,args代表如果有参数执行有参数
Object o = method.invoke(target, args);
return o;
}
}
  1. 通过接口特征构造动态代理对象
1
2
3
4
5
6
7
8
9
10
public class TestJdkProxy {
public static void main(String[] args) {
//构建真实角色
InterPerson ip = new RealPerson();
//通过proxy类进行代理角色创建
InterPerson iproxy = (InterPerson) Proxy.newProxyInstance(InterPerson.class.getClassLoader(),new Class[] {InterPerson.class} , new Myhanlder(ip));
iproxy.sayA();
iproxy.sayB("ahha");
}
}

使用cglib的方式

如果只有类,主要通过对字节码的操作.以继承的方式对原有类进行扩展

  1. 构建真实角色
1
2
3
4
5
6
7
8
9
public class NewRole {

public void go(){
System.out.println("回家");
};
public void gohome(){
System.out.println("回重庆");
};
}
  1. 构建代理拦截器
1
2
3
4
5
6
7
8
9
10
11
12
13
public class CglibProxy implements MethodInterceptor {

/**
* 通过拦截真实对象的方法
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// TODO Auto-generated method stub
Object obj = methodProxy.invokeSuper(o, objects);
return obj;
}

}
  1. 生成代理对象
1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
//构建一个增强类
Enhancer eh = new Enhancer();
// 增强类需要设置被代理的类型。以及代理的方法的回调
eh.setSuperclass(NewRole.class);
eh.setCallback(new CglibProxy());
//构建代理类
NewRole nr = (NewRole) eh.create();
nr.go();
nr.gohome();

}

spring利用aop机制的实现

名词概念:

  1. Aspect:切面,由一系列切点、增强和引入组成的模块对象,可定义优先级,从而影响增强和引入的执行顺序
  2. Join point:接入点,程序执行期的一个点,例如方法执行、类初始化、异常处理(一般用来获取方法中的一些元数据)
  3. Advice:增强,切面在特定接入点的执行动作,包括 “around,” “before” and “after”等多种类型
  4. Pointcut:切点,用来匹配特定接入点的谓词(表达式)
  5. Weaving:织入,将一个或多个切面与类或对象链接在一起创建一个被增强对象(也就是构建代理对象的过程)

通知名词:

  1. 前置通知 在目标方法执行之前执行执行的通知
  2. 后置通知 在目标方法执行之后执行的通知 (出现异常便不再调用)
  3. 环绕通知 在目标方法执行之前和之后都可以执行额外代码的通知。
  4. 异常通知 在目标方法抛出异常时执行的通知
  5. 最终通知 是在目标方法执行之后执行的通知。

通知的应用场景:

IMAGE

实现方式:

  1. 构建真实角色
1
2
3
4
5
6
7
8
public class UserServiceImpl   implements  UserService{

@Override
public void save() {
// TODO Auto-generated method stub
System.out.println(1/0);
}
}
  1. 定义增强类
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
package com.wwj.springaop;

import org.aspectj.lang.ProceedingJoinPoint;

public class Myadvice {
public void before() {
System.out.println(" 这是前置通知! ");
}

// 后置通知
public void afterReturning() {
System.out.println(" 这是后置通知 ( 如果出现异常不会调用 )");
}

// 环绕通知
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println(" 这是环绕通知之前的部分! ");
Object proceed = pjp.proceed();// 调用目标方法
System.out.println(" 这是环绕通知之后的部分! ");
return proceed;
}

// 异常通知
public void afterException() {
System.out.println(" 异常出现了! ");
}

// 最终通知
public void after() {
System.out.println(" 这是后置通知 ( 出现异常也会调用 )");
}
}
  1. 配置xml 实例化角色 增强类 以及切面,和织入的过程
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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans" 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-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd ">

<!-- 1.构建需要被代理的对象 -->
<bean name="userService" class="com.wwj.springaop.UserServiceImpl"></bean>
<!-- 2. 构建需要增强的方法 -->
<bean name="myAdvice" class="com.wwj.springaop.Myadvice"></bean>
<!-- 3. 定义aop -->
<!-- aop 配置 -->
<aop:config>
<!-- 自定义切入点 -->
<!--
1、execution(): 表达式主体。

2、第一个*号:表示返回类型,*号表示所有的类型。

3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包。

4、第二个*号:表示类名,*号表示所有的类。

5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
-->
<aop:pointcut expression="execution(* com.wwj.springaop..*.*(..))" id="anyMethod"/>

<aop:aspect ref="myAdvice">
<!-- 前置通知 -->
<!-- 测试自定义切入点 -->
<aop:before method="before" pointcut-ref="anyMethod"/>
<!-- 最终通知 -->
<aop:after method="after" pointcut-ref="anyMethod"/>
<!-- 后置通知 -->
<aop:after-returning method="afterReturning" pointcut-ref="anyMethod"/>
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="anyMethod"/>
<!-- 异常抛出通知 -->
<aop:after-throwing method="afterException" pointcut-ref="anyMethod"/>
</aop:aspect>
</aop:config>
</beans>

4.测试执行

1
2
3
4
5
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("application3.xml");
UserService userService = (UserService) ac.getBean("userService");
userService.save();
}

SpringAOP进行事务管理 (如何使用spring管理事务。事务的传播性)

通过上面的例子,我们可以看到即将接触到的事务采用环绕通知的方式为最佳.因为全程都在跟进

何为事务的传播性

  1. 当服务被定义,就有可能会有事务的产生
  2. 服务和服务之间相互调用,就会产生我们称作为事务的传播
  3. 事务的传播存在于多个服务相互调用,要保证遵循满足首次产生事务的的ACID

举例说明

  1. 转账是一个服务,扣款是一个服务(转账的时候需要调用扣款的服务就构成了)
  2. 转账的时候,扣款出了问题,就需要回滚到首次事务,来保证事务的完整性

代码说明(未加入事务)

  1. mysql数据库一张账户表(李四余额1000,张三余额1000) 张三转账给李四1000(动作)

代码示例

  1. 构建dao层以及接口
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

public interface AccountDao {

void updateAddMoney(int aid,int money);

void updateDeleteMoney(int aid,int money);

}
//分隔

@Repository(value="acDao")
public class AccountDaoImpl implements AccountDao{

@Resource
private JdbcTemplate jdbcTemplate;

@Override
public void updateAddMoney(int aid, int money) {
// TODO Auto-generated method stub
jdbcTemplate.update("update t_account set money=money-? where aid=?",money,aid);
}

@Override
public void updateDeleteMoney(int aid, int money) {
// TODO Auto-generated method stub
jdbcTemplate.update("update t_account set money=money+? where aid=?",money,aid);
}

}
  1. 构建服务接口和接口实现

扣款

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
/**
* 收款
* @author Yun
*
*/
public interface ReciveMoney {
/**
*
* @param aid 接收人id
* @param money 接收人金额
*/
void Recivemoney(int aid,int money);
}
//分隔
@Service("recs")
public class ReciveMoneyImpl implements ReciveMoney{

@Autowired
private AccountDao acDao;

@Override
public void Recivemoney(int aid, int money) {
// TODO Auto-generated method stub
acDao.updateDeleteMoney(aid, money);
}

}

转账

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
/**
*
* @author Yun
* 扣款
*/
public interface PayMoney {

/**
*
* @param aid 扣款人id
* @param rid 收款人id
* @param money 扣款人金额
*/
void paymoney(int aid,int money,int rid);
}
//分隔
@Service("pays")
public class PaymoneyImpl implements PayMoney{

@Autowired
private AccountDao acDao;

@Autowired
private ReciveMoney recs;

@Override
public void paymoney(int aid, int money,int rid) {
acDao.updateAddMoney(aid, money);
//构成了事务的传播性
recs.Recivemoney(rid, money);
}

}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component(value="testTX")
public class TestSpringTX {

@Autowired
private PayMoney pays;

public void test1(){
pays.paymoney(1,1000, 2);
}

public static void main(String[] args) {

ApplicationContext ac=new ClassPathXmlApplicationContext("application4.xml");
TestSpringTX tx = (TestSpringTX) ac.getBean("testTX");
tx.test1();
}}

金额变动假定没有出现任何异常的情况

IMAGE

==================>

IMAGE

假定扣款出现问题,模拟sql语句执行错误

IMAGE

你会发现,钱扣了,但钱没到账

SpringAOP的事务管理(采用spring进行事务的管理)

  1. spring管理事务2种方式
  • 声明式事务 (全局管理事务 可以采用xml方式 或者是 @Transactional 注解的类级别支持和方法的级别)
  • 编程式事务 (spring推荐使用TransactionTemplate)类似在jdbc中开启事务(了解)

一个功能是否要事务,必须纳入设计.编码考虑.不能仅仅完成了基本功能就ok

  1. spring使用声明式事务
  • 基于XML
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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd ">
<!-- 自动根据扫描对应包下面的注解,并实例化对应的对象 -->
<context:component-scan base-package="com.wwj.springtx"></context:component-scan>

<!-- 连接管理交给C3P0 -->
<bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>

<!--JDBCTemplate 需要 datasource 连接池 -->
<bean name="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 1使用spring事务管理管理数据源操作 -->
<bean name="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 2.定义需要被增强的方法,也就是那些需要介入事务的管理 -->
<!--
propagation:REQUIRED(依赖) 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
isolation: 隔离级别
read-only: 是否只读
-->
<tx:advice id="advice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="pay*" propagation="REQUIRED" /><!-- *是对所有方法都加 -->
</tx:attributes>
</tx:advice>

<!-- 3.配置切入点,切哪 也就是 织入的过程 -->
<!-- 配置织入 -->
<aop:config >
<!-- 配置切点表达式 -->
<aop:pointcut expression="execution(* com.wwj.springtx..*.*(..))" id="txPc"/>
<!-- 配置切面 : 通知+切点
advice-ref:通知的名称
pointcut-ref:切点的名称
-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPc" />
</aop:config>
</beans>
  • 基于注解的方式进行

IMAGE

  1. 直接在方法或者类上面加:

@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.REPEATABLE_READ,timeout=-1)

  1. xml中加入
1
2
3
4
5
6
7
8
<!-- 1使用spring事务管理管理数据源操作 -->
<bean name="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 支持事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>