Spring03:案例转账功能(事务问题)、动态代理解决、AOP
- 完善Account案例
- 分析案例中的问题
- 回顾之前讲过的技术--动态代理
- 动态代理的另一种实现方式
- 解决案例中的问题
- AOP的概念
- Spring中的AOP相关术语
- Spring中基于xml和注解的AOP配置※
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置Service -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<!-- 注入dao对象 -->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置dao对象-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<!--注入QueryRunner-->
<property name="runner" ref="runner"></property>
</bean>
<!--配置QueryRunner对象-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy02"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>
package com.itheima.test;
import com.itheima.domain.Account;
import com.itheima.service.IAccountService;
import com.itheima.service.impl.AccountServiceImpl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
/**
* 使用Junit单元测试:测试我们的配置
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/bean.xml"})
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("aaa","bbb",100f);
}
}
@Override
public Account findAccountByName(String accountName) {
try {
List<Account> accounts = runner.query("select * from account where name = ?", new BeanListHandler<Account>(Account.class),accountName);
if (accounts == null || accounts.size() == 0) return null;
if (accounts.size() > 1) throw new RuntimeException("结果集不唯一,数据存在问题");
return accounts.get(0);
} catch (Exception e) {
throw new RuntimeException(e);//相当于return
}
}
@Override
public void transfer(String sourceName, String targetName, Float money) {
//1.根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//3.转出账户金额减少
source.setMoney(source.getMoney() - money);
//4.转入账户金额增加
target.setMoney(target.getMoney() + money);
//int i = 1 / 0;
//5.更新转出账户
accountDao.updateAccount(source);
//6.更新转入账户
accountDao.updateAccount(target);
}
package com.itheima.utils;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* 连接的工具类,用于从数据源中获取一个连接,并且实现和线程的绑定
*/
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 获取当前线程上的连接
*/
public Connection getThreadConnection(){
try {
//1.先从ThreadLocal上获取
Connection conn = tl.get();
//2.判断当前线程上是否有连接
if (conn == null){
//3.从数据源中获取一个连接,并且和线程绑定,存入ThreadLocal中
conn = dataSource.getConnection();
tl.set(conn);
}
//4.返回当前线程上的连接
return conn;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
package com.itheima.utils;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* 连接的工具类,用于从数据源中获取一个连接,并且实现和线程的绑定
*/
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 获取当前线程上的连接
*/
public Connection getThreadConnection(){
try {
//1.先从ThreadLocal上获取
Connection conn = tl.get();
//2.判断当前线程上是否有连接
if (conn == null){
//3.从数据源中获取一个连接,并且和线程绑定,存入ThreadLocal中
conn = dataSource.getConnection();
tl.set(conn);
}
//4.返回当前线程上的连接
return conn;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 把连接和线程解绑
*/
public void removeConnection(){
tl.remove();
}
}
package com.itheima.utils;
import java.sql.SQLException;
/**
* 和事务管理相关的工具类,包含开启事务、提交事务、回滚事务和释放连接
*/
public class TransactionManager {
//获取当前线程上的Connection
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 开启事务
*/
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(true);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 释放连接
*/
public void release(){
try {
connectionUtils.getThreadConnection().close();//并不是真正关闭连接,而是还回连接池中
connectionUtils.removeConnection();//进行线程的解绑
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.itheima.utils;
import java.sql.SQLException;
/**
* 和事务管理相关的工具类,包含开启事务、提交事务、回滚事务和释放连接
*/
public class TransactionManager {
//获取当前线程上的Connection
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 开启事务
*/
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(true);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 释放连接
*/
public void release(){
try {
connectionUtils.getThreadConnection().close();//并不是真正关闭连接,而是还回连接池中
connectionUtils.removeConnection();//进行线程的解绑
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.itheima.service.impl;
import com.itheima.dao.IAccountDao;
import com.itheima.domain.Account;
import com.itheima.service.IAccountService;
import com.itheima.utils.TransactionManager;
import java.util.List;
/**
* 账户的业务层实现类
* 事务控制应当在业务层
*/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
private TransactionManager txManager;
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public List<Account> findAllAccount() {
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
List<Account> accounts = accountDao.findAllAccount();
//3.提交事务
txManager.commit();
//4.返回结果
return accounts;
} catch (Exception e) {
//回滚操作
txManager.rollback();
throw new RuntimeException(e);
} finally {
//释放连接
txManager.release();
}
}
@Override
public Account findAccountById(Integer accountId) {
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
Account account = accountDao.findAccountById(accountId);
//3.提交事务
txManager.commit();
//4.返回结果
return account;
} catch (Exception e) {
//回滚操作
txManager.rollback();
}finally {
//释放连接
txManager.release();
}
return accountDao.findAccountById(accountId);
}
@Override
public void saveAccount(Account account) {
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
accountDao.saveAccount(account);
//3.提交事务
txManager.commit();
//4.返回结果
} catch (Exception e) {
//回滚操作
txManager.rollback();
}finally {
//释放连接
txManager.release();
}
}
@Override
public void updateAccount(Account account) {
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
accountDao.updateAccount(account);
//3.提交事务
txManager.commit();
//4.返回结果
} catch (Exception e) {
//回滚操作
txManager.rollback();
}finally {
//释放连接
txManager.release();
}
}
@Override
public void deleteAccount(Integer accountId) {
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
accountDao.deleteAccount(accountId);
//3.提交事务
txManager.commit();
//4.返回结果
} catch (Exception e) {
//回滚操作
txManager.rollback();
}finally {
//释放连接
txManager.release();
}
}
@Override
public void transfer(String sourceName, String targetName, Float money) {
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
//2.1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//2.3转出账户金额减少
source.setMoney(source.getMoney() - money);
//2.4.转入账户金额增加
target.setMoney(target.getMoney() + money);
//int i = 1 / 0;
//2.5.更新转出账户
accountDao.updateAccount(source);
//2.6.更新转入账户
accountDao.updateAccount(target);
//3.提交事务
txManager.commit();
//4.返回结果
} catch (Exception e) {
//回滚操作
txManager.rollback();
} finally {
//释放连接
txManager.release();
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置Service -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<!-- 注入dao对象 -->
<property name="accountDao" ref="accountDao"></property>
<!--注入事务管理器-->
<property name="txManager" ref="txManager"></property>
</bean>
<!--配置dao对象-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<!--注入QueryRunner-->
<property name="runner" ref="runner"></property>
<!--注入ConnectionUtils-->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置QueryRunner对象-->
<!--不再提供Connection对象,没有数据源,不会从数据源中获取连接-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy02"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--配置Connection的工具类-ConnectionUtils-->
<bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils">
<!--注入数据源的配置-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务管理器-->
<bean id="txManager" class="com.itheima.utils.TransactionManager">
<!--注入ConnectionUtils-->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
</beans>
package com.itheima.service.impl;
import com.itheima.dao.IAccountDao;
import com.itheima.domain.Account;
import com.itheima.service.IAccountService;
import com.itheima.utils.TransactionManager;
import java.util.List;
/**
* 账户的业务层实现类
* 事务控制应当在业务层
*/
public class AccountServiceImpl implements IAccountService {
/**
* 每次获取连接,无法实现事务控制
*/
private IAccountDao accountDao;
private TransactionManager txManager;
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public List<Account> findAllAccount() {
return accountDao.findAllAccount();
}
@Override
public Account findAccountById(Integer accountId) {
//2.执行操作
return accountDao.findAccountById(accountId);
}
@Override
public void saveAccount(Account account) {
accountDao.saveAccount(account);
}
@Override
public void updateAccount(Account account) {
accountDao.updateAccount(account);
}
@Override
public void deleteAccount(Integer accountId) {
accountDao.deleteAccount(accountId);
}
@Override
public void transfer(String sourceName, String targetName, Float money) {
//2.1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//2.3转出账户金额减少
source.setMoney(source.getMoney() - money);
//2.4.转入账户金额增加
target.setMoney(target.getMoney() + money);
//int i = 1 / 0;
//2.5.更新转出账户
accountDao.updateAccount(source);
//2.6.更新转入账户
accountDao.updateAccount(target);
//3.提交事务
txManager.commit();
}
}
package com.itheima.utils;
import java.sql.SQLException;
/**
* 和事务管理相关的工具类,包含开启事务、提交事务、回滚事务和释放连接
*/
public class TransactionManager {
//获取当前线程上的Connection
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 开启事务
*/
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(true);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 释放连接
*/
public void release(){
try {
connectionUtils.getThreadConnection().close();//并不是真正关闭连接,而是还回连接池中
connectionUtils.removeConnection();//进行线程的解绑
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.itheima.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 模拟一个消费者
*/
public class Client {
public static void main(String[] args) {
Producer producer = new Producer();
/**
* 动态代理
* 特点:字节码随用随创建,随用随加载
* 作用:在不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于接口的动态代理:
* 设计的类:Proxy
* 提供者:JDK官方
* 如何创建代理对象:
* 使用Proxy类中的newProxyInstance方法
* 创建代理对象的要求:
* 被代理类至少实现一个接口,如果没有则不能使用
* newProxyInstance方法的参数:
* Classloader:类加载器
* 用于加载代理对象字节码,和被代理对象使用相同的类加载器
* Class[]:字节码数组
* 用于让代理对象和被代理对象有相同的方法
* InvocationHandler:用于提供增强的代码
* 让我们写如何代理,一般情况下写该接口的实现类,通常情况下是匿名内部类
* 此接口的实现类都是谁用谁写
*/
//不实现任何接口时,无法正常使用
IProducer proxyProducer = (IProducer)Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.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 {
//想要增强,可以在此处提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
float money = (float) args[0];
//2.判断当前方法是不是销售
if ("saleProduct".equals(method.getName())){
returnValue = method.invoke(producer,money * 0.8f);
}
return returnValue;
}
});
proxyProducer.saleProduct(10000f);
}
}
package com.itheima.proxy;
/**
* 一个生产者
* 生产厂家需要有标准-销售和售后(接口)
*/
public class Producer implements IProducer{
/**
* 销售
* @param money
*/
//@Override
public void saleProduct(float money){
System.out.println("销售产品,并拿到"+money+"元钱");
}
/**
* 售后
* @param money
*/
//@Override
public void afterService(float money){
System.out.println("提供售后服务,并拿到"+money+"元钱");
}
}
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
</dependencies>
package com.itheima.cglib;
import com.itheima.proxy.IProducer;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 模拟一个消费者
*/
public class Client {
public static void main(String[] args) {
Producer producer = new Producer();
/**
* 动态代理
* 特点:字节码随用随创建,随用随加载
* 作用:在不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于接口的动态代理:
* 设计的类:Proxy
* 提供者:第三方cglib库
* 如何创建代理对象:
* 使用Ehancer类中的create方法
* 创建代理对象的要求:
* 被代理类不能是最终类
* create方法的参数:
* Class:字节码
* 用于指定被代理对象的字节码,producer.getClass()
* Callback:用于提供增强的代码
* 让我们写如何代理,一般情况下写该接口的实现类,通常情况下是匿名内部类
* 此接口的实现类都是谁用谁写
* 一般写的都是该接口的子接口实现类:MethodIntercepter
*/
Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行被代理对象的任何方法都会经过该方法
* @param proxy
* @param method
* @param args
* 以上三个参数和基于接口的动态代理中invoke方法的参数相同
* @param methodProxy 当前执行方法的代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//想要增强,可以在此处提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
float money = (float) args[0];
//2.判断当前方法是不是销售
if ("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money * 0.8f);
}
return returnValue;
}
});
cglibProducer.saleProduct(10000f);
}
}
package com.itheima.factory;
import com.itheima.dao.IAccountDao;
import com.itheima.domain.Account;
import com.itheima.service.IAccountService;
import com.itheima.utils.TransactionManager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 用于创建service代理对象的工厂
*/
public class BeanFactory {
private IAccountService accountService;
private TransactionManager txManager;
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
/**
* 获取service的代理对象
*
* @return
*/
public IAccountService getAccountService() {
Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.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 {
Object rtValue = null;
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
rtValue = method.invoke(accountService, args);
// 3.提交事务
txManager.commit();
//4.返回结果
return rtValue;
} catch (Exception e) {
//回滚操作
txManager.rollback();
throw new RuntimeException(e);
} finally {
//释放连接
txManager.release();
}
}
});
return accountService;
}
public final void setAccountService(IAccountService accountService) {
this.accountService = accountService;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置代理的service对象-->
<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
<!--配置beanfactory-->
<bean id="beanFactory" class="com.itheima.factory.BeanFactory">
<!--注入Service-->
<property name="accountService" ref="accountService"></property>
<!--注入事务管理器-->
<property name="txManager" ref="txManager"></property>
</bean>
<!-- 配置Service -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<!-- 注入dao对象 -->
<property name="accountDao" ref="accountDao"></property>
</bean>
</beans>
package com.itheima.test;
import com.itheima.domain.Account;
import com.itheima.service.IAccountService;
import com.itheima.service.impl.AccountServiceImpl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
/**
* 使用Junit单元测试:测试我们的配置
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/bean.xml"})
public class AccountServiceTest {
@Autowired
@Qualifier("proxyAccountService")
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("aaa","bbb",100f);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>day03_eesy_003springaop</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>9</maven.compiler.source>
<maven.compiler.target>9</maven.compiler.target>
</properties>
<dependencies>
<!--spring的aop会用到-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.3.RELEASE</version>
</dependency>
<!--用于解析切入点表达式-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
</dependencies>
</project>
package com.itheima.service;
/**
* 账户的业务层接口
*/
public interface IAccountService {
/**
* 模拟保存账户
*/
void saveAccount();
/**
* 模拟更新账户
* @param i
*/
void updateAccount(int i);
/**
* 模拟删除账户
* @return
*/
int deleteAccount();
}
package com.itheima.service.impl;
import com.itheima.service.IAccountService;
/**
* 账户的业务层实现类
*/
public class AccountServiceImpl implements IAccountService {
@Override
public void saveAccount() {
System.out.println("执行了保存");
}
@Override
public void updateAccount(int i) {
System.out.println("执行了更新"+i);
}
@Override
public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}
package com.itheima.utils;
/**
* 用具记录日志的工具类,内部提供公共代码
*/
public class Logger {
/**
* 向控制台打印日志:计划在其切入点方法执行之前执行(切入点方法就是业务层方法)
*/
public void printLog(){
System.out.println("Logger类中的printLog方法开始记录日志了");
}
}
<?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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--先配置Spring的IOC,把Service对象配置进来【控制反转】-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!--Spring中基于xml的aop配置步骤【面向切面编程,动态代理】
1、把通知bean(logger类)也交给Spring管理
2、使用aop:config标签表明aop的配置
3、使用aop:aspect标签表明开始配置切面
id属性用于给切面一个唯一标识
ref属性指定通知类bean的id
4、在aop:aspect标签的内部,使用对应的标签来配置通知的类型
当前示例是让printLog方法在切入点之前执行,所以是前置通知
aop:before表示配置前置通知
method属性:表示哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义是对业务层中的哪些方法增强
切入点表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.类名.方法名 参数列表
标准写法:
public void com.itheima.service.impl.AccountService.saveAccount()
5、
-->
<!--配置logger类-->
<bean id="Logger" class="com.itheima.utils.Logger"></bean>
<!--配置aop-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="Logger">
<!--配置通知的类型,并建立通知方法和切入点方法的关联-->
<aop:before method="printLog" pointcut="execution(public void com.itheima.service.impl.AccountService.saveAccount())"></aop:before>
</aop:aspect>
</aop:config>
</beans>
package com.itheima.test;
import com.itheima.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 测试AOP的配置
*/
public class AOPTest {
public static void main(String[] args) {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
IAccountService as = (IAccountService)ac.getBean("accountService");
//3.执行方法
//切入点表达式只配置了对保存的增强
as.saveAccount();//查看是否实现了记录日志/日志的打印
as.updateAccount(1);
as.deleteAccount();
}
}
<?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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--先配置Spring的IOC,把Service对象配置进来【控制反转】-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!--Spring中基于xml的aop配置步骤【面向切面编程,动态代理】
切入点表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.类名.方法名 参数列表
标准写法:
public void com.itheima.service.impl.AccountService.saveAccount()
1.访问修饰符可以省略 void com.itheima.service.impl.AccountService.saveAccount()
2.返回值可以使用通配符表示任意返回值 * com.itheima.service.impl.AccountService.saveAccount()
3.包名可以使用通配符表示任意包,但是有几级包,就需要写几个*.
3.1包名可以使用*..表示当前包及子包 * *..
4.类名和方法名都可以使用*实现通配 * *..*(*)
4.1参数列表:
可以直接写数据类型
基本类型直接写名称 int * *..*(int)
引用类型写报名.类名的方式 java.lang.String
类型可以使用通配符*表示任意类型,但必须有参数int * *..*(*)
可以使用..表示有无参数均可,有参数时表示任意类型 * *..*(..)
全通配写法:
* *..*.#(..)
实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有方法
* com.itheima.service.impl.*.*(..)
aspectj坐标表示切入点表达式的语言联盟
-->
<!--配置logger类-->
<bean id="Logger" class="com.itheima.utils.Logger"></bean>
<!--配置aop-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="Logger">
<!--配置通知的类型,并建立通知方法和切入点方法的关联-->
<aop:before method="printLog" pointcut="execution(* *..*.*(..))"></aop:before>
</aop:aspect>
</aop:config>
</beans>
<?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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--先配置Spring的IOC,把Service对象配置进来【控制反转】-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!--配置logger类-->
<bean id="Logger" class="com.itheima.utils.Logger"></bean>
<!--配置aop-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="Logger">
<!--配置通知的类型,并建立通知方法和切入点方法的关联-->
<!--事务要么try提交,要么catch回滚-->
<!--配置前置通知:在切入点方法执行之前执行-->
<aop:before method="beforePrintLog" pointcut="execution(* *..*.*(..))"></aop:before>
<!--配置后置通知:在切入点方法正常执行之后执行,它和异常通知永远只能执行一个-->
<aop:after-returning method="afterReturningPrintLog" pointcut="execution(* *..*.*(..))"></aop:after-returning>
<!--配置异常通知:在切入点方法执行产生异常之后执行,它和后置通知永远只能执行一个-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(* *..*.*(..))"></aop:after-throwing>
<!--配置最终通知:无论切入点方法是否正常执行,它都会在其后面执行-->
<aop:after method="afterPrintLog" pointcut="execution(* *..*.*(..))"></aop:after>
</aop:aspect>
</aop:config>
</beans>
package com.itheima.utils;
/**
* 用具记录日志的工具类,内部提供公共代码
*/
public class Logger {
/**
* 前置通知
*/
public void beforePrintLog(){
System.out.println("Logger类中的beforePrintLog方法开始记录日志了");
}
/**
* 后置通知
*/
public void afterReturningPrintLog(){
System.out.println("Logger类中的afterReturningPrintLog方法开始记录日志了");
}
/**
* 异常通知
*/
public void afterThrowingPrintLog(){
System.out.println("Logger类中的afterThrowingPrintLog方法开始记录日志了");
}
/**
* 最终通知
*/
public void afterPrintLog(){
System.out.println("Logger类中的afterPrintLog方法开始记录日志了");
}
}
<?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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--先配置Spring的IOC,把Service对象配置进来【控制反转】-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!--配置logger类-->
<bean id="Logger" class="com.itheima.utils.Logger"></bean>
<!--配置aop-->
<aop:config>
<!--
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
-->
<!--配置切面-->
<aop:aspect id="logAdvice" ref="Logger">
<!--配置通知的类型,并建立通知方法和切入点方法的关联-->
<!--事务要么try提交,要么catch回滚-->
<!--配置前置通知:在切入点方法执行之前执行-->
<aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
<!--配置后置通知:在切入点方法正常执行之后执行,它和异常通知永远只能执行一个-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>
<!--配置异常通知:在切入点方法执行产生异常之后执行,它和后置通知永远只能执行一个-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
<!--配置最终通知:无论切入点方法是否正常执行,它都会在其后面执行-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
<!--配置切入点表达式,id属性用于表示表达式的唯一标识,expression指定表达式内容
此标签写在aop:aspect标签内部,只能在当前切面使用
也可以写在aop:aspect标签外部(必须写在aspect标签之前),可以在所有切面使用
-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
</aop:aspect>
</aop:config>
</beans>
<?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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--先配置Spring的IOC,把Service对象配置进来【控制反转】-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!--配置logger类-->
<bean id="Logger" class="com.itheima.utils.Logger"></bean>
<!--配置aop-->
<aop:config>
<!--
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
-->
<!--配置切面-->
<aop:aspect id="logAdvice" ref="Logger">
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
<!--配置环绕通知 详细的注释请看logger类中-->
<aop:around method="aroundAroundPrintLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
</beans>
package com.itheima.utils;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 用具记录日志的工具类,内部提供公共代码
*/
public class Logger {
/**
* 环绕通知
* 问题:
* 当配置了环绕通知之后,切入点方法没有执行,而通知方法执行了
* 分析:
* 通过对比动态代理中的环绕代理通知代码,发现动态代理的环绕通知有明确的切入点方法调用
*
* 动态代理的环绕通知-有明确的切入点调用
* 我们没有切入点方法
* 解决:
* Spring框架提供了一个接口:ProceedingJoinPoint,该接口有一个方法proceed,此方法就相当于明确调用切入点方法
* 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用
* Spring中的环绕通知:
* 是Spring框架为我们提供的一种可以在代码中手动控制增强方法的实现方式
*/
public Object aroundAroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try {
Object[] args = pjp.getArgs();//得到方法执行所需的参数
System.out.println("前置通知开始记录日志了");
rtValue = pjp.proceed(args);//明确调用业务层/切入点方法
System.out.println("后置通知开始记录日志了");
return rtValue;
} catch (Throwable throwable) {
System.out.println("异常通知开始记录日志了");
throw new RuntimeException(throwable);
}finally {
System.out.println("最终通知开始记录日志了");
return rtValue;
}
}
}
package com.itheima.utils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 用具记录日志的工具类,内部提供公共代码
*/
@Component("logger")
@Aspect //表示当前类是一个切面类
public class Logger {
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
private void pt1(){}
/**
* 前置通知
*/
@Before("pt1()")
public void beforePrintLog(){
System.out.println("Logger类中的beforePrintLog方法开始记录日志了");
}
/**
* 后置通知
*/
@AfterReturning("pt1()")
public void afterReturningPrintLog(){
System.out.println("Logger类中的afterReturningPrintLog方法开始记录日志了");
}
/**
* 异常通知
*/
@AfterThrowing("pt1()")
public void afterThrowingPrintLog(){
System.out.println("Logger类中的afterThrowingPrintLog方法开始记录日志了");
}
/**
* 最终通知
*/
@After("pt1()")
public void afterPrintLog(){
System.out.println("Logger类中的afterPrintLog方法开始记录日志了");
}
/**
* 环绕通知
* 问题:
* 当配置了环绕通知之后,切入点方法没有执行,而通知方法执行了
* 分析:
* 通过对比动态代理中的环绕代理通知代码,发现动态代理的环绕通知有明确的切入点方法调用
*
* 动态代理的环绕通知-有明确的切入点调用
* 我们没有切入点方法
* 解决:
* Spring框架提供了一个接口:ProceedingJoinPoint,该接口有一个方法proceed,此方法就相当于明确调用切入点方法
* 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用
* Spring中的环绕通知:
* 是Spring框架为我们提供的一种可以在代码中手动控制增强方法的实现方式
*/
//@Around("pt1()")
public Object aroundAroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try {
Object[] args = pjp.getArgs();//得到方法执行所需的参数
System.out.println("前置通知开始记录日志了");
rtValue = pjp.proceed(args);//明确调用业务层/切入点方法
System.out.println("后置通知开始记录日志了");
return rtValue;
} catch (Throwable throwable) {
System.out.println("异常通知开始记录日志了");
throw new RuntimeException(throwable);
}finally {
System.out.println("最终通知开始记录日志了");
return rtValue;
}
}
}
<?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:aop="http://www.springframework.org/schema/aop"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置Spring创建容器时要扫描的包-->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!--不再需要配置service-->
<!--把通知类交给Spring管理-->
<!--配置Spring开启注解AOP注解的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!--基于注解的aop有顺序的调用过程,而环绕通知没有调用顺序的问题※-->
</beans>
- 转账案例中事务问题的切入
- 记录日志
- 重复问题,判断用户是否登录,判断用户是否有权限
- 解决方式:
- 提取出来重复代码
- 通过动态代理(在不改变源码的情况下将方法增强)将重复代码加入到方法中
- AOP
- 增强的代码
- 执行的时机、类型与客体
Spring03:案例转账功能(事务问题)、动态代理解决、AOP的更多相关文章
- JavaWeb之动态代理解决request请求编码问题
动态代理解决编码问题 1.设计模式 出现原因:软件开发过程中,遇到相似问题,将问题的解决方法抽取模型(套路) 常见设计模式:单例,工厂,适配器,装饰者,动态代理. 2.装饰者模式简单介绍 谷歌汽车开发 ...
- Java 动态代理与AOP
动态代理与AOP 代理模式 代理模式给某一个目标对象(target)提供代理对象(proxy),并由代理对象控制对target对象的引用. 模式图: 代理模式中的角色有: 抽象对象角色(Abstrac ...
- 动态代理到基于动态代理的AOP
动态代理,是java支持的一种程序设计方法. 动态代理实现中有两个重要的接口和类,分别是InvocationHandler(interface),Proxy(class). 要实现动态代理,必须要定义 ...
- 动态代理实现AOP【转】
http://blog.csdn.net/beijiguangyong/article/details/8624016 根据前面介绍的Proxy和InvocationHandler,实在很难看出这种动 ...
- 动态代理的两种方式,以及区别(静态代理、JDK与CGLIB动态代理、AOP+IoC)
Spring学习总结(二)——静态代理.JDK与CGLIB动态代理.AOP+IoC 目录 一.为什么需要代理模式 二.静态代理 三.动态代理,使用JDK内置的Proxy实现 四.动态代理,使用cg ...
- 动态代理实现AOP
代理 代理顾名思义:代为处理.不是对目标对象的直接操作,而是通过代理对目标对象进行包装,此时可以在目标对象的基础上添加额外的操作以满足业务需求.图示 分类:动态代理.静态代理. 代理三要素:共同接口. ...
- Java动态代理-->Spring AOP
引述要学习Spring框架的技术内幕,必须事先掌握一些基本的Java知识,正所谓“登高必自卑,涉远必自迩”.以下几项Java知识和Spring框架息息相关,不可不学(我将通过一个系列分别介绍这些Jav ...
- .Net 框架实现AOP(动态代理实现AOP,本文为翻译)
在上一节,我们将静态实现AOP,但是对于一个大型项目,要想为每个类,每个方法都去实现AOP ,进行日志记录和权限验证似乎是不可能的. 即使可能对于成百上千个类维护,也是很难维护.所以今天的主题就是如标 ...
- Java使用动态代理实现AOP
参考资料: http://www.importnew.com/15420.htmlhttp://www.cnblogs.com/techyc/p/3455950.html Spring是借助了动态代理 ...
随机推荐
- Django 聚合查询 分组查询 F与Q查询
一.聚合查询 需要导入模块:from django.db.models import Max, Min, Sum, Count, Avg 关键语法:aggregate(聚合结果别名 = 聚合函数(参数 ...
- Logstash集成GaussDB(高斯DB)数据到Elasticsearch
GaussDB 简介 GaussDB 数据库分为 GaussDB T 和 GaussDB A,分别面向 OLTP 和 OLAP 的业务用户. GaussDB T 数据库是华为公司全自研的分布式数据库, ...
- SpringCloud组件编写Dockerfile文件模板
在组件根目录下的Dockerfile文件 # Dockerfile文件内容 FROM idocker.io/jre:1.8.0_212 #自定义的基础镜像 VOLUME /tmp # 挂载目录 ADD ...
- 索引模板和动态索引模板 (Index Template和Dynamic Template)
相关阅读 Index Templates https://www.elastic.co/guide/en/elasticsearch/reference/7.1/indices-templates.h ...
- EFCore分表实现
实现原理 当我们new一个上下文DbContext 后, 每次执行CURD方式时 ,都会依次调用OnConfiguring(),OnModelCreating()两个方法. OnConfiguring ...
- CSS-part1
一. CSS选择器 1.css引入方式 <!DOCTYPE html> <html lang="en"> <head> <meta cha ...
- 货币转换I
A=input() if A[0] in ['U','u']: RMB=(eval(A[3:]))*6.78 print("RMB{:.2f}".format(RMB)) else ...
- 为Azure-云准备一个基于Red Hat 8.x 的虚拟机镜像
由于公司最近要求部分项目上线到Azure云上,要求操作系统使用的Redhat 8.x,而且必须加固 而在Azure官网提供的镜像中,又没有Redhat,于是只有自己自定义Redhat镜像,最后加固,作 ...
- C语言之走迷宫深度和广度优先(利用堆栈和队列)
完成以下迷宫 利用二维数组储存每一个数组里的值,若是不能走则为1,若是可行就是0,走过了就设为2. 一般是再复制一个数组,用来记录. 堆栈的思想就是将一个点的上下左右都遍历一遍,若可行进栈,跳出遍历, ...
- 【多线程那些事儿】如何使用C++写一个线程安全的单例模式?
如何写一个线程安全的单例模式? 单例模式的简单实现 单例模式大概是流传最为广泛的设计模式之一了.一份简单的实现代码大概是下面这个样子的: class singleton { public: stati ...