1.什么是AOP
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
2.AOP的应用场景
1.记录日志 2.权限校验 3.spring事务管理
3.AOP如何使用
创建service层接口
public interface MathService {
int Add(int a, int b);
int Jian(int a, int b);
int Cheng(int a, int b);
int Chu(int a, int b);
}
这里创建了四个方法,分别是加减乘除,并创建它的实现类,实现这些方法。
@Service
public class MathServiceImpl implements MathService{
@Override
public int Add(int a, int b) {
return a+b;
}
@Override
public int Jian(int a, int b) {
return a-b;
}
@Override
public int Cheng(int a, int b) {
return a*b;
}
@Override
public int Chu(int a, int b) {
return a/b;
}
}
需要注意的是,一定要在实现类上方添加@Serivice注解,否则该类将无法提交给spring容器进行管理。
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
引入spring依赖以及spring-aop的依赖
<?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/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--扫描包-->
<context:component-scan base-package="com.yzx.service"/>
<!--开启切面注解驱动-->
<aop:aspectj-autoproxy/>
</beans>
创建spring.xml配置文件,开启包扫描以及开启切面注解驱动,如果不开启切面注解,则切面将无法使用。
@Aspect
@Component
public class MyAspect {
}
创建自定义的切面类,并添加切面注解,以达到实现切面编程,添加Component注解使该类由spring容器管理。
切面有五种通知方式以及一个切入点
@Before 前置通知. 被代理的方法执行前--执行
@After: 后置通知: 被代理的方法执行完后--执行
@AfterReturning: 后置返回通知: 被代理的方法碰到return.--才会执行
@AfterThrowing: 后置异常通知: 当被代理的方法出现异常时--才会执行。
@Around: 环绕通知。
@Pointcut:切入点,定义哪个方法为切入点
@Aspect
@Component
public class MyAspect {
@Pointcut(value = "execution(* com.yzx.service.MathServiceImpl.*(..))")
public void pointCut() {}
@After(value = "pointCut()")
public void after(){
System.out.println("==After==");
}
}
@Pointcut注解中的execution表示路径路径表达式,第一个*表示的是方法的返回类型,比如public String,后面则是报名,第二个*表示的是这个类中的所有方法,括号中的双点表示匹配任何参数的方法,可有可无的参数
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml");
MathService bean = context.getBean(MathService.class);
System.out.println(bean.Add(100, 200));
}
}
创建测试类,运行查看。
可以看到,在方法执行之后,切面类执行了After方法,然后输出了返回值,然后修改切面类
@Aspect
@Component
public class MyAspect {
@Pointcut(value = "execution(* com.yzx.service.MathServiceImpl.*(..))")
public void pointCut() {}
@After(value = "pointCut()")
public void after(){
System.out.println("==After==");
}
@Before(value = "pointCut()")
public void before(){
System.out.println("==Before==");
}
@AfterReturning(value = "pointCut()")
public void afterReturning() {
System.out.println("==AfterReturning==");
}
@AfterThrowing(value = "pointCut()")
public void afterThrowing() {
System.out.println("==AfterThrowing==");
}
}
添加执行之前通知,有返回值执行之前通知,有异常通知,运行查看。
可以看到,前置通知在后置通知之前执行力,而返回值通知也在后置通知之前执行了,因为有返回值通知执行要高于后置通知。在实现类中添加异常,查看异常通知
@Service
public class MathServiceImpl implements MathService{
@Override
public int Add(int a, int b) {
int h = 10/0;
return a+b;
}
@Override
public int Jian(int a, int b) {
return a-b;
}
@Override
public int Cheng(int a, int b) {
return a*b;
}
@Override
public int Chu(int a, int b) {
return a/b;
}
}
可以看到结果,不仅抛出了除数为0的异常,还执行了异常通知方法,但是没有执行返回值通知,因为出现了异常,程序就不执行了,所以也不会执行return,自然也不会执行返回值通知
4.环绕通知
通过@Around方法来实现环绕通知,环绕通知包含了上方的五个通知,修改自定义切面类
@Aspect
@Component
public class MyAspect {
@Around(value = "execution(* com.yzx.service.*.*(..))")
public Object around(ProceedingJoinPoint joinPoint){
System.out.println("==执行之前==");
try {
System.out.println("==执行之后==");
return joinPoint.proceed();
} catch (Throwable e) {
System.out.println("==异常出现==");
throw new RuntimeException(e);
}finally {
System.out.println("==总是执行==");
}
}
}
环绕通知必须有返回值,并且需要传入ProceedingJoinPoint作为形参获取返回值对象,在try语句外的是@Before通知,try语句内是@After通知,return则是@AfterReturning通知,catch语句内是@AfterThrowing通知,finally语句内是不管有没有异常,都会执行的代码块。
通过joinPoint.proceed()方法可以获取到切入的方法的返回值。运行查看。
左边是没有添加异常代码的结果,右边是添加了异常代码的执行结果,可以看到左边的没有异常通知并且输出了返回值,而右边的则执行了异常通知并且没有输出返回值。
ProceedingJoinPoint表示连接点,即切入点切入之后代理的方法的方法体,调用proceed方法继续执行切入的方法,否则将无法继续执行方法。
5.事务
事务就是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用。
举例:例如a向b转账,会执行加钱和扣钱的操作,这两个操作需要的就是事务的一致性,要么都执行,要么都不执行。但JDBC默认的是事务自动提交,即一次一次执行事务,如果a向b转账之后,在b还没收到转账的中间出现了异常,那么b加钱的操作就不会在继续执行了,但是a已经执行了扣钱的操作,b并没有执行加钱的操作。不仅如此,还有权限分配的操作,权限分配一般由先删除角色的所有权限,然后添加传过来的权限,如果在删除过角色的权限后出现异常,程序也是不会继续执行了,那么添加权限操作将也不会执行,所以角色会丢失所有的权限。解决方法也很简单就是实现事务的一致性即可。
创建了一个测试用例的数据库,随便添加几条数据,money表示用户的余额
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
引入mysql依赖。现在由小王向小李转账500元,来进行事务统一
public class Test {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = null;
try {
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test0816?serverTimezone=Asia/Shanghai", "root", "root");
conn.setAutoCommit(false); //设置事务自动提交关闭
conn.prepareStatement("update user set money=money-500 where uid=1").executeUpdate();
conn.prepareStatement("update user set money=money+500 where uid=3").executeUpdate();
conn.commit();//提交事务
} catch (SQLException e) {
if (conn != null) {
conn.rollback();//如果出现异常,事务回滚
}
throw new RuntimeException(e);
}
}
}
链接jdbc,并使用try,catch环绕代码。手动关闭jdbc的事务自动提交,添加两条sql语句,一个加钱一个扣钱并执行,然后手动提交事务,在catch异常中,添加事务回滚,如果代码出现了异常,则是事务回滚。查看执行结果
可以看到小王减少了500,小李增加了500,这是没有异常的情况下,如果在两条sql中添加了异常,会出现什么情况
在两行中插入异常,然后运行查看数据库。
可以看到,小王虽然在异常前已经执行了方法,但金额并没有减少,因为出现异常之后事务进行了回滚,所以回滚了刚执行的方法。如果没有使用事务,那么结果将会是小王减少了500,出现了异常,然后小李的金额不为改变。
6.Spring实现事务
spring框架一定会提供一个事务切面类。前置通知---开启手动事务、后置返回通知[事务提交] 、异常通知[事务回滚]
<!--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>
引入依赖,并修改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: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/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--扫描包-->
<context:component-scan base-package="com.yzx"/>
<!--开启切面注解驱动-->
<aop:aspectj-autoproxy/>
<!--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/test0816?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.yzx.dao包下的所有接口生产代理实现类-->
<property name="basePackage" value="com.yzx.Tran.dao"/>
</bean>
<!--事务切面管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启事务切面管理注解的驱动-->
<tx:annotation-driven/>
</beans>
public interface UserDao {
//创建一个修改金额的方法
int UpdateMoneyByUid(@Param("uid")Integer uid,@Param("money") double money);
}
创建Dao层的方法
<update id="UpdateMoneyByUid">
update user set money = money + #{money} where uid = #{uid}
</update>
Mapper的方法语句。
public interface UserService {
//创建转账的方法
void ZhuanZhang(int fid,int sid,double money);
}
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserDao userDao;
@Transactional
@Override
public void ZhuanZhang(int fid, int sid, double money) {
//fid表示付款人,sid表示收款人,减少金额添加负号
userDao.UpdateMoneyByUid(fid,-money);
userDao.UpdateMoneyByUid(sid,money);
}
}
Service层和实现类,实现类中@Transactional注解默认情况下,spring是不识别的,因为在配置文件中开启了事务管理注解驱动,所以现在spring可以识别该注解,在方法体内,调用dao层的修改金额方法,付款人减少金额,收款人添加金额。运行查看
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml");
UserService bean = context.getBean(UserService.class);
bean.ZhuanZhang(1,3,500);
}
}
在实现类中俩行调用dao层方法中添加异常并运行查看结果
可以看到,出现了异常,所有的事务都进行了回滚,并没有进行提交。