1.什么是AOP
AOP(Aspect Orient Programming)面向切面编程,在传统OOP中,纵向抽成一个个对象,在AOP中可以将一个个对象的某些类似的方法横向抽成一个切面,降低代码的重复性,方便管理。
切面(Aspect):切面为那些横跨多个对象的功能从主业务逻辑中分离出来,形成切面。
切点(Pointcut):主要定义在哪些地方应用,可以通过路径表达式或者注解匹配到目标方法或类
处理(Advice):定义了处理内容以及处理时机比如:前置通知Before(在目标方法执行之前调用)、后置通知After(在目标方法执行之后调用,不考录是否发生异常)、返回通知AfterReturning(在目标方法成功执行后调用)和异常通知AfterThrowing(在目标方法抛出异常后调用)
2.为什么使用AOP
(1)将共同代码抽成切面,集中管理,避免代码重复
(2)业务逻辑和非业务逻辑(如事务、权限、日志)得以分离,提高代码维护
(3)可以在不改变源代码的情况下,添加、修改和删除功能
案例:在加减乘除方法中添加记录日志操作
public class MathServiceImpl implements MathService {
/**
* 加法
*
* @param a
* @param b
* @return
*/
@Override
public double add(double a, double b) {
double result = a + b;
System.out.println("测试记录日志");
return result;
}
/**
* 减法
*
* @param a
* @param b
* @return
*/
@Override
public double subtract(double a, double b) {
double result = a - b;
System.out.println("测试记录日志");
return result;
}
/**
* 乘法
*
* @param a
* @param b
* @return
*/
@Override
public double multiply(double a, double b) {
double result = a * b;
System.out.println("测试记录日志");
return result;
}
/**
* 除法
*
* @param a
* @param b
* @return
*/
@Override
public double div(double a, double b) {
double result = a / b;
System.out.println("测试记录日志");
return result;
}
}
发现需要每个都需要添加相同的代码,如果日后进行修改的话比较麻烦,不利于维护。下面我们使用AOP将这个记录日志抽取成切面,切点为这个类的所有方法,处理为后置返回通知。
3.如何使用AOP
3.1引入相关依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
3.2配置xml:
<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">
<!--包扫描-->
<context:component-scan base-package="com.gjc.demo01"/>
<!--开启AOP切面注解驱动-->
<aop:aspectj-autoproxy/>
</beans>
3.3创建切面类
切面
@Aspect 标识为注解类
切点
@Pointcut 表示为切点 可以设置路径或者注解
通知
@Before前置通知:在目标方法执行之前
@After后置通知:在目标方法执行之后调用,不考录是否发生异常 ,相当与异常中的finally
@AfterReturning后置返回通知:在目标方法执行成功后调用
@AfterThrowing异常通知:在目标方法抛出异常后调用
@Around环绕通知:结合以上通知
完成以上案例:
将记录日志抽成一个切面
@Aspect //标志为切面类
@Component //标志为交给spring容器管理
public class logAspect {
//定义切点 访问修饰符为public 返回类型double 在com.gjc.demo01.service.impl.MathServiceImpl下的所有方法
@Pointcut(value = "execution(public double com.gjc.demo01.service.impl.MathServiceImpl.*(..))")
public void point(){
};
//定义通知
@AfterReturning(value = "point()")//后置返回通知 方法执行成功后通知
public void a(){
System.out.println("测试记录日志");
}
}
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("spring.xml");
MathService mathService = (MathService) app.getBean("mathServiceImpl");
mathService.multiply(10, 20);//乘法
mathService.add(10,20);//加法
}
}
运行结果:每个方法后都执行了记录日志
乘法200.0
测试记录日志
加法30.0
测试记录日志
3.4可以使用通配符来配置路径
//第一个*表示可以为任意修饰符和任意返回类型
//第二个*表示可以为任意类
//第三个*表示可以为任意方法
//..表示为任意参数
@Pointcut(value = "execution(* com.gjc.demo01.service.impl.*.*(..))")
public void point(){
};
3.5也可以使用注解模式来定义切点
创建注解:
@Target(ElementType.METHOD) //只能用在方法上
@Retention(RetentionPolicy.RUNTIME) //运行时生效
public @interface MyAnnotation {
}
定义切点:
@Pointcut(value = "@annotation(com.gjc.demo01.annotation.MyAnnotation)") //定义为切点
public void point(){
}
//定义通知
@AfterReturning(value = "point()")//后置返回通知 方法执行成功后通知
public void a(){
System.out.println("测试记录日志");
}
添加注解:
package com.gjc.demo01.service.impl;
import com.gjc.demo01.annotation.MyAnnotation;
import com.gjc.demo01.service.MathService;
import org.springframework.stereotype.Service;
@Service
public class MathServiceImpl implements MathService {
/**
* 加法
*
* @param a
* @param b
* @return
*/
@MyAnnotation
@Override
public double add(double a, double b) {
double result = a + b;
System.out.println("加法"+result);
return result;
}
/**
* 减法
*
* @param a
* @param b
* @return
*/
@MyAnnotation
@Override
public double subtract(double a, double b) {
double result = a - b;
System.out.println("减法"+result);
return result;
}
/**
* 乘法
*
* @param a
* @param b
* @return
*/
@Override
public double multiply(double a, double b) {
double result = a * b;
System.out.println("乘法"+result);
return result;
}
/**
* 除法
*
* @param a
* @param b
* @return
*/
@Override
public double div(double a, double b) {
double result = a / b;
System.out.println("除法"+result);
return result;
}
}
测试:只有加法和减法用了注解的才是切入点
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("spring.xml");
MathService mathService = (MathService) app.getBean("mathServiceImpl");
mathService.add(10,20);//加法
mathService.subtract(10,20);//减法
mathService.multiply(10, 20);//乘法
}
}
加法30.0
测试记录日志
减法-10.0
测试记录日志
乘法200.0
3.4通知类型
@Before(value = "point()") //前置通知 在方法执行之前通知
public void b(){
System.out.println("----方法执行之前----");
}
@After(value = "point()") //后置通知 在方法执行之后通知 不考录是否发生异常
public void c(){
System.out.println("----方法执行之后----");
}
@AfterReturning(value = "point()") //后置返回通知 方法执行成功后通知
public void a(){
System.out.println("测试记录日志");
}
@AfterThrowing(value ="point()")//异常通知 方法抛出异常通知
public void d(){
System.out.println("----异常执行----");
}
测试:
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("spring.xml");
MathService mathService = (MathService) app.getBean("mathServiceImpl");
mathService.add(10,20);//加法
}
}
----方法执行之前----
加法30.0
----方法执行之后----
测试记录日志
模拟异常测试:
----方法执行之前----
----方法执行之后----
----异常执行----
环绕通知(重点):
@Around(value = "point()")
public Object e(ProceedingJoinPoint joinPoint){ //joinPoint连接点 理解为被执行的方法对象
System.out.println("前置通知");
try {
Object proceed = joinPoint.proceed();//执行链接点
System.out.println("后置返回通知");
return proceed;
} catch (Throwable e) {
e.printStackTrace();
System.out.println("异常通知");
}finally {
System.out.println("后置通知");
}
return null;
}
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("spring.xml");
MathService mathService = (MathService) app.getBean("mathServiceImpl");
mathService.add(10,20);//加法
}
}
前置通知
加法30.0
后置返回通知
后置通知
4事务
数据库操作的一种机制,要么全部成功要么全部不成功(回滚)
有四个特性:原子性Atomicity、一致性Consistency、隔离性Isolation、持久性Durability称为ACID
spring如何实现事务:
1.前置通知开启手动事务
2.后置返回通知对事物进行提交
3.异常通知事务回滚
使用spring提供的事务
1.添加依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<!--spring事务依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.12.RELEASE</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>
<!--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: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.gjc以及该包下的子包-->
<context:component-scan base-package="com.gjc"/>
<!--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/qy168?serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</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.gjc.dao包下的所有接口生产代理实现类-->
<property name="basePackage" value="com.gjc.dao"/>
</bean>
<!---================以下内容是关于事务的配置===================-->
<!--事务切面管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启事务管理注解的驱动-->
<tx:annotation-driven/>
</beans>
service:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public void zhuanzhang(int id, int uid, double money) {
userDao.update(id,-money);
int i=10/0; //模拟异常
userDao.update(uid,money);
}
}
dao:
@Mapper
public interface UserDao {
/**
* 根据id修改余额
* @param id
* @param money
*/
public void update(@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.gjc.dao.UserDao">
<update id="update">
update user set money=money+#{money} where id=#{id}
</update>
</mapper>
测试:id7给id8的用户转了200,id7钱减少了,但id8没增加
public static void main(String[] args) {
ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) app.getBean("userServiceImpl");
userService.zhuanzhang(7,8,200);
}
添加事务注解@Transactional
@Override
@Transactional //事务注解
public void zhuanzhang(int id, int uid, double money) {
userDao.update(id,-money);
int i=10/0; //模拟异常
userDao.update(uid,money);
}
转账中出现异常进行回滚,有一个失败都失败