1 AOP
1.1 简介
Spring框架众所周知有两大核心IOC与AOP,AOP 的全称是“Aspect Oriented Programming”,即面向切面编程且是OOP面向对象的延伸,主要用于在方法执行前的条件判断与log日志文件跟Spring事务管理。
1.2 为什么用AOP
我们在编写完业务代码后,有一天领导让你在所有CRUD操作(SQL语句)执行后追加到日志里,你会怎么做?这可以定义公用方法然后不停的调用?那如果代码很多呢入上千上万那我们一个个写就算写出来了。
如果领导又不要了呢显而易见可维护性非常的低,在AOP中只需要设置切入点(文件路径方法)以及相应的通知方法跟少量配置就可以了这对于我们添加日志等等的业务代码外的操作更加简便可维护性大大的提高。
1.3 如何用AOP
1.3.1 配置依赖
<dependencies>
<!--springwebmvc依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.17</version>
</dependency>
<!--springAOP面向切面编程依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.17</version>
</dependency>
</dependencies>
1.3.2 配置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: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 https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--注解扫描-->
<context:component-scan base-package="com.lby"/>
<!--aop注解开启-->
<aop:aspectj-autoproxy/>
</beans>
1.3.3 切面类创建
先定义接口与实现类为UserDao与UserDaoImpl
public interface UserDao {
public Integer select(Integer integer);
}
public class UserDaoImpl implements UserDao {
@Override
public Integer select(Integer integer) {
//随便定义点主要看效果
integer =+ integer;
return integer;
}
}
名称随便起我这里叫MyAspectj @Aspect确认当前类为切面类,@Component加入Spring容器
我们先别管那些通知切入点是什么意思我们先看效果,更容易明白。
@Component //加入到Spring容器里管理
@Aspect //确认是切面类
public class MyAspectj {
//切入点 执行 访问修饰符 返回类型 方法地址.方法名称(参数类型)
@Pointcut(value ="execution(public Integer com.lby.dao.Impl.UserDaoImpl.select(Integer))")
public void selectAspect(){}
//前置通知
@Before(value = "selectAspect()")
public void selectAspectBefore(){
System.out.println("前置执行");
}
//后置返回通知
@AfterReturning(value = "selectAspect()",returning = "x")
public void selectAspectAfterReturning(Integer x){
System.out.println("返回结果后执行 且能接收结果哦:"+x);
}
//后置通知
@After(value = "selectAspect()")
public void selectAspectAfter(){
System.out.println("后置执行");
}
//报错后通知
@AfterThrowing(value = "selectAspect()")
public void selectAspectAfterThrowing(){
System.out.println("报错执行");
}
}
Test测试类
public class Test {
public static void main(String[] args) {
//需要用接口接收不然需要改Spring的配置
UserDao userDaoImpl = (UserDao) new ClassPathXmlApplicationContext("classpath:spring.xml").getBean("userDaoImpl");
System.out.println(userDaoImpl.select(6));
}
}
结果:
报错的结果:在select方法设置1/0 我们看到了报错执行的使用
AOP的使用主要是切面就是切面类,切入点就是给个路径找到哪个方法,在他执行前,后,报错等等时执行的些东西。
1.4 AOP的五大增强与切入点
AOP有五大增强,前面已经看到了四个的使用他们的效果在这边细说一遍,剩下的一个他能顶替另外四个。注:可以说通知也可以说增强
Before 前置增强:在代码运行前执行,报错也会执行。
After 后置增强:在代码运行后执行,报错也会执行。
AfterReturning 后置返回增强:在代码成功返回结果后执行。 注:报错就终止。
AfterThrowing 异常增强:报错时执行。
Around 环绕增强: 在执行前、执行后、报错时、返回结果时都可以操作。
1.4.1 切入点之execution使用
value="execution(访问修饰符 返回值类型 路径.类名.方法名(参数类型) )")
访问修饰符与返回值 | 可以用星(*)缩写 |
包及其子级包 | 可以用俩点(..)缩写 |
类名与方法名 | 可以用星(*)缩写 |
参数类型 | 可以用俩点(..)缩写 |
配合Around环绕增强改编刚刚的切面类
@Component //加入到Spring容器里管理
@Aspect //确认是切面类
public class MyAspectj {
//切入点 执行 访问修饰符 返回类型 方法地址.方法名称(参数类型)
@Pointcut(value ="execution(* *..*.*(..))")
public void selectAspect(){}
//环绕时通知
@Around(value = "selectAspect()")
public Integer selectAspectAround(ProceedingJoinPoint joinPoint){
System.out.println("可以做前置增强");
try {
Integer proceed = (Integer) joinPoint.proceed();
System.out.println("可以做后置返回增强"+proceed);
return proceed;
} catch (Throwable e) {
System.out.println("可以做异常增强");
} finally {
System.out.println("可以做后置增强");
}
return null;
}
}
结果: 因为刚刚的1/0没删
正确的结果:
1.4.2 切入点之@annotation使用
定义注解MyAnnotation类
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) //加不加都一样因为Spring帮我们管理他能扫描到就行,主要是可读性高
public @interface MyAnnotation {
String value() default "";
}
把切面类的切入点(Pointcut)修改一下
//切入点
@Pointcut(value = "@annotation(com.lby.aop.MyAnnotation)")
public void selectAspect(){}
且在select方法加一个@MyAnnotation
@Service
public class UserDaoImpl implements UserDao {
@Override
@MyAnnotation //加上注解
public Integer select(Integer integer) {
//为了与前一次不一样改下值
integer =+ integer*2;
return integer;
}
}
执行效果:
1.5 AOP结构
Aspect切面:定义的切面类,里面存放增强及切入点等。
JoInPoInt连接点:里面有着切点对象的方法与一些参数信息。
Pointcut切点:又称切入点,是能够指定对象的途径,如路径、注解。
Target目标:切入的准确目标如方法等等。
Advice增强:根据切入点(Pointcut)途径中的准确目标(Target)进行选择增强时机及内容。
Proxy代理:根据应用场景采用JDK默认动态代理或CGLIB代理作为AOP的底层。
Weaving织入:就是用生成,动态代理对象(Proxy)并整合切面对象目标方法编织成固定流程的过程。 注:织入是在运行时开始的。
2 Spring事务
2.1 简介
事务是什么?事务是“事件的一系列任务”他们事务要求他们是一致的,成功的话都成功,失败的话都失败,如同转账 我转你100,你加100,我扣100必须是一致的 我转账扣除失败,你就不能加。
2.2 为何要用事务
当我们的CRUD操作牵扯到两个表时,要求他们一加都加,一减都减可我们没多少省事办法来保障,就需要事务来帮助我们要求他的一致性。
2.3 Spring事务的使用
2.3.1 Spring事务配置
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.17</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.17</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>
<!--springAOP面向切面编程依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.17</version>
</dependency>
<!--Spring事务的依赖tx为缩写-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.17</version>
</dependency>
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:aop="http://www.springframework.org/schema/aop"
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/aop https://www.springframework.org/schema/aop/spring-aop.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.lby"/>
<aop:aspectj-autoproxy/>
<!--数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///qy168?serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="2114382"/>
</bean>
<!--spring封装了一个类SqlSessionFactoryBean类,可以把mybatis中的配置-->
<bean id="sqlSessionFactoryBean" 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="sqlSessionFactoryBean"/>
<!--它会为com.lby.dao包下的所有接口生产代理实现类-->
<property name="basePackage" value="com.lby.dao"/>
</bean>
<!--以下内容是关于事务的配置-->
<!--事务切面管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven/>
</beans>
User实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private double balance;
public User(Integer id, double balance) {
this.id = id;
this.balance = balance;
}
}
Dao与Service跟实现类
public interface UserDao {
int update(User user);
}
public interface UserService {
int update(int add_id,int minus_id,double balance);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
public int update(int add_id,int minus_id,double balance) {
int update = userDao.update(new User(add_id, balance));
int update1 = userDao.update(new User(minus_id, -balance));
return update+update1;
}
}
Mapper文件
<?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.lby.dao.UserDao">
<update id="update">
update user set balance=balance+#{balance} where id=#{id}
</update>
</mapper>
项目架构
Test测试类
public class Test {
public static void main(String[] args) {
ApplicationContext app=new ClassPathXmlApplicationContext("classpath:spring.xml");
UserService userServieImpl = (UserService) app.getBean("userServiceImpl");
System.out.println(userServieImpl.update(1, 2, 100));
}
}
运行时:
如果再Service俩修改中加个1/0 可以看到张三的钱加了但李四没减 ! 这时候我们就需要事务让他们保持一直需要在Service方法上加事务注解@Transactional
加了后就不会出现加了没减的现象了,就是事务回退如果一个失败另一个会回退回去。