springAOP
AOP介绍
AOP:面向切面编程,面向方面编程。AOP是OOP的补充延伸,AOP底层使用的是动态代理来实现的。
Spring的AOP使用的动态代理是:JDK动态代理和CGLIB动态代理。Spring在这两种动态代理中灵活切换,如果是代理接口,会默认使用JDK动态代理,如果代理类,该类没有实现接口,就会使用CGLIB动态代理。也可以使用配置让spring只是用CGLIB。
为什么要是用AOP技术呢?
在系统中有一些系统服务,例如:日志、事务管理、安全等。这些系统服务被称为交叉业务,这些交叉业务几乎是通用 的,不管是做银行转账,还是删除用户信息等,这些都是需要做的。在每一个业务处理过程中,都掺杂这些交叉业务的话,存在两方面问题:
- 一、交叉业务在多个业务流程中反复出现,显然这个交叉业务代码没有得到复用。修改这些交叉业务代码的话,需要修改多处;
- 二、程序员无法关注核心业务代码的编写,在编写这些业务代码的同事还需要处理这些交叉业务;
下图就是AOP的思想。
(图片来源-动力节点老杜spring讲义)
用一句话总结就是:将与核心业务无关的代码独立的抽取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程称为AOP;
AOP的优点是:代码复用性强、代码易维护、使开发者更关注业务逻辑。
AOP的概念<七大术语>
AOP的七大术语分别为:连接点 joinPoint、切点Pointcut、通知Advice、切面Aspect、织入Weaving、代理对象Proxy、目标对象Target
- 连接点Joinpoint: 在程序执行过程中,可以织入切面的位置。方法的执行前后,异常抛出的之后等位置
- 切点Pointcut: 在程序执行流程中,真正织入切面的方法。(一个切面对应多个连接点)
- 通知Advice: 通知又叫增强,就是具体织入的代码,包括前置通知、后置通知、环绕通知、异常通知、最终通知;
- 切面Aspect: 切点加上通知就是切面;
- 织入Weaving: 把通知应用到对象上的过程;
- 代理对象Proxy:一个目标对象被织入后产生的新对象;
- 目标独享Target: 被织入通知的对象。
下面这张图介绍了切面、通知、切点,连接点的关系;
(图片来源,动力节点老杜)
切点表达式
execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])
访问控制权限修饰符:
- 可选项。
- 没写,就是4个权限都包括。
- 写public就表示只包括公开的方法。
返回值类型:
- 必填项。
- * 表示返回值类型任意。
全限定类名:
- 可选项。
- 两个点“…”代表当前包以及子包下的所有类。
- 省略时表示所有的类。
方法名:
- 必填项。
- *表示所有方法。
- set*表示所有的set方法。
形式参数列表:
-
必填项
-
() 表示没有参数的方法
-
(…) 参数类型和个数随意的方法
-
(*) 只有一个参数的方法
-
(*, String) 第一个参数类型随意,第二个参数是String的。
异常:
- 可选项。
- 省略时表示任意异常类型。
总结如下图
spring中的AOP
Spring对AOP的实现包括以下三种方式:
-
第一种:Spring框架结合Aspectj框架实现的AOP,基于注解方式;
-
第二种:spring框架结合Aspectj框架实现的AOP,基于XML方式;
-
第三种:Spring 框架自己实现的AOP,基于XML配置方式。
基于XML实现方式实现AOP
话不多说直接,上代码;
pom.xml(注意:以下的版本号为5.3.23,(通知顺序,5.3以上的版本进行了修改,5.3以下的版本通知顺序和这版和这版以后的不同))
<!--spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.23</version>
</dependency>
<!--spring aop依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.23</version>
</dependency>
<!--spring aspects依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.23</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:context="http://www.springframework.org/schema/context"
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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userServiceImpl" class="com.xidu.aop.service.UserServiceImpl"/>
<context:component-scan base-package="com.xidu.aop"/>
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
Main.java
import com.xidu.aop.service.UserService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) applicationContext.getBean("userServiceImpl");
// userService.deleteUser();
userService.insertUser();
// userService.editUser();
// userService.selectUser();
}
}
UserService.java
package com.xidu.aop.service;
public interface UserService {
void insertUser();
void deleteUser();
void editUser();
String selectUser();
}
UserServiceImpl.java
package com.xidu.aop.service;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService{
@Override
public void insertUser() {
//使用sleep()方法模拟网络延迟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("新增用户成功...");
}
@Override
public void deleteUser() {
try {
Thread.sleep(777);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("删除用户成功...");
}
@Override
public void editUser() {
try {
Thread.sleep(1112);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("修改用户成功...");
}
@Override
public String selectUser() {
try {
Thread.sleep(459);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("查询用户成功...");
return "张三";
}
}
TimeHander.java
package com.xidu.aop.interupter;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class TimerHandler {
int i = 0;
@Pointcut("execution(* com.xidu.aop..*(..))")
public void cut(){
}
@Before("cut()")
public void runBefore(){
System.out.println("第"+i+"个执行,"+"Before方法执行了");
i++;
}
@AfterReturning("cut()")
public void runAfterReturning(){
System.out.println("第"+i+"个执行,"+"AfterReturning方法执行了");
i++;
}
@After("cut()")
public void runAfter(){
System.out.println("第"+i+"个执行,"+"After方法执行了");
i++;
}
@Around("cut()")
public void runAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("第"+i+"个执行,"+"Around方法开始执行");
i++;
long start = System.currentTimeMillis();
joinPoint.proceed();
long end = System.currentTimeMillis();
System.out.println("运行程序使用"+(end-start)+"毫秒");
System.out.println("第"+i+"个执行,"+"Around方法结束执行");
i++;
}
}
输出结果:分析之后就可知道通知的执行顺序;
第0个执行,Around方法开始执行
第1个执行,Before方法执行了
新增用户成功...
第2个执行,AfterReturning方法执行了
第3个执行,After方法执行了
运行程序使用1017毫秒
第4个执行,Around方法结束执行
以上输出结果是5.3.23的执行顺序,如果是5.2以下的版本呢;
修改pom.xml文件
<!-- spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--spring aop依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--spring aspects依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
运行结果如下:会发现顺序修改了(按道理讲,这个顺序就是BUG呀)
第0个执行,Around方法开始执行
第1个执行,Before方法执行了
新增用户成功...
运行程序使用1022毫秒
第2个执行,Around方法结束执行
第3个执行,After方法执行了
第4个执行,AfterReturning方法执行了
基于注解实现AOP
修改Main.java文件
import com.xidu.aop.config.Config;
import com.xidu.aop.service.StudentService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
StudentService studentService = (StudentService) applicationContext.getBean("studentServiceImpl");
studentService.getStudentName();
}
}
修改spring.xml文件
package com.xidu.aop2.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan({"com.xidu.aop2"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class Config {
}
总结:
最后说一些自己对AOP的想法吧,设置切面时,需要设置切面类、切面方法。spring在创建bean时,首先创建了用户指定的bean对象,然后根据用户设置切面,对之前的bean对象进行了再次修饰。就是说底层创建了张三、李四、王五三个类,现在需要让三个对象飞起来,spring就给三个对象统一添加了翅膀属性,用户在使用张三、李四、王五时,使用的是,已经带翅膀的张三、李四和王五。