Spring AOP面向切面编程
什么是AOP?
AOP全称Aspect Oriented Programming
意为面向切面编程,也叫做面向方法编程,是通过预编译方式和运行期动态代理的方式实现不修改源代码的情况下给程序动态统一添加功能的技术。
简单来说:AOP就是面向切面编程 在不影响核心代码的前提下,可以在任意位置添加非核心代码。
利用AOP可以对业务逻辑各个部分进行隔离,从而使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高开发效率。
AOP的使用场景主要包括日志记录、性能统计、安全控制、事务处理、异常处理等。
AOP的本质是什么呢?
AOP是实现分散关注的编程方法,将关注封装在切面中。如何分散关注呢?
将需求功能从不相关的类中分离出来,同时使多个类共用一个行为,一旦行为发生变化,不必修改多个类,只修改行为即可。
AOP将软件系统划分为两个部分:核心关注点、横切关注点,业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的特点是经常发生在核心关注点的多个位置,而且它们功
能基本相似。AOP的作用在于分离系统中的各个关注点,将核心关注点和横切关注点分离开来。
AOP核心概念有哪些呢?
- 横切关注点
对哪些方法进行拦截,拦截后怎样处理。
Aspect
切面
切面是散落在系统各处通用的业务逻辑代码,如日志模块、权限模块、事务模块等。
切面用来装载切入点PointCut
和通知Advice
切面通常是一个类,可以定义切入点和通知。类是对物体特征的抽象,切面是对横切关注点的抽象。
切面是业务流程运行的某个特定步骤,是应用运行过程中的关注点,关注点通常会横切多个对象,因此也被称为横切关注点。
JointPoint
连接点
连接点是程序执行过程中明确的点,一般是类中方法的调用。连接点是程序在运行过程中能够插入切面的地点,比如方法调用、异常抛出、字段修改等。
Advice
通知
通知是AOP在特定切入点上执行的增强处理,是拦截到连接点之后要执行的代码,通知可以分为前置通知Before
、后置通知AfterReturning
、异常通知AfterThrowing
、最终通知After
、环绕通知Around
五类。
PointCut
切入点
切入点是带有通知的连接点,在程序中主要体现为编写切入点表达式。切入点是对连接点进行拦截的定义。切入点用于定义通知应该切入到哪些连接点上,不同的通知需要切入到不同的连接点上,这种精
准的匹配是由切入点的正则表达式来定义的。
切入点是可以插入增强处理的连接点,当某个连接点满足执行要求时,该连接点将被连接增强处理,该连接点也就变成了切入点。
切入点是拦截的方法,连接点JointPoint
拦截后将变成切入点。
Proxy
代理对象
代理对象是AOP创建的对象,包含通知,代理是目标对象的加强。 代理是将通知应用到目标对象之后被动态创建的对象,可以简单理解代理对象的功能等同于目标对象的核心业务逻辑功能加上共有功
能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。
Weaving
织入
通过切入点切入,将切面应用到目标对象并导致代理对象创建的过程。将切面应用到目标对象从而创建一个新的代理对象的过程,这个过程可以发生在编译器、类转载期、运行期,不同的发生点有着不同
的前提条件。如果发生在编译器就需要有一个支持这种AOP实现的特殊编译器,发生在类转载期就需要有一个支持AOP实现的特殊类转载其,发生在运行期则可以直接通过反射机制与动态代理机制来动态实现。
Target
目标对象
目标对象是指代理的目标对象,是指要织入的对象模块。目标对象是那些即将切入切面的对象,也就是被通知的对象。这些对象中已经只剩下干干净净的核心业务逻辑代码,所有的共有功能等待AOP容器
的切入。
目标对象是AOP进行增强处理的对象,也被称为增强的对象。如果AOP是通过运行时代理来实现的,那么这个对象将是一个被代理的对象。
MyProxy 类实现
public class MyProxy { //目标对象 private ArithmeticCalculator target; //构造函数 public MyProxy(ArithmeticCalculator a){ target=a; } public ArithmeticCalculator getProxyInstance(){ // ClassLoader loader, 被代理对象的加载器 // Class<?>[] interfaces, 被代理对象实现的接口 // InvocationHandler h: 被代理对象哪些行为需要被代理 ClassLoader classLoader = target.getClass().getClassLoader(); Class<?>[] interfaces={ArithmeticCalculator.class}; InvocationHandler h=new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("BBB--->The "+method.getName()+" method begin with "+ Arrays.asList(args)); Object result = method.invoke(target, args); System.out.println("BBB--->The "+method.getName()+" method ends: "+result); return result; } }; return (ArithmeticCalculator) Proxy.newProxyInstance(classLoader,interfaces,h); } }
public class Test { public static void main(String[] args) { ArithmeticCalculator target=new ArithmeticCalculatorImpl(); //创建了王宝强对象 MyProxy myProxy=new MyProxy(target); //创建代理类对象 //得到代理对象 ArithmeticCalculator proxyInstance = myProxy.getProxyInstance(); Double result = proxyInstance.sub(10, 2); System.out.println(result); } }
(1) 把相关spring的依赖加入
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.9.RELEASE</version> </dependency> <!--切面依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.9.RELEASE</version> </dependency> </dependencies>
(2)创建接口类
public interface ArithmeticCalculator { /** * 加法运算 * @param a * @param b * @return */ public Double add(double a, double b); /** * 减法运算 * @param a * @param b * @return */ public Double sub(double a, double b); /** * 乘法运算 * @param a * @param b * @return */ public Double mul(double a, double b); /** * 除法运算 * @param a * @param b * @return */ public Double div(double a, double b); }
(3)创建接口实现类
@Component(value = "a") public class ArithmeticCalculatorImpl implements ArithmeticCalculator { public Double add(double a, double b) { double c=a+b; return c; } public Double sub(double a, double b) { double c=a-b; return c; } public Double mul(double a, double b) { double c=a*b; return c; } public Double div(double a, double b) { double c=a/b; return c; } }
(4)创建一个切面类
@Component @Aspect public class LogAspect { @Before(value = "execution(* com.aaa.after.ArithmeticCalculatorImpl.*(..))") public void before(JoinPoint joi){ String name = joi.getSignature().getName();//得到方法对象 Object[] args = joi.getArgs(); System.out.println("BBB--->The "+name+" method begin with "+ Arrays.toString(args)); } @After(value = "execution(* com.aaa.after.ArithmeticCalculatorImpl.*(..))") public void after(JoinPoint joi){ String name = joi.getSignature().getName();//得到方法对象 Object[] args = joi.getArgs(); System.out.println("BBB--->The "+name+" method ends "); } }
(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" xmlns:mvc="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 https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!--包扫描--> <context:component-scan base-package="com.ykq.aop.after"/> <!--开启切面注解--> <mvc:aspectj-autoproxy /> </beans>
(6)测试类
public class Test { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml"); ArithmeticCalculator a = (ArithmeticCalculator) context.getBean("a"); Double add = a.sub(10, 5); System.out.println(add); } }
案例:买书(余额不足无法购买书,库存数量不会减少)
(建相关的包及实现类)
(2)dao层
public interface BookDao { /** * 根据图书编号查询图书价格 * @param isbn * @return */ int findBookPriceByIsbn(String isbn); /** * 根据图书编号修改库存数量 * @param isbn */ void updateBookStock(String isbn); /** * 根据用户名修改用户余额 * @param username * @param money */ void updateAccount(@Param("username") String username, @Param("money")int money); /** * 根据图书编号查询图书库存 * @param isbn * @return */ int findStockByIsbn(String isbn); /** * 根据用户名查询用户余额 * @param usname * @return */ int findBalanceByUserName(String usname); }
(3)service层
public interface BookService { /** * 下单 * @param username * @param isbn */ void purchase(String username,String isbn); }
@Service public class BookServiceImpl implements BookService { @Autowired private BookDao bookDao; @Override @Transactional public void purchase(String username, String isbn) { //1、根据isbn查询图书价格 int price = bookDao.findBookPriceByIsbn(isbn); //查询库存 int stock = bookDao.findStockByIsbn(isbn); if(stock>0){ //2、根据isbn修改库存 bookDao.updateBookStock(isbn); }else { throw new RuntimeException("库存不足"); } //查询用户余额 int balance = bookDao.findBalanceByUserName(username); if(balance>=price){ //3、根据用户修改余额 bookDao.updateAccount(username,price); }else { throw new RuntimeException("余额不足,请及时充值"); } } }
(4)dao层实现
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.aaa.dao.BookDao"> <!-- 根据编号修改图书库存数量--> <update id="updateBookStock"> update book_stock set stock=stock-1 where isbn=#{isbn} </update> <!-- 根据username修改余额--> <update id="updateAccount"> update account set balance=balance-#{money} where username=#{username} </update> <!-- 根据编号查询图书价格--> <select id="findBookPriceByIsbn" resultType="java.lang.Integer"> select price from book where isbn=#{isbn} </select> <!-- 根据编号查询库存数量--> <select id="findStockByIsbn" resultType="java.lang.Integer"> select stock from book_stock where isbn=#{isbn} </select> <!-- 根据用户名查询余额--> <select id="findBalanceByUserName" resultType="java.lang.Integer"> select balance from account where username=#{usname} </select> </mapper>
(5)配置xml文件
<?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:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 包扫描--> <context:component-scan base-package="com.aaa.service"/> <!-- 配置数据源--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/school_db"/> <property name="username" value="root"/> <property name="password" value="123@qwe"/> </bean> <!-- 创建工厂--> <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.aaa.dao"/> </bean> <!--①事务类:理解为切面类--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!--②开启事务注解--> <tx:annotation-driven/> </beans>
(6)测试
public class ShopTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); BookService bookService = (BookService)context.getBean("bookServiceImpl"); bookService.purchase("ykq","1002"); } }
数据库表原数据
用户余额
图书库存
(1)测试开始
购买成功后的表数据
(2)再次购买
数据库表内容不变