AOP
1.什么是AOP?
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP:它是面向切面编程的语言,它可以让你的业务代码和非业务代码进行隔离。在不改变业务代码的前提下,可以增加新的非业务代码。
2.为什么使用AOP?
1.分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
2.减少代码的重复,各个模块的重用性加强。
3.降低 模块间的耦合度,提高代码的可操作性和可维护性。
如果我们不适用AOP,那么必然会导致项目中的日志与具体的业务逻辑都写在一起,而且是每一个接口里面都需要日志,那么日志的代码数量远远超过具体业务逻辑代码,这样不利于后期的维护已修改。同时违背了软件设计的思想。如果使用的AOP,我们把这种不是必然需要的同时也是服务具体业务的模块单独分装起来,在业务逻辑前或后直接调用,从而提高代码的可操作性和可维护性。
3.AOP的应用场景
- 记录日志
- 权限校验
- spring事务管理。
4.AOP的结构
AOP要做的三件事在哪里切入,也就是权限校验,等非业务操作在哪些业务 代码中执行;什么时候切入,是业务代码执行前还是执行后;切入后做什 么事,比如做权限校验、日志记录等。
- Aspect: 切面
- PointCut:切点:—方式: 路径表达式 (2)注解形式
- Advice: 处理的时机
5.如何使用AOP
public class MathServiceImpl implements MathService {
public double add(double a, double b) {
double result=a+b;
System.out.println("AAA--->The add method result="+result);
return result;
}
public double mul(double a, double b) {
double result=a-b;
System.out.println("AAA--->The mul method result="+result);
return result;
}
public double cheng(double a, double b) {
double result=a*b;
System.out.println("AAA--->The cheng method result="+result);
return result;
}
public double div(double a, double b) {
double result=a/b;
System.out.println("AAA--->The div method result="+result);
return result;
}
}
发现: 我们在每个操作后,都要记录日志,如果后期日志内容发生改变。需要在每个操作后都进行修改。 不利于代码的维护。
我们来使用AOP来解决。
5.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容器来管理 等价于@Service @Controller
@Component
public class MyAspect {
//定义为切点
@Pointcut(value = "execution(public double com.aaa.aop.MathServiceImpl.add(double, double))")
private void mypointcut(){}
@After(value = "mypointcut()")
public void a(){
System.out.println("AAA---->The method result");
}
}
(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:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="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
http://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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--包扫描-->
<context:component-scan base-package="com.aaa.aop"/>
<!--开启切面注解的驱动:导致spring无法其他@Aspect @Before @Pointcut-->
<aop:aspectj-autoproxy/>
</beans>
(4)测试
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.cheng(20, 10));
}
}
使用通配符来统配类路径
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
//该类对象的创建交于spring容器来管理 等价于@Service @Controller
@Component
public class MyAspect {
//通配符:
/**
* 第一个* : 表示任意修饰符 任意返回类型。
* 第二个* : 表示该包下所有的类。
* 第三个* : 类下所有的方法
* ..: 表示任意参数
*
* 建议包就别使用统配符
*/
//定义为切点
@Pointcut(value = "execution(* com.aaa.aop.*.*(..))")
private void mypointcut(){}
@After(value = "mypointcut()")
public void a(){
System.out.println("AAA---->The method result");
}
}
5.2 注解模式
(1)自定义注解
package com.aaa.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = {ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "";
}
(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
//该类对象的创建交于spring容器来管理 等价于@Service @Controller
@Component
public class MyAspect {
//通配符:
/**
* 第一个* : 表示任意修饰符 任意返回类型。
* 第二个* : 表示该包下所有的类。
* 第三个* : 类下所有的方法
* ..: 表示任意参数
*
* 建议包就别使用统配符
*/
//定义为切点
@Pointcut(value = "@annotation(com.aaa.aop.MyAnnotation)")
private void mypointcut(){}
@After(value = "mypointcut()")
public void a(){
System.out.println("AAA---->The method result");
}
}
5.3 aop切面通知的类型
通知 | 说明 |
---|---|
before(前置通知) | 通知方法在目标方法调用之前执行 |
after (后置通知) | 通知方法在目标方法返回或异常后调用 |
after-returning (返回后通知) | 通after-returning (返回后通知) |
after-throwing (抛出异常通知) | 通知方法会在目标方法抛出异常后调用 |
around (环绕通知) | 通知方法会将目标方法封装起来 |
//表示该类为切面类
@Aspect
//表示该类由spring管理
@Component
public class MyAspect {
//定义切点
//路径表达式方式
/**
* 第一个*:表示任意访问修饰符和返回类型
* 第二个*:表示包下的任意类
* 第三个*:表示任意方法
* ..:表示任意参数类型和个数
*/
@Pointcut("execution(* com.aaa.aop.*.*(..))")
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;
}
}
6. spring如何操作事务
6.1 什么是事务
事务的介绍
事务就是用户定义的一系列执行SQL语句的操作, 这些操作要么完全地执行,要么完全地都不执行, 它是一个不可分割的工作执行单元。
事务的使用场景:
在日常生活中,有时我们需要进行银行转账,这个银行转账操作背后就是需要执行多个SQL语句,假如这些SQL执行到一半突然停电了,那么就会导致这个功能只完成了一半,这种情况是不允许出现,要想解决这个问题就需要通过事务来完成。
事务的四大特性:
原子性(Atomicity)
一致性(Consistency)
隔离性(Isolation)
持久性(Durability)
原子性:
一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性
一致性:
数据库总是从一个一致性的状态转换到另一个一致性的状态。(在前面的例子中,一致性确保了,即使在转账过程中系统崩溃,支票账户中也不会损失200美元,因为事务最终没有提交,所以事务中所做的修改也不会保存到数据库中。)
隔离性:
通常来说,一个事务所做的修改操作在提交事务之前,对于其他事务来说是不可见的。(在前面的例子中,当执行完第三条语句、第四条语句还未开始时,此时有另外的一个账户汇总程序开始运行,则其看到支票帐户的余额并没有被减去200美元。)
持久性:
一旦事务提交,则其所做的修改会永久保存到数据库。
举例: 转账
扣钱和加钱----要么都执行要么都不执行。
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/diyige
?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 {
}
}
}
6.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>
(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: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/diyige?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);
//收钱
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);
}
}