目录
1.什么是AOP
AOP(Aspect Oriented Programming)面向切面思想,是Spring的三大核心思想之一(AOP-面向切面、IOC-控制反转、DI-依赖注入)。- AOP全名Aspect Oriented Programming,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- AOP是面向切面编程的语言,它可以让你的业务代码和非业务代码进行隔离。在不改变业务代码的前提下,可以增加新的非业务代码。
2.为什么使用AOP
Java是一个面向对象(OOP)的编程语言,但它有个弊端就是当需要为多个不具有继承关系的对象引入一个公共行为时,例如日志记录、权限校验、事务管理、统计等功能,只能在每个对象里都引用公共行为, 这样做不便于维护,而且有大量重复代码,AOP的出现弥补了OOP的 这点不足。
举例:
上图得知有多少个业务操作,就要写多少重复的校验和日志记录代码,这显然是无法接受的。当然用面向对象的思想,可以把这些重复的代码抽离出来,写成公共方法如下图所示:
此时代码冗余和可维护性的问题得到解决,但每个业务方法中依然要依次手动调用这些公共方法,也是略显繁琐。 此时就可以使用AOP,AOP将权限校验、日志记录等非业务代码完全提取出来,与业务代码分离,并寻找节点切入业务代码中 。
3. AOP应用场景
AOP可用于权限校验、记录日志、事务处理。
4. AOP原理
之前提到 JDK 代理和 Cglib 代理两种动态代理,优秀的Spring框架把两种方式在底层都集成了进去,无需担心自己去实现动态生成代理。那么Spring 是如何生成代理对象的?创建容器对象的时候,根据切入点表达式拦截的类,生成代理对象。如果目标对象有实现接口,使用jdk代理。如果目标对象没有实现接口,则使用Cglib 代理。然后从容器获取代理后的对象,在运行期植入 " 切面 " 类的方法。如果目标类没有实现接口,且 class 为 fifinal 修饰的,则不能进行Spring AOP 编程。
5. AOP体系结构
使用AOP要做三件事情:
①在哪里切入:就是权限校验,等业务操作在哪些业务代码中执行;
②什么时候切入:业务代码执行前还是业务代码执行后;
③切入之后做什么:例如权限校验、日志记录、事务处理;
- Pointcut:切点,决定处理如权限校验、日志记录等在何处切入业务代码中(即织入切面)。切点分为execution(路径表达式)方式和annotation(注解形式)方式。前者可以用路径表达式指定哪些类织入切面,后者可以指定被哪些注解修饰的代码织入切面。
- Advice:处理,包括处理时机和处理内容。处理内容就是要做什么事, 比如校验权限和记录日志。处理时机就是在什么时机执行处理内容,分为前置处理(即业务代码执行前)、后置处理(业务代码执行后)等。
- Aspect:切面,即Pointcut和Advice。
- Joint point:连接点,是程序执行的一个点。例如,一个方法的执行或者一个异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。
- Weaving:织入,就是通过动态代理,在目标对象方法中执行处理内容的过程。
6. 如何使用AOP
@Service
public class MathServiceImpl implements MathService {
@Override
public double add(double a, double b) {
double result = a+b;
System.out.println("AAA日志 The add method result="+result);
return result;
}
@Override
public double sub(double a, double b) {
double result = a-b;
System.out.println("AAA日志 The sub method result="+result);
return result;
}
@Override
public double mul(double a, double b) {
double result = a*b;
System.out.println("AAA日志 The mul method result="+result);
return result;
}
@Override
public double div(double a, double b) {
double result = a/b;
System.out.println("AAA日志 The div method result="+result);
return result;
}
}
由上述代码可以看出,如果我们想要修改日志,必须在业务源代码上进行修改,不仅操作繁多,而且不利于代码的维护,因此就需要使用AOP来解决。
6.1路径表达式(execution)模式
(1)引入相关依赖
<dependencies>
<!--引入Spring核心依赖库-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<!--引入Spring切面依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
</dependencies>
(2)创建一个切面类
//表示该类为切面类
@Aspect
//表示该类由spring管理
@Component
public class MyAspect {
//定义切点
//路径表达式方式
@Pointcut("execution(public double com.aaa.demo01.MathServiceImpl.add(double,double))")
public void myPointcut(){}
@Before(value="myPointcut()")
public void before(){
System.out.println("AAA日志 the add method begin");
}
@After(value="myPointcut()")
public void after(){
System.out.println("AAA日志 The add method end");
}
}
(3) 创建spring配置文件
<?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" xmlns:mvc="http://www.springframework.org/schema/mvc"
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 http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--包扫描-->
<context:component-scan base-package="com.aaa.demo01"/>
<!--开启切面注解的驱动:导致spring无法其他@Aspect @Before @Pointcut-->
<aop:aspectj-autoproxy/>
</beans>
(4)创建一个测试类
public class Test {
public static void main(String[] args) {
//加载Spring配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
MathService mathServiceImpl = (MathService) applicationContext.getBean("mathServiceImpl");
System.out.println(mathServiceImpl.add(10,10));
}
}
(5)使用通配符来通配类路径
//表示该类为切面类
@Aspect
//表示该类由spring管理
@Component
public class MyAspect {
//定义切点
//路径表达式方式
/**
* 第一个*:表示任意访问修饰符和返回类型
* 第二个*:表示包下的任意类
* 第三个*:表示任意方法
* ..:表示任意参数类型和个数
*/
@Pointcut("execution(* com.aaa.demo01.*.*(..))")
public void myPointcut(){}
@Before(value="myPointcut()")
public void before(){
System.out.println("AAA日志 the add method begin");
}
@After(value="myPointcut()")
public void after(){
System.out.println("AAA日志 The add method end");
}
}
6.2 自定义注解模式(annotation)
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "";
}
//②注解方式
@Pointcut(value = "@annotation(com.aaa.demo01.MyAnnotation)") //定义为切点
private void myPointcut2(){}
//前置通知
@Before(value="myPointcut2()")
public void before(){
System.out.println("AAA日志 the add method begin");
}
public class MathServiceImpl implements MathService {
@MyAnnotation
@Override
public double add(double a, double b) {
double result = a+b;
System.out.println("~~~~~~~~~~");
return result;
}
}
6.3 aop切面通知的类型
- @Before 前置通知. 被代理的方法执行前--执行
- @After: 后置通知: 被代理的方法执行完后--执行
- @AfterReturning: 后置返回通知: 被代理的方法碰到return.--才会执行
- @AfterThrowing: 后置异常通知: 当被代理的方法出现异常时--才会执行。
- @Around: 环绕通知。
//表示该类为切面类
@Aspect
//表示该类由spring管理
@Component
public class MyAspect {
//定义切点
//路径表达式方式
/**
* 第一个*:表示任意访问修饰符和返回类型
* 第二个*:表示包下的任意类
* 第三个*:表示任意方法
* ..:表示任意参数类型和个数
*/
@Pointcut("execution(* com.aaa.demo01.*.*(..))")
public void myPointcut(){}
// //前置通知
// @Before(value="myPointcut()")
// public void before(){
// System.out.println("AAA日志 the add method begin");
// }
// //在使用MyAnnotation注解的方法之后执行内容,无论如何都执行
// @After(value="myPointcut()")
// public void after(){
// System.out.println("AAA日志 The add method end");
// }
//
// //后置返回通知,碰到return,如果方法出现异常;这种通知不会被执行
// //returning会把方法执行的结果赋值给该变量
// @AfterReturning(value = "myPointcut()",returning = "r")
// //参数名必须和returning的名称一致
// public void afterReturning(Object r){
// System.out.println("~~~后置返回通知~~~"+r);
// }
//
// //后置异常通知:当被切入的方法出现异常时,才会被执行
// @AfterThrowing(value = "myPointcut()")
// public void afterThrowable(){
// System.out.println("~~~异常通知~~~");
// }
//环绕通知
//joinPoint:连接点可以看成是被执行的方法对象
@Around(value = "myPointcut()")
public Object around(ProceedingJoinPoint joinPoint){
System.out.println("业务代码执行前执行~~~");
try {
//执行连接点
Object result = joinPoint.proceed();
System.out.println("方法执行后~~~");
return result;
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("方法出现异常时~~~");
}finally{
System.out.println("任何时间段都可以执行~~~");
}
return 0.0;
}
}
7. spring如何操作事务
7.1 什么是事务?
事务介绍:
- 事务就是用户定义的一系列执行SQL语句的操作, 这些操作要么完全地执行,要么完全地都不执行, 它是一个不可分割的工作执行单元。
- 事务的使用场景: 在日常生活中,有时我们需要进行银行转账,这个银行转账操作背后就是需要执行多个SQL语句,假 如这些SQL执行到一半突然停电了,那么就会导致这个功能只完成了一半,这种情况是不允许出现, 要想解决这个问题就需要通过事务来完成。
事务的四大特性:
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(Isolation)
- 持久性(Durability)
- 原子性: 一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么 全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性。
举例:转账
JDBC完成事务管理
public class Test {
public static void main(String[] args) {
Connection connection=null;
try{
Class.forName("com.mysql.cj.jdbc.Driver");
connection= DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai","root","123456");
//把事务设置为手动提交
connection.setAutoCommit(false);
PreparedStatement preparedStatement = connection.prepareStatement("update t_user set balance=balance-100 where id=1");
preparedStatement.executeUpdate();
int a=10/0;
PreparedStatement preparedStatement1 = connection.prepareStatement("update t_user set balance=balance+100 where id=2");
preparedStatement1.executeUpdate();
//事务提交
connection.commit();
}catch(Exception e){
e.printStackTrace();
//发现异常,事务回滚
try {
connection.rollback();
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
}finally {
}
}
}
7.2 spring如何实现事务
- 前置通知---开启手动事务
- 后置返回通知[事务提交]
- 异常通知[事务回滚]
①添加依赖
<dependencies>
<!--引入Spring核心依赖库-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<!--引入Spring切面依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
<!--spring事务依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.12.RELEASE</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>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.15.RELEASE</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>
</dependencies>
②spring配置文件
<?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/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.ykq以及该包下的子包-->
<context:component-scan base-package="com.aaa"/>
<!--spring整合mybatis的配置-->
<!--数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<!--mysql驱动为8.0以后必须使用时区-->
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!--spring封装了一个类SqlSessionFactoryBean类,可以把mybatis中的配置-->
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:/mapper/*.xml"/>
</bean>
<!--为指定dao包下的接口生产代理实现类-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sessionFactory"/>
<!--它会为com.ykq.dao包下的所有接口生产代理实现类-->
<property name="basePackage" value="com.ykq.dao"/>
</bean>
<!--事务切面管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启事务管理注解的驱动-->
<tx:annotation-driven/>
</beans>
③dao层和mapper
public interface UserDao {
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">
<mapper namespace="com.aaa.dao.UserDao">
<update id="updateBalance">
update t_user set balance=balance+#{money} where id=#{id}
</update>
</mapper>
④service层
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
//该方法交给Spring的事务管理,默认Spring不识别。
@Transactional
public void updateBalance(int id, int uid, double money) {
//转钱
userDao.updateBalance(id,-money);
int a=10/0;
//收钱
userDao.updateBalance(uid,money);
}
}
⑤测试类
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("classpath:spring.xml");
UserService userServiceImpl = (UserService) applicationContext.getBean("userServiceImpl");
userServiceImpl.updateBalance(1,2,500.0);
}
}