Spring AOP
AOP 的全称是“Aspect Oriented Programming” 面向切面编程,是一种编程模式,将业务逻辑的各个部分进行隔离,使开发业务逻辑时可 减少代码重复率、提高业务开发效率
# AOP代理
代理:自己不做,找人帮你做
代理模式:在一个原有功能的基础上添加新的功能
分类:静态代理和动态代理
# 静态代理
将 服务性代码 分离出来。通过接口进行静态代理
接口
public interface IService {
void add();
}
业务类
public class UserService implements IService {
@Override
public void add() {
System.out.println("UserService---user---");
}
}
代理类
public class Proxy_affair implements IService {
private IService iService;
public Proxy_affair(IService iService) {
this.iService = iService;
}
@Override
public void add() {
try {
System.out.println("事务开启");
//核心业务
iService.add();
System.out.println("事务结束");
} catch (Exception e) {
System.out.println("事务回滚");
}
}
}
测试类
@Test
public void test() {
UserService userService = new UserService();
//一级代理(事务)
Proxy_affair proxy_affair = new Proxy_affair(userService);
//二级代理(日志)
//添加多个业务进行测试
//Proxy_log proxy_log = new Proxy_log(proxy_affair);
//proxy_log.add();
proxy_affair.add();
}
/*运行结果
日志开始
事务开启
UserService---add---
事务结束
日志结束
*/
总结
- 代理在不修改 业务功能 的前提下,对 业务功能 进一步进行拓展
- 如果有 多个代理类 会影响 接口方法增加 和 维护难度
# 动态代理
程序运行的时候,根据要被代理的对象动态生成代理类
动态代理有类型两种类型分为 基于JDK的动态代理 和 基于CGLIB的动态代理
# 基于JDK的动态代理
JDK代理的前提 目标对象 必须实现接口,否则无法实现JDK动态代理
Proxy类
Class Proxy
java.lang.Object java.lang.reflect.Proxy
Proxy
提供创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类
主要方法
Proxy.newProxyInstance
public static Object newProxyInstance(ClassLoader loader,
类<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
ClassLoader loader: 类加载器,因为动态代理类,借助别人的类加载器。一般使用被代理对象的类加载器
Class[] interfaces: 接口类对象的集合,针对接口的代理,针对哪个接口做代理
InvocationHandler h: 句柄,回调函数,编写代理的规则代码
InvocationHandler.invoke (接口方法)
Object invoke(Object proxy,
方法 method,
Object[] args)
throws Throwable
Object proxy: 代理对象
Method method: 被代理的方法
Object[] args: 被代理方法的 参数数组
原始实例 (main方法实现)
业务类
public class UserService implements IService {
@Override
public void add() {
System.out.println("UserService---user---");
}
}
main方法执行
public static void main(String[] args) {
//目标对象
UserService userService = new UserService();
//返回代理对象
IService proxyService = (IService) Proxy.newProxyInstance(
//目标对象类 加载器
userService.getClass().getClassLoader() ,
//实现的接口集合
userService.getClass().getInterfaces() ,
//动态代理 规则编辑
new InvocationHandler() {
/**
* @param proxy 代理对象
* @param method 被代理的方法
* @param args 被代理方法的 参数数组
* @return 被代理方法的返回
*/
@Override
public Object invoke(Object proxy , Method method , Object[] args) throws Throwable {
Object invoke = null;
try {
System.out.println("开始事务");
//核心业务
invoke = method.invoke(userService,args);
System.out.println("提交事务");
} catch (Exception e) {
System.out.println("事务回滚");
e.printStackTrace();
throw e;
}finally {
System.out.println("finally---");
}
System.out.println("invoke : " + invoke);
return invoke;
}
}
);
//执行代理业务
proxyService.add();
}
/*运行结果
开始事务
UserService---add---
提交事务
finally---
invoke : null
*/
工具化设计
业务类
public class UserService implements IService {
@Override
public void add() {
System.out.println("UserService---user---");
}
}
Aop接口
/**
* 切面:服务代码,切入核心代码
*/
public interface Aop {
//4个阶段
void before();
void after();
void exception();
void myFinally();
}
切面类 (事务)
public class TranLAop implements Aop{
@Override
public void before() {
System.out.println("事务---before");
}
@Override
public void after() {
System.out.println("事务---after");
}
@Override
public void exception() {
System.out.println("事务---exception");
}
@Override
public void myFinally() {
System.out.println("事务---myFinally");
}
}
工具类
public class ProxyFactory {
private IService iService;
private Aop aop;
public ProxyFactory(IService iService , Aop aop) {
this.iService = iService;
this.aop = aop;
}
public IService getProxyInstance(){
return (IService) Proxy.newProxyInstance(
//目标对象类 加载器
iService.getClass().getClassLoader() ,
//实现的接口集合
iService.getClass().getInterfaces() ,
//动态代理 规则编辑
new InvocationHandler() {
/**
* @param proxy 代理对象
* @param method 被代理的方法
* @param args 被代理方法的参数
* @return 被代理方法的返回
*/
@Override
public Object invoke(Object proxy , Method method , Object[] args) throws Throwable {
Object invoke = null;
try {
aop.before();
//核心业务
invoke = method.invoke(iService,args);
aop.after();
} catch (Exception e) {
aop.exception();
e.printStackTrace();
throw e;
}finally {
aop.myFinally();
}
return invoke;
}
}
);
}
}
测试类
//工具化 实现动态代理
@Test
public void test() {
UserService userService = new UserService();
TranLAop tranLAop = new TranLAop();
IService proxyFactory = new ProxyFactory(userService,tranLAop).getProxyInstance();
proxyFactory.add();
}
/*运行结果
事务---before
UserService---add---
事务---after
事务---myFinally
*/
# 基于CGLIB的动态代理
Cglib代理,也称子类代理。在内存中构建一个子类对象从而实现对目标对象功能的扩展(目标对象无需应用接口也可实现目标对象的代理)
GLIB 被许多 AOP 框架所使用,其底层是通过使用一个小而快的字节码处理框架 ASM(Java 字节码操控框架)转换字节码并生成新的类
应用前提需要引入Cglib动态代理jar
<!--CGLIB的动态代理-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.2</version>
</dependency>
业务类 (学生类)
public class StudentService {
public void add(int id , String name) {
System.out.println("StudentService---add---");
System.out.println("引入数据: " + id +"--"+name);
}
}
main方法执行
public static void main(String[] args) {
//目标对象(无接口)
StudentService service = new StudentService();
//cglib动态代理 (因 无接口,使用类型是与目标对象一直)
StudentService proxyService = (StudentService) Enhancer.create(
//目标对象类
service.getClass() ,
//回调对象代理规则
new MethodInterceptor() {
/**
*
* @param o
* @param method 针对方法
* @param objects
* @param methodProxy
* @return 调取方法的返回
*/
@Override
public Object intercept(Object o , Method method , Object[] objects , MethodProxy methodProxy)
throws Throwable {
try {
System.out.println("开始事务");
//核心业务
Object invoke = methodProxy.invokeSuper(o , objects);
System.out.println("提交事务");
return null;
} catch (Throwable throwable) {
System.out.println("事务回滚");
throw throwable;
} finally {
System.out.println("finally-----");
}
}
}
);
proxyService.add(001 , "张三");
}
/*运行结果
开始事务
StudentService---add---
引入数据: 1--张三
提交事务
finally-----
*/
工具化设计
业务类
public class StudentService {
public void add(int id , String name) {
System.out.println("StudentService---add---");
System.out.println("引入数据: " + id +"--"+name);
}
}
Aop接口
/**
* 切面:服务代码,切入核心代码
*/
public interface Aop {
//4个阶段
void before();
void after();
void exception();
void myFinally();
}
切面类 (日志)
public class LogAop implements Aop{
@Override
public void before() {
System.out.println("日志---before");
}
@Override
public void after() {
System.out.println("日志---after");
}
@Override
public void exception() {
System.out.println("日志---exception");
}
@Override
public void myFinally() {
System.out.println("日志---myFinally");
}
}
工具类
public class CglibProxyFactory {
private StudentService service;
private Aop aop;
public CglibProxyFactory(StudentService service , Aop aop) {
this.service = service;
this.aop = aop;
}
public Object getProxyInstance() {
return Enhancer.create(
//目标对象类
service.getClass() ,
//回调对象代理规则
new MethodInterceptor() {
/**
*
* @param o
* @param method 针对方法
* @param objects
* @param methodProxy
* @return 调取方法的返回
*/
@Override
public Object intercept(Object o , Method method , Object[] objects , MethodProxy methodProxy)
throws Throwable {
try {
aop.before();
//核心业务
Object invoke = methodProxy.invokeSuper(o , objects);
aop.after();
return null;
} catch (Throwable throwable) {
aop.exception();
throw throwable;
} finally {
aop.myFinally();
}
}
}
);
}
}
测试类
@Test
public void test() {
//目标对象(无接口)
StudentService service = new StudentService();
//cglib动态代理 (因 无接口,使用类型是与目标对象一直)
Aop aop = new LogAop();
StudentService proxyInstance = (StudentService) new CglibProxyFactory(service , aop).getProxyInstance();
proxyInstance.add(001,"张三");
}
/*运行结果
日志---before
StudentService---add---
引入数据: 1--张三
日志---after
日志---myFinally
*/
# AOP通知
Spring的AOP实现底层就是对上面动态代理的代码进行封装,封装后我们子需要对关注部分进行代码编写,并通过配置的方式完成指定目标的方法增强
# 通知种类
Spring 通知 指定目标类方法 的连接点位置,分为以下5种通知类型
- 前置通知 在方法执行前的通知,可以应用于权限管理等功能
- 后置通知 在方法执行后的通知,可以应用于关闭流、上传文件、删除临时文件等功能
- 环绕通知 在方法执行 前、后 都通知,可以应用于日志、事务管理等功能
- 异常通知 在方法抛出异常时的通知,可以应用于处理异常记录日志等功能
- 最终通知 方法执行完毕后最后的通知
# AOP术语
- Target(目标对象) 要被增强的对象,一般业务逻辑类对象
- Proxy(代理) 一个类被AOP 织入增强后,产生一个结果代理类
- Aspect(切面) 切面点 增强功能 , 就是一些代码完成某些功能 , 非业务功能。是切入点和通知的结合
- Joinpoint(连接点) 程序执行过程中 明确的点。在Spring中,这些点指定与 核心业务的方法(Spring只支持方法类型的连接点)
- Pointcut(切入点) 切入点 指 声明 一个/多个 连接点的集合。通过切入点指定一组方法(非final方法)
- Advice(通知/增强) 通知 指 拦截到 连接点 后要做的 通知。通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同
- Weaving(织入) 织入 指 增强应用到目标对象来创建新的代理对象的过程。 spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入
# AspectJ 实现AOP
AspectJ 基于 Java语言的AOP框架,它扩展了 Java语言,使得AOP功能使用更便捷
# 切入点表达式
AspectJ 定义 专门的表达式 指定 切入点 . 通用型 execution表达式原型:
execution([modifiers-pattern] ret-type-pattern declaring-type-pattern name-pattern(param-pattern) [throws-pattern])
//必要参数:方法返回值 方法声明(参数)
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
参数说明 (中括号表示可选类型)
- modifiers-pattern :访问权限类型
- ret-type-pattern :返回类型
- declaring-type-pattern :包名类名
- name-pattern(param-pattern) :方法名(参数的 类型 和 个数)
- throws-pattern :抛出异常类型
PS : 表达式各个部分可以用空格隔开,可用以下符号
符号 | 范围 | 说明 |
---|---|---|
* | 所有 | 0 ~ n 个任意字符 |
.. | 方法参数、包路径 | 指定任意个参数;当前包的子路径 |
+ | 类名、接口 | 当前类路径的子类;当前接口及实现的类 |
切入点表达式实例
execution(* com.service.*.*(..))
定义 com.service 包路径里的 任意类、任意方法、方法任意返回类型
execution(* com.service..*.*(..))
定义 com.service 包路径里的 任意子包、任意类、任意方法、方法任意返回类型
execution(* com.service.IUserService+.*(..))
当路径 com.service.IUserService 的文件类型为以下条件:
若为接口 , 则为接口中 任意方法及其所有实现类中的任意方法
若为类 , 则为该类及其子类中的任意方法
# XML实现AOP
基于 xml
的配置文件方式进行定义 切面、切入点 等
依赖、插件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sans</groupId>
<artifactId>Spring-AOP</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>15</maven.compiler.source>
<maven.compiler.target>15</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- spring依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
<!-- CGLIB的动态代理 -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.2</version>
</dependency>
<!--aop切面依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--编译插件-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
业务接口
public interface IService {
void add(int id , String name);
boolean update(int a , int b);
}
业务类
import org.springframework.stereotype.Service;
@Service
public class TeamService implements IService{
@Override
public void add(int id , String name) {
//制造异常
//int num = id/0;
System.out.println("MyAspect---add---");
}
@Override
public boolean update(int a , int b) {
System.out.println("MyAspect---update---");
if (a + b > 10) {
return true;
}
return false;
}
}
切面类
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* 切面类 XML实现
* 简洁代理通知
*/
@Component //创建权限交给 spring容器 进行创建
@Aspect //aspectj 框架注解 标识该类为切面类
public class MyAspect_XML {
public void before(JoinPoint jp){
System.out.println("前置通知");
}
public void afterReturn(Object result){
System.out.println("后置通知");
}
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("======================AroundOpen");
Object obj = pjp.proceed();
System.out.println("======================AroundEnd");
return obj;
}
public void exception(JoinPoint jp, Throwable ex){
System.out.println("异常通知");
}
public void myFinally(JoinPoint jp){
System.out.println("最终通知");
}
}
配置文件 (扫描包,引入代理)
<?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
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.newAOP" />
<!-- xml实现 代理-->
<aop:config>
<aop:pointcut id="pt_ALL" expression="execution(* com.newAOP.service..*.*(..))"/>
<aop:aspect ref="myAspect_XML">
<aop:before method="before" pointcut-ref="pt_ALL"/>
<aop:after-returning method="afterReturn" pointcut-ref="pt_ALL" returning="result"/>
<aop:after-throwing method="exception" pointcut-ref="pt_ALL" throwing="ex"/>
<aop:after method="myFinally" pointcut-ref="pt_ALL"/>
<!-- 容易出现问题-->
<!-- <aop:around method="around" pointcut-ref="pt_ALL"/>-->
</aop:aspect>
</aop:config>
</beans>
测试
@Test
public void test(){
ApplicationContext ac = new ClassPathXmlApplicationContext("MySpring.xml");
IService teamService = (IService) ac.getBean("teamService");
teamService.add(001,"张三");
System.out.println("\n");
teamService.update(6, 5);
}
/*运行结果
前置通知
MyAspect---add---
后置通知
最终通知
前置通知
MyAspect---update---
后置通知
最终通知
*/
# 注解实现AOP
对切面类使用指定 注解 定义 切面、切入点 等
注解类型
注解名称 | 说明 |
---|---|
@Aspect | 定义 切面(指定 类) |
@Pointcut | 定义 切入点表达式(指定 类) |
@Before | 定义 前置通知(指定 方法) |
@AfterReturning | 定义 后置通知(指定 方法) |
@Around | 定义 环绕通知(指定 方法) |
@AfterThrowing | 定义 异常通知(指定 方法) |
@After | 定义 最终通知(指定 方法,无论异常都会通知) |
依赖配置 、业务类 和 业务接口 与上述一致
切面类
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 切面类
* @author Sans
*/
@Component //创建权限交给 spring容器 进行创建
@Aspect //aspectj 框架注解 标识该类为切面类
public class MyAspect {
/**
* 切点表达式为:
* 方法 任意返回类型
* 指定路径 com.newAOP包
* 指定包下的所有 子包、子类、类、接口、方法、方法参数
*/
@Pointcut("execution(* com.newAOP..*.*(..))")
private void pointCut_all(){ }
/**
* 前置通知
* @param jp
*/
@Before("pointCut_all()")
public void before(JoinPoint jp){
System.out.println("======================BeforeOpen");
System.out.println("前置通知");
System.out.println("拦截信息:");
System.out.println("\t方法名称:"+jp.getSignature().getName());
Object[] args = jp.getArgs();
if (args.length != 0){
System.out.println("\t参数格式:"+args.length);
System.out.println("\t参数列表:");
for (Object arg : args) {
System.out.println("\t\t"+arg);
}
}
System.out.println("======================BeforeEnd");
}
/**
* 后置通知
* @param result
*/
@AfterReturning (value="pointCut_all()",returning = "result")
public void afterReturn(Object result){
System.out.println("======================AfterReturnOpen");
System.out.println("后置通知");
System.out.println("拦截信息:");
System.out.println("\t方法返回值:"+result);
System.out.println("======================AfterReturnEnd");
}
/**
* 环绕通知
* @param pjp
* @return 方法返回类型
* @throws Throwable 当前方法运行抛出的异常
*/
@Around("pointCut_all()")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("======================AroundOpen");
System.out.println("环绕通知");
Object obj = pjp.proceed();
System.out.println("方法 返回值为:"+obj);
System.out.println("======================AroundEnd");
return obj;
}
/**
* 异常通知
* @param jp 连接点状态
* @param ex 异常返回
*/
@AfterThrowing(value = "pointCut_all()",throwing = "ex")
public void exception(JoinPoint jp,Throwable ex){
System.out.println("======================ExceptionOpen");
System.out.println("异常通知");
//返回连接点的签名(异常)
System.out.println("异常原因:"+jp.getSignature());
//返回 throwable 的详细消息字符串
System.out.println("异常类型:"+ex.getMessage());
System.out.println("======================ExceptionEnd");
}
/**
* 最终通知(无论出现异常都会执行的通知)
*/
@After("pointCut_all()")
public void myFinally(JoinPoint jp){
System.out.println("======================FinallyOpen");
System.out.println("最终通知");
System.out.println(jp.getSignature().getName()+" 方法结束!");
System.out.println("======================FinallyEnd");
}
}
配置文件 (扫描包,切面类注解应用)
<?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
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
//扫描包。个人配置包路径 为 com.newAOP
<context:component-scan base-package="com.newAOP" />
//允许 Spring容器 权限应用
<aop:aspectj-autoproxy expose-proxy="true"/>
</beans>
测试
@Test
public void test(){
ApplicationContext ac = new ClassPathXmlApplicationContext("MySpring.xml");
IService teamService = (IService) ac.getBean("teamService");
teamService.add(001,"张三");
System.out.println("\n");
teamService.update(6, 5);
}
/*运行结果
======================AroundOpen
环绕通知
======================BeforeOpen
前置通知
拦截信息:
方法名称:add
参数格式:2
参数列表:
1
张三
======================BeforeEnd
MyAspect---add---
======================AfterReturnOpen
后置通知
拦截信息:
方法返回值:null
======================AfterReturnEnd
======================FinallyOpen
最终通知
add 方法结束!
======================FinallyEnd
方法 返回值为:null
======================AroundEnd
======================AroundOpen
环绕通知
======================BeforeOpen
前置通知
拦截信息:
方法名称:update
参数格式:2
参数列表:
6
5
======================BeforeEnd
MyAspect---update---
======================AfterReturnOpen
后置通知
拦截信息:
方法返回值:true
======================AfterReturnEnd
======================FinallyOpen
最终通知
update 方法结束!
======================FinallyEnd
方法 返回值为:true
======================AroundEnd
*/
# XML与Annotation 声明区别
xml在外部文件 .xml
进行配置;Annotation在类文件中进行配置 无需外部辅助
xml效率一般(需要解析);Annotation效率高
xml需要解析工具进行完成;Annotation无需解析,利用Java反射进行完成
xml易于观察对象关系(业务量较多时);Annotation 不易观察