springAop
分析经典转账案例中的问题及解决办法
ThreadLocal:线程局部变量,保证同一个线程使用同一个连接保证同一个线程使用同一个连接
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;
//自己编写的此类,让大家明白。jdk中存在一个ThreadLocal
@Component
public class ThreadLocal {
private static Map<Thread,Connection> map = new HashMap<Thread, Connection>();
//以当前线程对象为key,向Map中存数据
public void set(Connection conn){
map.put(Thread.currentThread(),conn);
}
//以当前线程对象为key,从Map中获取数据
public Connection get(){
return map.get(Thread.currentThread());
}
//以当前线程对象为key,从Map中删除数据
public void remove(){
map.remove(Thread.currentThread());
}
}
编写一个专门控制事务的类:事务管理器
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
//简易事务管理器
@Component
public class TransactionManager {
@Autowired
private ThreadLocal threadLocal;
@Autowired
private DataSource dataSource;
//如果当前线程上有连接,就直接用;
// 没有连接从池中获取一个连接,并绑定到当前线程上
public Connection getConnection(){
try {
Connection conn = threadLocal.get();
if(conn==null){
conn = dataSource.getConnection();
threadLocal.set(conn);
}
return conn;
} catch (SQLException e) {
throw new RuntimeException("从数据源获取连接失败,请检查您的数据源",e);
}
}
public void startTransaction(){
try {
Connection connection = getConnection();
connection.setAutoCommit(false);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public void commit(){
try {
Connection connection = getConnection();
connection.commit();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public void rollback(){
try {
Connection connection = getConnection();
connection.rollback();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public void release(){
try {
Connection connection = getConnection();
connection.close();
threadLocal.remove();// 从当前线程上解绑
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
改造转账案例:目标能控制事务
step1、修改spring配置文件
不给定数据源
step2、改造dao实现
step3、改造业务实现
import com.zl.commons.TransactionManager;
import com.zl.dao.AccountDao;
import com.zl.domain.Account;
import com.zl.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service //交给spring容器管理
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Autowired
private TransactionManager txManager;
public void transfer(String sourceAccountNumber, String targetAccountNumber, Float money) {
try{
txManager.startTransaction();//开启事务
//根据转出账户查询Account对象
Account sAccount = accountDao.findAccountByNumber(sourceAccountNumber);
//根据注入账户查询Account对象
Account tAccount = accountDao.findAccountByNumber(targetAccountNumber);
//转出账户对象减money
sAccount.setBalance(sAccount.getBalance()-money);
//转入账户对象加money
tAccount.setBalance(tAccount.getBalance()+money);
//持久化转出账户
accountDao.updateAccount(sAccount);
int i=1/0;//模拟出现异常
//持久化转入账户
accountDao.updateAccount(tAccount);
txManager.commit();//提交事务
}catch (Exception e){
//回滚事务
txManager.rollback();
throw new RuntimeException(e);
}finally {
//释放资源
txManager.release();
}
}
}
分析以上代码问题(AOP思想)
我们最关心的就是核心业务代码,精力也应该集中在解决业务问题上。而不是天天编写重复的事务代码。
动态代理实现面向切面编程
step1、修改业务代码
step2、借助动态代理实现事务代码织入到核心业务代码中(增强)
- 基于接口的动态代理(JDK代理:Proxy):被代理类和代理类实现相同的接口。即被代理类必须实现至少一个接口。
import com.zl.commons.TransactionManager;
import com.zl.domain.Account;
import com.zl.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//产生代理对象的工厂
@Component
public class ProxyBeanFactory {
@Autowired
private TransactionManager txManager;
@Autowired
@Qualifier("accountServiceImpl") //由于spring容器中会存在多个AccountService类型的对象,此处最好用名称查找
private AccountService accountService;//原对象,被代理对象
//基于接口的动态代理:代理类和被代理类都实现了AccountService接口
@Bean //将方法的返回对象存到spring容器中,默认名字是getInterfaceProxyObject
public AccountService getInterfaceProxyObject(){
return (AccountService) Proxy.newProxyInstance(
accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object rtValue = null;
if("transfer".equals(method.getName())) {//如果当前执行的方法是转账,才控制事务
try {
txManager.startTransaction();//开启事务
rtValue = method.invoke(accountService, args);//代表原核心业务代码
txManager.commit();
}catch (Exception e){
txManager.rollback();
throw new RuntimeException(e);
}finally {
txManager.release();
}
}else{
rtValue = method.invoke(accountService,args);//其他发放不增强
}
return rtValue;
}
}
);
}
}
- 基于子类的动态代理:被代理类没有实现任何接口,代理类是被代理类的子类。借助第三方的开发包(CGLIB,不需要单独导入,spring内置了)
import com.zl.commons.TransactionManager;
import com.zl.domain.Account;
import com.zl.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//产生代理对象的工厂
@Component
public class ProxyBeanFactory {
@Autowired
private TransactionManager txManager;
@Autowired
@Qualifier("accountServiceImpl") //由于spring容器中会存在多个AccountService类型的对象,此处最好用名称查找
private AccountService accountService;//原对象,被代理对象
//利用CGLIB实现基于子类的动态代理。代理类是被代理类的子类
@Bean 将方法的返回对象存到spring容器中,默认名字是getSubClassProxyObject
public AccountService getSubClassProxyObject(){
return (AccountService) Enhancer.create(
accountService.getClass(),// 代理类的父类是什么类型
new org.springframework.cglib.proxy.InvocationHandler() {//和JDK中的InocationHandler雷同
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object rtValue = null;
if("transfer".equals(method.getName())) {//如果当前执行的方法是转账,才控制事务
try {
txManager.startTransaction();//开启事务
rtValue = method.invoke(accountService, args);//代表原核心业务代码
txManager.commit();
}catch (Exception e){
txManager.rollback();
throw new RuntimeException(e);
}finally {
txManager.release();
}
}else{
rtValue = method.invoke(accountService,args);//其他发放不增强
}
return rtValue;
}
}
);
}
注意:如果使用spring的AOP,我们不需要自己编写代理代码。spring会自动生成代理对象,如果被代理类实现了某个或某些接口,spring将使用JDK的动态代理;如果被代理类没有实现任何接口,spring将采用CGLIB基于子类的动态代理方式。而这些是不需要我们关心的。
AOP面向切面编程
编码阶段要做到:我们应该做到的
1、核心代码单独编写
2、非核心代码(切面代码)单独编写
运行阶段要做到:交给spring完成(配置),我们自己不需要写代理
核心代码和非核心代码能一起运行,完成需要的功能
spring framework中的AOP
常用概念
1、连接点(joinpoint):指代码中的所有方法,都做连接点。
2、切入点(pointcut):指代码中需要增强的方法,叫做切入点。
3、被代理对象:只包含核心业务代码的对象,需要增强的对象。
4、代理对象:由spring产生的代理对象,他对被代理对象进行了一定的功能增强。
5、通知(advice):即增强对象(比如案例中TransactionManager事务管理器,那些切面代码)。
6、切面(Aspect):通知(Advice)+切入点(Pointcut)=切面
7、织入(Weaving):将通知的代码运行期间织入到被代理对象中,这是一个动作。
切入点表达式
单独定义表达式:
表达式的写法:
五大通知
四个基本通知
重点:明确不同的通知的执行位置
前置通知(before):核心代码执行前执行。
后置通知(afterReturning):核心代码没有任何问题,正常返回后执行。
异常通知(afterThrowing):核心代码运行期间发生了异常,此时catch中的代码执行,异常通知就认为在异常代码中。
最终通知(after):最后都必须执行的。
注意:后置通知和异常通知互斥的
环绕通知
不要被名字的直接意思给误会了。
所谓环绕通知,实际上是spring框架给程序提供的一种==“自由”==编写通知代码执行位置的方式。写在前面就是前置通知,写在后面就是后置通知,写在异常中异常通知,写在finally中就是最终通知。即环绕通知有四个基本通知的所有功能且可以自由定制。(买衣服:四个基本通知买的成品,选择适合自己身材的号码;环绕通知相当于量身定做)
代码演示:
//环绕通知:自定义通知的方式
//ProceedingJoinPoint:正在处理的连接点(切入点)
//该对象spring框架会自动传入
//如果该对象作为方法参数时,必须位于第一个
//spring框架会封装正在执行的核心代码到该对象中
public void around(ProceedingJoinPoint pjp){
// Object objs[] = pjp.getArgs();//获取当前执行的方法中的参数
// pjp.getSignature().getName();//获取当前执行的方法的名字
try {
System.out.println("before......你的前置通知");
pjp.proceed();//代表核心业务方法
System.out.println("after.....你的后置通知");
} catch (Throwable throwable) {//抓的级别更高的Throwable
System.out.println("异常.....你的后置通知");
throwable.printStackTrace();
}finally {
System.out.println("最终......你的最终通知");
}
}
Spring的基于注解的AOP配置
a、四个基本通知
测试效果:
b、抽取切入点表达式:给人感觉比较怪异记住这种格式
c、配置环绕通知注解(注解方式建议使用环绕,不会有顺序错乱)
小结:spring的aop的注解配置
step1、必须在spring配置文件中打开开关
step2、在通知类上使用@Aspect
step3、配置切入点:@Pointcut
step4、四个基本通知注解:@Before@AfterReturning@AfterThrowing@After,注意:有可能存在执行顺序不正确的问题
step5、环绕通知:@Around
Spring的基于类的AOP配置
目标:用类的配置替换xml(no xml)
step1、建立一个配置类:开启AOP的注解支持
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@ComponentScan("com.zl")
@EnableAspectJAutoProxy //开启AOP的注解支持
public class SpringConfig {
}
step2、修改测试类