AOP概念的引入
第一步创建普通Maven项目
导入依赖
<dependencies><!--spring的核心--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.0.2.RELEASE</version></dependency><!--日志--><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.12</version></dependency><!--Junit测试--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!--数据库连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.10</version></dependency><!--Spring整合junit测试--><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.0.2.RELEASE</version><scope>test</scope></dependency><!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.6</version></dependency></dependencies>
编写数据库数据实体类
package com.qcby.model;public class Account {private Integer id;private String name;private Double money;public Account() {}public Account(Integer id, String name, Double money) {this.id = id;this.name = name;this.money = money;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Double getMoney() {return money;}public void setMoney(Double money) {this.money = money;}@Overridepublic String toString() {return "Account{" +"id=" + id +", name='" + name + '\'' +", money=" + money +'}';}
}
业务层接口
package com.qcby.service;import com.qcby.model.Account;public interface AccountService {public void updateSaveAll(Account account1, Account account2);
}
业务层实现类
package com.qcby.service.Impl;import com.qcby.dao.AccountDao;
import com.qcby.model.Account;
import com.qcby.service.AccountService;
import com.qcby.utils.TxUtils;public class AccountServiceImpl implements AccountService{private AccountDao accountDao;public void setAccountDao(AccountDao accountDao) {this.accountDao = accountDao;}/*** 对saveAll方法进行增强* @param account1* @param account2* @throws SQLException*/@Overridepublic void updateSaveAll(Account account1, Account account2) {//try {//TxUtils.startTransaction();//保存账号1accountDao.updateSaveAll(account1);//显示除零错误int a=1/0;//保存账户2accountDao.updateSaveAll(account2);//TxUtils.commit();//}catch (Exception e){//e.printStackTrace();//TxUtils.rollback();//}}
}
持久层接口
package com.qcby.dao;import com.qcby.model.Account;public interface AccountDao {public void updateSaveAll(Account account);
}
持久层实现类
package com.qcby.dao;import com.qcby.model.Account;
import com.qcby.utils.TxUtils;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;public class AccountDaoImpl implements AccountDao {//实现两个账户的转账@Overridepublic void updateSaveAll(Account account) {Connection connection = null;PreparedStatement stmt = null;try{connection = TxUtils.getConnection();String sql = "update account set money = money + ? where name = ?";// 预编译SQL语句stmt = connection.prepareStatement(sql);stmt.setDouble(1,account.getMoney());stmt.setString(2,account.getName());//查询int result = stmt.executeUpdate();System.out.println("修改影响了"+result+"行数据");}catch (Exception e){try{stmt.close();} catch (SQLException e1) {e1.printStackTrace();}}}
}
使用事务工具类TxUtils获取连接,使两次对数据库的操作在同一个连接,使用线程存储这个连接,避免因错误等原因导致”钱转丢问题“
package com.qcby.utils;import java.sql.Connection;
import java.sql.SQLException;import javax.sql.DataSource;import com.alibaba.druid.pool.DruidDataSource;/*** 事务的工具类*/
public class TxUtils {private static DruidDataSource ds = null;// 使用ThreadLocal存储当前线程中的Connection对象private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();// 在静态代码块中创建数据库连接池static {try {// 通过代码创建Druid数据库连接池ds = new DruidDataSource();ds.setDriverClassName("com.mysql.jdbc.Driver");ds.setUrl("jdbc:mysql://localhost:3306/spring_db");ds.setUsername("root");ds.setPassword("2020");} catch (Exception e) {throw new ExceptionInInitializerError(e);}}/*** @Method: getConnection* @Description: 从数据源中获取数据库连接* @Anthor:* @return Connection* @throws SQLException*/public static Connection getConnection() throws SQLException {// 从当前线程中获取ConnectionConnection conn = threadLocal.get();if (conn == null) {// 从数据源中获取数据库连接conn = getDataSource().getConnection();// 将conn绑定到当前线程threadLocal.set(conn);}return conn;}/*** @Method: startTransaction* @Description: 开启事务* @Anthor:**/public static void startTransaction() {try {Connection conn = threadLocal.get();if (conn == null) {conn = getConnection();// 把 conn绑定到当前线程上threadLocal.set(conn);}// 开启事务conn.setAutoCommit(false);} catch (Exception e) {throw new RuntimeException(e);}}/*** @Method: rollback* @Description:回滚事务* @Anthor:*/public static void rollback() {try {// 从当前线程中获取ConnectionConnection conn = threadLocal.get();if (conn != null) {// 回滚事务conn.rollback();}} catch (Exception e) {throw new RuntimeException(e);}}/*** @Method: commit* @Description:提交事务* @Anthor:*/public static void commit() {try {// 从当前线程中获取ConnectionConnection conn = threadLocal.get();if (conn != null) {// 提交事务conn.commit();}} catch (Exception e) {throw new RuntimeException(e);}}/*** @Method: close* @Description:关闭数据库连接(注意,并不是真的关闭,而是把连接还给数据库连接池)* @Anthor:**/public static void close() {try {// 从当前线程中获取ConnectionConnection conn = threadLocal.get();if (conn != null) {conn.close();// 解除当前线程上绑定connthreadLocal.remove();}} catch (Exception e) {throw new RuntimeException(e);}}/*** @Method: getDataSource* @Description: 获取数据源* @Anthor:* @return DataSource*/public static DataSource getDataSource() {// 从数据源中获取数据库连接return ds;}}
使用JDK动态代理
创建一个JdkProxy代理类,来对目标对象的方法进行增强,代理核心方法updateSaveAll()
package com.qcby.JdkProxy;import com.qcby.service.AccountService;
import com.qcby.utils.TxUtils;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** 生成代理对象* 传入目标对象,生成该对象的代理对象,返回,在对目标对象的方法进行增强*/
public class JdkProxy {/*** 获取代理对象的方法 返回代理对象 对目标对象的方法进行增强* @param accountService* @return*/public static Object getProxy(AccountService accountService){/*** 使用JDK动态代理生成代理对象* 第一个参数:类的加载器* 第二个参数:当前传入的对象实现了哪些接口要字节码对象* 第三个参数:回调函数*/Object proxy = Proxy.newProxyInstance(JdkProxy.class.getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {/*** 调用代理对象的方法invoke方法就会执行* @param proxy* @param method* @param args* @return* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//System.out.println("你调用了代理对象的invoke方法了。。。");//对目标对象的方法进行增强Object result = null;try {//开启事务TxUtils.startTransaction();result = method.invoke(accountService, args);//提交事务TxUtils.commit();} catch (Exception e) {e.printStackTrace();//回滚事务TxUtils.rollback();} finally {TxUtils.close();}return result;}});return proxy;}
}
配置文件
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><bean id="accountService" class="com.qcby.service.Impl.AccountServiceImpl"><property name="accountDao" ref="accountDao"/></bean><bean id="accountDao" class="com.qcby.dao.AccountDaoImpl"/>
</beans>
编写测试类
package com.qcby.springAopTest;import com.qcby.JdkProxy.JdkProxy;
import com.qcby.model.Account;
import com.qcby.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;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Test01 {@Autowiredprivate AccountService accountService;@Testpublic void run(){Account account1 = new Account(null,"aaa",-1000.00);Account account2 = new Account(null,"bbb",1000.00);//accountService.updateSaveAll(account1,account2);Object proxyobj = JdkProxy.getProxy(accountService);//强转成accountService因为生成代理对象时候是写死的AccountService proxy = (AccountService) proxyobj;//在调用增强后的代理对象的savaAll方法//accountService.updateSaveAll(account1,account2);proxy.updateSaveAll(account1,account2);}}
AOP相关的概念
AOP的概述
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程
- AOP是一种编程范式,隶属于软工范畴,指导开发者如何组织程序结构
- 通过预编译方式或者运行期动态代理实现程序功能的统一维护的一种技术
- AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型
- 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)
可以在不修改源代码的前提下,对程序进行增强!!
减少重复的代码,提供开发的xiaolv,维护方便
AOP的底层原理
AOP是基于动态代理实现的,动态分为两种
JDK的动态代理技术 基于接口
1、为接口创建代理类的字节码文件
2、使用ClassLoader将字节码文件加载到JVM
3、创建代理类实例对象,执行对象的目标方法
cglib代理技术 基于类
AOP相关的术语
名词 | 解释 |
Joinpoint(连接点) | 连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。 |
Pointcut(切入点) | 切入点是指我们要对哪些Joinpoint进行拦截的定义 |
Advice(通知/增强) | 通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能) |
Target(目标对象) | 代理的目标对象 |
Weaving(织入) | 把增强应用到目标对象来创建新的代理对象的过程 |
Proxy(代理) | 一个类被AOP织入增强后,就产生一个结果代理类 |
Aspect(切面) | 切入点和通知的结合,以后咱们自己来编写和配置的 |
AOP配置文件方式
创建maven项目,引入依赖
<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.0.2.RELEASE</version></dependency><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.12</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><!-- AOP联盟 --><dependency><groupId>aopalliance</groupId><artifactId>aopalliance</artifactId><version>1.0</version></dependency><!-- Spring Aspects --><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.0.2.RELEASE</version></dependency><!-- aspectj --><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.3</version></dependency></dependencies>
创建applicationContext_demo2.xml配置文件,引入具体的AOP的schema约束
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"></beans>
创建包结构,编写具体的接口和实现类
接口
package com.qcby.demo2;public interface UserService {public void save();
}
实现类
package com.qcby.demo2;public class UserServiceImpl implements UserService{@Overridepublic void save() {System.out.println("业务层:保存用户...");}
}
将目标类配置到Spring中
<!--管理bean对象--><bean id="userService" class="com.qcby.demo2.UserServiceImpl"/>
定义切面类
package com.qcby.demo2;/*** 自定义切面类 = 切入点(表达式) + 通知(增强的代码)*/
public class MyXmlAspect {/*** 通知*/public void log(){//发送手机端行//发送邮件/记录日志/事务管理System.out.println("增强的方法执行了。。。");}
}
在配置文件中定义切面类
<!--配置切面类,把该类交给IOC容器管理--><bean id="myXmlAspect" class="com.qcby.demo2.MyXmlAspect"/>
在配置文件中完成aop的配置
<!--配置AOP的增强--><aop:config><!--配置切面 = 切入点 + 通知组成--><aop:aspect ref="myXmlAspect"><!--前置通知:UserServiceImpl的save方法执行前,会增强--><aop:before method="log" pointcut="execution(public void com.qcby.demo2.UserServiceImpl.save())"/></aop:aspect></aop:config>
完成测试
package com.qcby.demo2Test;import com.qcby.demo2.UserService;
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;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext_demo2.xml")
public class demo2Test {@Autowiredprivate UserService userService;/*** 测试*/@Testpublic void run1(){userService.save();}
}
测试结果
切入点的表达式
再配置切入点的时候,需要定义表达式,具体展开如下:
切入点表达式的格式如下:
- execution([修饰符] 返回值类型 包名.类名.方法名(参数))
- 修饰符可以省略不写,不是必须要出现的。
- 返回值类型是不能省略不写的,根据你的方法来编写返回值。可以使用 * 代替。
- 包名例如:com.tx.demo3.BookDaoImpl
- 首先com是不能省略不写的,但是可以使用 * 代替
- 中间的包名可以使用 * 号代替
- 如果想省略中间的包名可以使用 ..
- 类名也可以使用 * 号代替,也有类似的写法:*DaoImpl
- 方法也可以使用 * 号代替
- 参数如果是一个参数可以使用 * 号代替,如果想代表任意参数使用 ..
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd">
<!--管理bean对象--><bean id="userService" class="com.qcby.demo2.UserServiceImpl"/>
<!--配置切面类,把该类交给IOC容器管理--><bean id="myXmlAspect" class="com.qcby.demo2.MyXmlAspect"/>
<!--配置AOP的增强--><aop:config><!--配置切面 = 切入点 + 通知组成--><aop:aspect ref="myXmlAspect"><!--前置通知:UserServiceImpl的save方法执行前,会增强<aop:before method="log" pointcut="execution(public void com.qcby.demo2.UserServiceImpl.save())" />-->
<!--切入点的表达式execution() 固定的写法public 是可以省略不写的方法的返回值 int String 通用的写法,可以编写 * 不能省略不写的包名+类名 不能省略不写的,编写 * UserServiceImpl AccountServiceImpl方法名称 save() 可以写 *参数列表 (..) 表示任意类型和个数的参数比较通用的表达式:execution(public * com.qcby.*.*ServiceImpl.*(..))--><aop:before method="log" pointcut="execution(* com.qcby.*.*ServiceImpl.save*(..))" />
</aop:aspect></aop:config>
</beans>
AOP的通知类型
通知类型 | 类型说明 |
before | 前置通知 目标方法执行前,进行增强。 |
afte | 最终通知 目标方法执行成功或者失败,进行增强。 |
after-returning | 后置通知 目标方法执行成功后,进行增强。 |
after-throwing | 异常通知 目标方法执行失败后,进行增强。 |
around | 环绕通知 目标方法执行前后,都可以进行增强。目标对象的方法需要手动执行。 |
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd">
<!--管理bean对象--><bean id="userService" class="com.qcby.demo2.UserServiceImpl"/>
<!--配置切面类,把该类交给IOC容器管理--><bean id="myXmlAspect" class="com.qcby.demo2.MyXmlAspect"/>
<!--配置AOP的增强--><aop:config><!--配置切面 = 切入点 + 通知组成--><aop:aspect ref="myXmlAspect"><!--AOP的通知类型前置通知:目标方法执行前,进行增强。<aop:before method="log" pointcut="execution(* com.qcby.*.*ServiceImpl.save*(..))" />最终通知:目标方法执行成功或者失败,进行增强。<aop:after method="log" pointcut="execution(* com.qcby.*.*ServiceImpl.save*(..))" />后置通知:目标方法执行成功后,进行增强。<aop:after-returning method="log" pointcut="execution(* com.qcby.*.*ServiceImpl.save*(..))" />异常通知:目标方法执行失败后,进行增强。<aop:after-throwing method="log" pointcut="execution(* com.qcby.*.*ServiceImpl.save*(..))" />环绕通知:目标方法执行前后,都可以进行增强。目标对象的方法需要手动执行。--><aop:around method="aroundLog" pointcut="execution(* com.qcby.*.*ServiceImpl.save*(..))" />
</aop:aspect></aop:config>
</beans>
Spring的AOP技术-注解方式
AOP注解方式入门程序(半注解)
给切面类添加注解 @Aspect,编写增强的方法,使用通知类型注解声明
package com.qcby.demo3;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;@Component // 把该类交给IOC去管理
@Aspect // 声明是切面类 == <aop:aspect ref="myXmlAspect">
public class MyAnnoAspect {/*** 通知的方法*///@Before(value = "切入点表达式")@Before(value = "execution(public * com.qcby.demo3.OrderServiceImpl.save(..))")public void log(){System.out.println("增强了。。。");}
}
配置文件中开启自动代理
<aop:aspectj-autoproxy/>
编写测试
/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext_demo3.xml")
public class Demo3 {
@Autowiredprivate OrderService orderService;
/*** 测试*/@Testpublic void run1(){orderService.save();}
}
通知类型的注解
通知类型注解
注解 | |
@Before | 前置通知 |
@AfterReturing | 后置通知 |
@Around | 环绕通知(目标对象方法默认不执行的,需要手动执行) |
@After | 最终通知 |
@AfterThrowing | 异常抛出通知 |
纯注解的方式
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration // 配置类
@ComponentScan(value = "com.qcby.demo3") // 扫描包
@EnableAspectJAutoProxy // 开启自动代理 == <aop:aspectj-autoproxy />
public class SpringConfig {}