1.什么是AOP?
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方
式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个
热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑
的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高
了开发的效率。
(1)面向切面编程(方面),利用AOP可以对业务逻辑的各个部分进行隔离,从而是的业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
(2)通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
2.为什么用AOP?
我们一般做活动的时候,一般对每一个接口都会做活动的有效性校验(是否开始、是否结束等等)、以及这个接口是不是需要用户登录。
按照正常的逻辑,我们一般是下面这样做的。
有多少个业务操作,就要写多少重复的校验和日志记录代码,这显然是无法接受的。当然用面向对象的思想,可以把这些重复的代码抽离出来,写成公共方法,就是下面这样
同样有个问题,我虽然不用每次都copy代码了,但是,每个接口总得要调用这个方法吧。于是就有了切面的概念,我将方法注入到接口调用的某个地方(切点)。
这样接口只需要关心具体的业务,而不需要关注其他非该接口关注的逻辑或处理。
橙色处,就是面向切面编程。
3.AOP应用场景
-
记录日志:比如说在某个模块操作的时候,会记录存到数据库的日志里面(比如说操作时间,登录时间,又没有操作成功等...)
-
权限校验:比如说操作某个方法的时候必须是当前用户拥有的权限才可以使用
-
spring事务管理:转账事务,开启事务就不用以前写的那么臃肿了
4.AOP的结构
AOP要做的三件事在哪里切入,也就是权限校验,等非业务操作在哪些业务 代码中执行;什么时候切入,是业务代码执行前还是执行后;切入后做什 么事,比如做权限校验、日志记录等。
Aspect(切面): Aspect 声明类似于 Java 中的类声明,定义了统一的类,叫切面类,在 Aspect 中会包含着一些 Pointcut(切点) 以及相应的 Advice()。
Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
Pointcut(切点):表示在哪儿些地方连接到,根据路径(execution)或者加有指定注解(annotation)的代码块切入
Advice(处理):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
Target(目标对象):织入 Advice 的目标对象.。
Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
5.如何使用AOP
以前的写法:
package com.aaa.service;
public interface MathService {
/**
* 加法运算
* @param a
* @param b
* @return
*/
public double add(double a, double b);
/**
* 减法运算
* @param a
* @param b
* @return
*/
public double mul(double a, double b);
/**
* 乘法运算
* @param a
* @param b
* @return
*/
public double cheng(double a, double b);
/**
* 除法运算
* @param a
* @param b
* @return
*/
public double div(double a, double b);
}
package com.aaa.service.impl;
import com.aaa.service.MathService;
/*
* 带着 不能改变代码的思想
* */
@Service
public class MathServiceImpl implements MathService {
public double add(double a, double b) {
double result=a+b;
System.out.println("The add method result="+result);
return result;
}
public double mul(double a, double b) {
double result=a-b;
System.out.println("The mul method result="+result);
return result;
}
public double cheng(double a, double b) {
double result=a*b;
System.out.println("The cheng method result="+result);
return result;
}
public double div(double a, double b) {
double result=a/b;
System.out.println("The div method result="+result);
return result;
}
}
发现: 我们在每个操作后,都要记录日志,如果后期日志内容发生改变。需要在每个操作后都进行修改。 不利于代码的维护。
我们来使用AOP来解决。
AOP流程
1.先引入依赖
<dependencies>
<!--引入spring核心依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.18</version>
</dependency>
<!--引入spring切面依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.18</version>
</dependency>
</dependencies>
2.创建一个切面类
这里传的是路径表示在哪些类哪些方法上切入
我们可以右键方法名复制它的全路径
package com.aaa.aop;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect//标记该类为切面类
@Component//该类对象的创建交于spring容器来管理-----等价于@Service @Controller
public class MyAspect {
@Pointcut(value = "execution(public double com.aaa.aop.MathServiceImpl.add(double ,double ))")
private void myFangFa(){}
@After(value = "myFangFa()")
public void a (){
System.out.println("The add method result");
}
}
3.创建一个spring.xml配置文件
<!-- springmvc的配置-->
<!-- 包扫描/配置注解扫描 扫描com.aaa.aop以及该包以下的子包-->
<contet:component-scan base-package="com.aaa.aop"/>
<!--开启aop切面注解驱动-->
<aop:aspectj-autoproxy/>
4.测试
注意ben首字母要小写
package com.aaa.aop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
//加载spring配置文件
ApplicationContext app=new ClassPathXmlApplicationContext("classpath:spring.xml");
MathService mathServiceImpl = (MathService) app.getBean("mathServiceImpl");
System.out.println(mathServiceImpl.add(20, 10));
}
}
要想用切点需要换路径,不然不会输出
下面这张是没有切换
5. 使用通配符来统配类路径
要想都给加入切点是这样的
但是万一要是有一万个 都用这种方式加是很麻烦的
解决:我们可以用通配符 *
第一个 * :表示任意修饰符 任意返回类型
第二个 * :表示该包下所有类
第三个 * :类下所有方法
.. : 表示任意参数
注意:参数这里是两个点,不是三个,三个就成了可变参数了
建议包就别使用通配符了,虽然也能用
package com.aaa.aop;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect//标记该类为切面类
@Component//该类对象的创建交于spring容器来管理-----等价于@Service @Controller
public class MyAspect {
//通配符*
/*
* 第一个 * :表示任意修饰符 任意返回类型
* 第二个 * :表示该包下所有类
* 第三个 * :类下所有方法
* .. :表示任意参数
*
* 建议包就别使用通配符了
* */
@Pointcut(value = "execution(* com.aaa.aop.*.*(..))")//定义为切点
private void myFangFa(){}
@After(value = "myFangFa()")
public void a (){
System.out.println("The div method result");
}
}
2.注解模式
1.定义一个自定义注解
@Target(value = {ElementType.ANNOTATION_TYPE,ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "";
}
2.加在方法上
3.修改切面方式为注解
注意注解前面也要加路径
package com.aaa.aop;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect//标记该类为切面类
@Component//该类对象的创建交于spring容器来管理-----等价于@Service @Controller
public class MyAspect {
//通配符*
/*
* 第一个 * :表示任意修饰符 任意返回类型
* 第二个 * :表示该包下所有类
* 第三个 * :类下所有方法
* .. :表示任意参数
*
* 建议包就别使用通配符了
* */
/*
@Pointcut(value = "execution(* com.aaa.aop.*.*(..))")//定义为切点
private void myFangFa(){}
*/
@Pointcut(value = "@annotation(com.aaa.aop.MyAnnotation)")//定义为切点
private void myFangFa2(){}
//在使用MyAnnotation注解的方法之后执行内容
@After(value = "myFangFa2()")
public void a (){
System.out.println("The add method result");
}
}
其实不加也是能用的,但是推荐还是加上
3.AOP切面通知的类型
@Before 前置通知. 被代理的方法执行前--执行
@After: 后置通知: 被代理的方法执行完后--执行
@AfterReturning: 后置返回通知: 被代理的方法碰到return.--才会执行
@AfterThrowing: 后置异常通知: 当被代理的方法出现异常时--才会执行。
@Around: 环绕通知。
执行顺序:
正常:前,后反,后置
异常:前,异常,后置
后置通知After:
无论如何都会执行
//后置通知:无论如何都会执行
@After(value = "myFangFa2()")
public void a (){
System.out.println("====方法执行后切面的内容 后置通知====");
}
前置通知Before:
//前置通知:
@Before(value = "myFangFa2()")
public void b(){
System.out.println("====方法执行前切面的内容 前置通知====");
}
后置返回通知AfterRetuning:
碰到return 有返回值的 ,如果方法出现异常,这种通知不会被执行
@AfterReturning(value = "myFangFa2()",returning = "r")//returning它会把方法执行的结果赋值给该变量
public void afterRetuning(Object r){//这里的名要和returning里面的一致
System.out.println("------ 后置返回通知内容 "+r);
}
异常通知AfterThrowing:
当被切入的方法出现异常时,才会执行
//异常通知:当被切入的方法出现异常时,才会执行
@AfterThrowing(value = "myFangFa2()")
public void afterThrowing(){
System.out.println("~~~~~~~~出现异常的通知~~~~~~~~~~");
}
环绕通知
需要返回类型,而且要跟返回类型一致,可以用Object
跳过了输出内容,需要执行连接点
发现返回的还是0.0是因为被中间直接返回了,需要返回一个结果
返回结果就好了
//环绕通知:上面的方法一个解决
@Around(value = "myFangFa2()")
public Object around(ProceedingJoinPoint joinPoint) {//joinPoint:连接点 理解为被执行的方法对象
System.out.println("业务代码执行前的内容");
try {
Object result = joinPoint.proceed();//执行你的连接点
System.out.println("后置返回通知内容"+result);
return result;//返回结果
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("方法出现异常时出现");
}finally {
System.out.println("无论如何都会执行");
}
return 0.0;
}
正常情况下:
出现异常下
6.spring如何操作事务?
1.什么是事务?
事务(Transaction)是访问并可能更新数据库中各项数据项的一个程序执行单元(unit)。 事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。
事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。
事务结束有两种,事务中的步骤全部成功执行时,提交事务。如果其中一个失败,那么将会发生回滚操作,并且撤销之前的所有操作。也就是说,事务内的语句,要么全部执行成功,要么全部执行失败。
事务是恢复和并发控制的基本单位。
例子:转账
扣钱和加钱----要么全部执行成功,要么全部执行失败。
像JDBC---它模式事务自动提交的:一个扣钱,就立马去更改数据库了
package com.aaa.transaction;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
public class Test {
public static void main(String[] args) {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/qy168?serverTimezone=Asia/Shanghai","root","qingfengzilai.");
PreparedStatement statement = conn.prepareStatement("update account set money=money-100 where id=1");//转钱
statement.executeUpdate();
PreparedStatement statement2 = conn.prepareStatement("update account set money=money+100 where id=2");//收钱
statement2.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}finally {
}
}
}
此时我们加个错误的数据发现转钱了,但是没有收钱,这是不合理的
开启事务
package com.aaa.transaction;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Test {
public static void main(String[] args) {
Connection conn=null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/qy168?serverTimezone=Asia/Shanghai","root","qingfengzilai.");
conn.setAutoCommit(false);//设置事务手动提交
PreparedStatement statement = conn.prepareStatement("update account set money=money-100 where id=1");//转钱
statement.executeUpdate();
int c=10/0;
PreparedStatement statement2 = conn.prepareStatement("update account set money=money+100 where id=2");//收钱
statement2.executeUpdate();
conn.commit();//事务提交
} catch (Exception e) {
e.printStackTrace();
try {
conn.rollback(); //事务回滚
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
}finally {
}
}
}
此时再发现不会转钱
我们上面演示的是JDBC完成事务管理, 这种事务管理很麻烦的,我们可以使用事务切面
2.spring如何实现事务
spring框架一定会提供一个事务切面类。【1】前置通知---开启手动事务 [2]后置返回通知[事务提交] [3]异常通知[事务回滚]
1.引入依赖
<dependencies>
<!--spring的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.18</version>
</dependency>
<!--mysql的驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
<!--切面的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.18</version>
</dependency>
<!--spring事务的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.18</version>
</dependency>
<!--jdbc的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.18</version>
</dependency>
<!--mybatis的依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
<!--mybatis和spring整合的依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<!--druid的连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
</dependencies>
2.配置spring.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:contet="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" 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/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- springmvc的配置-->
<!-- 包扫描/配置注解扫描 扫描com.aaa以及该包以下的子包-->
<contet:component-scan base-package="com.aaa.dao"/>
<!--spring整合mybatis的配置 可以获取所有myBatis的功能-->
<!--数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<!--mysql驱动为8.0以后必须使用时区 记得修改用哪个表-->
<property name="url" value="jdbc:mysql:///qy168?characterEncoding=UTF-8&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="qingfengzilai."/>
</bean>
<!-- spring封装了sqlSessionFactoryBen类,可以把mybatis中的配置在下面添加-->
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--映射所有的mapper下带xml的文件-->
<property name="mapperLocations" value="classpath:/Mapper/*.xml"/>
<!-- 起别名 返回值类型就是实体了 不用加前面的com.aaa了 -->
<property name="typeAliasesPackage" value="com.xqw.entity"/>
</bean>
<!--为指定dao包下的接口生产代理实现类,自己不用写了 不加这个serviceImpl层用不了@Autowired -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sessionFactory"/>
<!--它会为com.aaa.dao包下的所有接口生产代理实现类-->
<property name="basePackage" value="com.xqw.dao"/>
</bean>
<!---================以下内容是关于事务的配置===================>
<!-事务切面管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启事务管理注解的驱动-->
<tx:annotation-driven/>
</beans>
3.dao和xml
public interface UserDao {
//1.修改账号余额
public void updateBalance(@Param("id") int id, @Param("money") double money);
}
<?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">
<!--nameSpan必须跟dao接口的名称一样-->
<mapper namespace="com.aaa.dao.AccountDao">
<update id="updateBalance">
update account set money=money+#{money} where id=#{id}
</update>
</mapper>
4.service
package com.aaa.service.Impl;
import com.aaa.dao.AccountDao;
import com.aaa.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
@Transactional//该方法交于spring的事务来管理了---默认spring不识别该注解
public void zhuangZhang(int id, int uid, double money) {
//1.扣钱
accountDao.updateBalance(id,-money);
//2.收钱
accountDao.updateBalance(uid,money);
}
}
5.测试:
package com.aaa;
import com.aaa.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext app=new ClassPathXmlApplicationContext("classpath:spring.xml");
AccountService accountServiceImpl=(AccountService) app.getBean("accountServiceImpl");
accountServiceImpl.zhuangZhang(1,2,100);
}
}