文章目录
spring第三章-AOP
一、目标
1、转账编码
2、解决转账问题
3、动态代理回顾
4、解决转账问题
5、什么是AOP
6、AOP的xml配置
7、AOP的注解配置
二、转账编码
1、引入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
2、实体类
public class Account {
private Integer id;
private String name;
private Float money;
}
3、持久层
1. 接口
package com.sgw.dao;
import com.sgw.domain.Account;
/**
* @author
* @Company http://www.sgw.com
* @Version 1.0
*/
public interface AcountDao {
/**
* 根据账户名查询账户
* @param fromName
* @return
*/
Account findByName(String fromName);
/**
* 更新账户
* @param fromAccount
*/
void update(Account fromAccount);
}
2. 实现类
package com.sgw.dao.impl;
import com.sgw.dao.AcountDao;
import com.sgw.domain.Account;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.sql.SQLException;
/**
* @author
* @Company http://www.sgw.com
* @Version 1.0
*/
@Repository
public class AcountDaoImpl implements AcountDao {
@Autowired
QueryRunner queryRunner;
@Override
public Account findByName(String fromName) {
String sql = "select * from account where name = ?";
try {
return queryRunner.query(sql ,new BeanHandler<>(Account.class),fromName);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
@Override
public void update(Account account) {
String sql = "update account set money = ? where name = ?";
try {
queryRunner.update(sql ,account.getMoney(), account.getName());
} catch (SQLException e) {
e.printStackTrace();
}
}
}
4、业务层
1. 接口
package com.sgw.service;
/**
* @author
* @Company http://www.sgw.com
* @Version 1.0
*/
public interface AccountService {
/**
*
* @param fromName 从哪个账户转出
* @param toName 转入哪个账户
* @param money 转多少钱
*/
public void transfer(String fromName, String toName ,Float money);
}
2. 实现类
package com.sgw.service.impl;
import com.sgw.dao.AcountDao;
import com.sgw.domain.Account;
import com.sgw.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
*
* 一个事务必须在一个Connection中完成
*
* ThreadLocal:线程绑定
* 绑定Connection对象
* 业务层和持久层需要Connection从ThreadLocal中获取
*
* @author
* @Company http://www.sgw.com
* @Version 1.0
*/
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
AcountDao acountDao;
@Override
public void transfer(String fromName, String toName, Float money) {
try {
//事务1:开启事务:conn.setAutoCommit(false);
//查询要转出的账户
Account fromAccount = acountDao.findByName(fromName);
//查询转入的账户
Account toAccount = acountDao.findByName(toName);
//修改要转出的账户余额:假设余额充足
fromAccount.setMoney(fromAccount.getMoney() - money);
//修改要转入的账户余额
toAccount.setMoney(toAccount.getMoney() + money);
//持久化到数据库
acountDao.update(fromAccount);
//出现异常
System.out.println(1/0);
acountDao.update(toAccount);
//事务2:提交事务:conn.commit();
} catch (Exception e) {
//事务3:回顾事务 conn.rollback();
e.printStackTrace();
} finally {
//事务4:还原状态 conn.setAutoCommit(true);
}
System.out.println();
}
}
5、配置文件
<?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:context="http://www.springframework.org/schema/context"
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">
<!--扫描表,创建对象-->
<context:component-scan base-package="com.sgw"></context:component-scan>
<!--创建queryRunner对象:构造方法中需要DataSource对象-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg type="javax.sql.DataSource" ref="dataSource"></constructor-arg>
</bean>
<!--创建dataSource对象:注入四个必要属性-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
</beans>
6、测试
package com.sgw;
import com.sgw.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author
* @Company http://www.sgw.com
* @Version 1.0
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestTransfer {
@Autowired
AccountService accountService;
@Test
public void test(){
//创建类对象,创建springIOC容器
// ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
accountService.transfer("aaa","bbb", 100f);
}
}
三、解决转账问题
1、引入工具类
引入ConnectionUtils
package com.sgw.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* 一个管理连接的工具类,用于实现连接和线程的绑定
*
* 保证当前线程中获取的Connection是同一个
*
* @author
* @Company http://www.sgw.com
* @Version 1.0
*/
@Component
public class ConnectionUtil {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
@Autowired
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 获取当前线程上绑定的连接
* @return
*/
public Connection getThreadConnection() {
try {
//1.先看看线程上是否绑了
Connection conn = tl.get();
if(conn == null) {
//2.从数据源中获取一个连接
conn = dataSource.getConnection();
//3.和线程局部变量绑定
tl.set(conn);
}
//4.返回线程上的连接
return tl.get();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* 把连接和当前线程解绑
*/
public void remove() {
tl.remove();
}
}
引入TransactionManager
package com.sgw.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.SQLException;
/**
* 事务管理器
* @author
* @Company http://www.sgw.com
* @Version 1.0
*/
@Component
public class TransactionManager {
@Autowired
private ConnectionUtil connectionUtil;
public void setConnectionUtil(ConnectionUtil connectionUtil) {
this.connectionUtil = connectionUtil;
}
//开启事务
public void beginTransaction() {
//从当前线程上获取连接,实现开启事务
try {
connectionUtil.getThreadConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
//提交事务
public void commit() {
try {
connectionUtil.getThreadConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
//回滚事务
public void rollback() {
try {
connectionUtil.getThreadConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
//释放连接
public void release() {
try {
connectionUtil.getThreadConnection().setAutoCommit(true);
//关闭连接(还回池中)
connectionUtil.getThreadConnection().close();
//解绑线程:把连接和线程解绑
connectionUtil.remove();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
2、修改业务层
package com.sgw.service.impl;
import com.sgw.dao.AcountDao;
import com.sgw.domain.Account;
import com.sgw.service.AccountService;
import com.sgw.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
*
* 一个事务必须在一个Connection中完成
*
* ThreadLocal:线程绑定
* 绑定Connection对象
* 业务层和持久层需要Connection从ThreadLocal中获取
*
* @author
* @Company http://www.sgw.com
* @Version 1.0
*/
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
AcountDao acountDao;
@Autowired
TransactionManager txManager;
/**
* 假设需要事务管理
* @param account
*/
public void update(Account account){
try {
//事务1:开启事务:conn.setAutoCommit(false);
txManager.beginTransaction();
acountDao.update(account);
//事务2:提交事务:conn.commit();
txManager.commit();
} catch (Exception e) {
//事务3:回顾事务 conn.rollback();
txManager.rollback();
e.printStackTrace();
} finally {
//事务4:还原状态 conn.setAutoCommit(true);
txManager.release();
}
}
@Override
public void transfer(String fromName, String toName, Float money) {
try {
//事务1:开启事务:conn.setAutoCommit(false);
txManager.beginTransaction();
//查询要转出的账户
Account fromAccount = acountDao.findByName(fromName);
//查询转入的账户
Account toAccount = acountDao.findByName(toName);
//修改要转出的账户余额:假设余额充足
fromAccount.setMoney(fromAccount.getMoney() - money);
//修改要转入的账户余额
toAccount.setMoney(toAccount.getMoney() + money);
//持久化到数据库
acountDao.update(fromAccount);
//出现异常
System.out.println(1/0);
acountDao.update(toAccount);
//事务2:提交事务:conn.commit();
txManager.commit();
} catch (Exception e) {
//事务3:回顾事务 conn.rollback();
txManager.rollback();
e.printStackTrace();
} finally {
//事务4:还原状态 conn.setAutoCommit(true);
txManager.release();
}
}
}
3、发现新的问题
问题
1. 重复代码
2. 代码臃肿问题
3. 技术与业务整合到一起了
解决的思路
1. 提取重复的代码
2. 业务层中不需要技术代码
3. 不修改业务层源码的情况下,技术增强
4. 使用动态代理
动态代理
特点:随用随创建,随用随加载
不修改原来代码的基础上,对原来的代码增强
四、动态代理回顾
1、jdk动态代理
a. jdk动态代理: 基于接口的动态代理
b. @Test
public void testJDKProxy(){
//真实的对象
NewSale newSale = new NewSaleImpl();
//创建代理对象-- 本质上就是接口的一个实现类
//参数1: 类加载器
//参数2:类实现的接口
//参数3:真实对象的增强部分:实现了InvocationHandler接口的实现类
//匿名内部类也是该接口的实现类
NewSale sale = (NewSale) Proxy.newProxyInstance(newSale.getClass().getClassLoader(), newSale.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 增强内容
* @param proxy : 代理对象
* @param method : 代理的方法,未增强的方法
* @param args : 代理的方法的参数
* @return 代理的方法的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//生成产品
ProductFactory productFactory = new ProductFactory();
productFactory.make();
//开始销售:通过反射执行真实对象的方法
//参数1:真实的对象
//参数2:方法的参数
method.invoke(newSale,args );
//判断是否挣钱了
//卖的价格:args[0] 假设产品的成本是 1500
if( (Float)args[0] > 1500){
//卖的价格高于成本,挣了,可卖
System.out.println("卖的价格高于成本,挣了,可卖");
}else{
//卖的价格低于成本,赔了,不可卖
System.out.println("卖的价格低于成本,赔了,不可卖");
}
return null;
}
});
sale.sale(1000F);
}
2、cglib动态代理
a. cglib动态代理: 基于类的动态代理
b. 第三方jar包: cglib-2.2.2.jar
c. 注意:代理的类不用final修饰
d. public void testCglibProxy(){
//真实对象
OldSale oldSale = new OldSale();
//创建cglib代理对象
//1. 创建增强类对象
Enhancer enhancer = new Enhancer();
//2. 指定代理对象的父类
enhancer.setSuperclass(oldSale.getClass());
//3. 指定增强的内容
//MethodInterceptor :接口是方法拦截器
enhancer.setCallback(new MethodInterceptor() {
/***
* 增强的内容
* @param o 代理对象,增强后的对象
* @param method 代理的方法
* @param objects 代理的方法的参数
* @param methodProxy 代理方法,增强后的方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//生成产品
ProductFactory factory = new ProductFactory();
factory.make();
//开始销售,执行真实对象的内容
method.invoke(oldSale, objects);
//判断是否挣钱了
//卖的价格:objects[0] 假设产品的成本是 1500
if( (Float)objects[0] > 1500){
//卖的价格高于成本,挣了,可卖
System.out.println("卖的价格高于成本,挣了,可卖");
}else{
//卖的价格低于成本,赔了,不可卖
System.out.println("卖的价格低于成本,赔了,不可卖");
}
return null;
}
});
//4. 创建代理对象:
OldSale saleProxy = (OldSale)enhancer.create();
saleProxy.sale(2000f);
}
五、动态代理解决新问题
1、jdk动态代理解决问题
@Test
public void testJDKProxyService(){
//创建业务层代理对象
AccountService accountServiceProxy = (AccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//开启事务
txManager.beginTransaction();
//执行真实的对象的方法
method.invoke(accountService, args);
//提交事务
txManager.commit();
} catch (Exception e) {
//事务回滚
txManager.rollback();
e.printStackTrace();
} finally {
//还原状态
txManager.release();
}
return null;
}
});
accountServiceProxy.transfer("aaa","bbb",200f);
}
2、cglib动态代理解决问题
@Test
public void testCglibProxy(){
//增强对象
Enhancer enhancer = new Enhancer();
//指定代理对象的父类
enhancer.setSuperclass(accountService.getClass());
//增强的内容
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
try {
txManager.beginTransaction();
method.invoke(accountService, objects);
txManager.commit();
} catch (Exception e) {
txManager.rollback();
e.printStackTrace();
} finally {
txManager.release();
}
return null;
}
});
//创建代理对象
AccountService accountServiceProxy = (AccountService)enhancer.create();
accountServiceProxy.transfer("aaa","bbb",200f);
}
六、什么是AOP
AOP是Spring提供的关键特性之一。AOP即面向切面编程,是OOP编程的有效补充。使用AOP技术,可以将一些系统性相关的编程工作,独立提取出来,独立实现,然后通过切面切入进系统。从而避免了在业务逻辑的代码中混入很多的系统相关的逻辑——比如权限管理,事物管理,日志记录等等。这些系统性的编程工作都可以独立编码实现,然后通过AOP技术切入进系统即可。从而达到了 将不同的关注点分离出来的效果。本文深入剖析Spring的AOP的原理。
AOP相关的概念
1) Aspect :切面,切入系统的一个切面。比如事务管理是一个切面,权限管理也是一个切面;
2) Join point :连接点,也就是可以进行横向切入的位置;
3) Advice :通知,切面在某个连接点执行的操作(分为: Before advice , After returning advice , After throwing advice , After (finally) advice , Around advice );
4) Pointcut :切点,符合切点表达式的连接点,也就是真正被切入的地方;
AOP 的实现原理
AOP分为静态AOP和动态AOP。静态AOP是指AspectJ实现的AOP,他是将切面代码直接编译到Java类文件中。动态AOP是指将切面代码进行动态织入实现的AOP。Spring的AOP为动态AOP,实现的技术为: JDK提供的动态代理技术 和 CGLIB(动态字节码增强技术) 。尽管实现技术不一样,但 都是基于代理模式 , 都是生成一个代理对象 。
七、AOP的xml配置
1、依赖
<!--引入spring的核心-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--引入spring的测试包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--引入单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--配置aop,必须引入一个包:版本必须要1.8.7以上-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
2、配置文件
<?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: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">
<!--扫描表,创建bean对象-->
<context:component-scan base-package="com.sgw"></context:component-scan>
<!--通知对象: 拦截到方法时,通知执行的对象-->
<!--
通知的类型
前置通知: 方法之前执行
后置通知: 方法执行完之后执行- 返回之前执行-如果有异常,则不执行
最终通知: 方法执行完后总会执行- finally
异常通知: 方法出现异常则执行
环绕通知: 前置通知+后置通知+最终通知+异常通知
-->
<bean id="logger" class="com.sgw.log.Logger"></bean>
<!--配置aop-->
<aop:config>
<!--配置切面= 切入点 + 通知
指定通知对象是谁
-->
<aop:aspect ref="logger">
<!--配置切入点
id:唯一的标志
expression: 表达式
* com.sgw.service.impl.*.*(..)
* com.sgw.service..*.*(..)
第一个*:代表方法任意返回值类型
第二个*: 类名任意,impl包中所有的类
第三个*: 任意方法名
(..) : 参数任意,个数任意,类型任意,顺序任意
其他的配置方式
public void com.sgw.service.impl.UserServiceImpl.findAll()
void com.sgw.service.impl.UserServiceImpl.findAll()
* com.sgw.service.impl.UserServiceImpl.findAll()
* com.sgw.service..UserServiceImpl.findAll() : .. 代表的是包,及其子包
-->
<aop:pointcut id="pointcut" expression="execution(* com.sgw.service.impl.*.*(..))"></aop:pointcut>
<!--织入: 告诉通知对象执行,具体执行哪一个方法-->
<!--前置通知-->
<!--<aop:before method="before" pointcut-ref="pointcut"></aop:before>-->
<!--后置通知-->
<!--<aop:after-returning method="afterReturning" pointcut-ref="pointcut"></aop:after-returning>-->
<!--最终通知-->
<!--<aop:after method="after" pointcut-ref="pointcut"></aop:after>-->
<!--异常通知-->
<!--<aop:after-throwing throwing="e" method="afterThrowing" pointcut-ref="pointcut"></aop:after-throwing>-->
<!--环绕增强-->
<aop:around method="around" pointcut-ref="pointcut"></aop:around>
</aop:aspect>
</aop:config>
</beans>
八、AOP的注解配置
package com.sgw.log;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
*@Component: 创建类对象
* @Aspect:配置该类为切面
* 切面是:切入点 + 通知
*
*
* @author
* @Company http://www.sgw.com
* @Version 1.0
*/
@Component
@Aspect
public class Logger {
/**
* 配置切入点
*/
@Pointcut("execution(* com.sgw.service.impl.*.*(..))")
public void pointcut(){};
/**
*
* @param joinPoint 连接点-- 拦截到的方法
*/
// @Before("pointcut()")
public void before(JoinPoint joinPoint){
//被代理的对象
Object target = joinPoint.getTarget();
//拦截的类的名称
String className = target.getClass().getName();
System.out.println("拦截到的类名:" +className);
//获取方法对象
Signature signature = joinPoint.getSignature();
//获取方法名
String methodName = signature.getName();
System.out.println("拦截到方法名:" + methodName);
System.out.println("前置通知");
}
// @AfterReturning("pointcut()")
public void afterReturning(){
System.out.println("后置增强");
}
// @After("pointcut()")
public void after(){
System.out.println("最终增强");
}
// @AfterThrowing(value = "pointcut()",throwing = "e")
public void afterThrowing(Exception e){
System.out.println("执行的方法的异常:"+e);
System.out.println("异常通知");
}
/**
* ProceedingJoinPoint 可以执行拦截到方法的连接点对象
* @param joinPoint
*/
@Around("pointcut()")
public void around(ProceedingJoinPoint joinPoint){
try {
System.out.println("前置通知");
//执行原来的方法: 可以获取方法的返回值
Object result = joinPoint.proceed();
System.out.println("后置增强");
} catch (Throwable e) {
System.out.println("异常通知");
// e.printStackTrace();
} finally {
System.out.println("最终增强");
}
}
}