AOP简介
我们在做某个业务的时候,可能需要对业务进行拓展,比如说我们做了一个查询业务,
功能上已经可以使用了,但是我们有了新的需求,需要对原有业务的功能进行扩充,比如
我们可能需要判断一下当前是否可以查询此业务(时段考试),我们也可能需要知道当前操作的一些信息,如操作人,操作时间,操作完成情况等来做日志管理。总之,我们需要对类中的方法进行功能扩展。
我们大概有以下几个方法:
- 直接在原方法中加入,这个自然是不合适的,先不说重复代码的问题,但是代码的优雅性就有大问题。
- 对该类做继承并进行方法的重写,来进行功能的扩展。这个方法自然是可行的。(继承的方式)
- 对实现类的接口进行一个新的实现方法去实现功能的扩展。(组合的方式,要求必须有接口)
第二种方式就是我们的cglib代理,第三种则是spring默认的jdk代理。
JDK代理
jdk代理其实是通过反射实现的,具体的大家可以看代码:
//能否利用一个工厂动态为目标对象创建代理
public class JDKProxyFactory {
//要求用户传递目标对象
//关于匿名内部类用法说明: 匿名内部类引用外部参数 要求参数必须final修饰
public static Object getProxy(final Object target){
//1.调用java API实现动态代理
/**
* 参数分析: 3个参数
* 1.ClassLoader loader, 类加载器(获取目标对象的Class)
* 2.类<?>[] interfaces, JDK代理要求 必须有接口
* java中可以多实现
* 3.InvocationHandler h 对目标方法进行扩展
*/
//1.获取类加载器
ClassLoader classLoader = target.getClass().getClassLoader();
//2.获取接口数组
Class[] interfaces = target.getClass().getInterfaces();
//3.通过动态代理创建对象
Object proxy = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
//invoke方法: 代理对象调用方法时invoke执行,扩展方法的编辑位置
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//proxy: 代理对象本身
//method: 用户调用的方法对象
//args: 用户调用方法的参数
// result 标识目标方法执行的返回值
Object result = null;
try {
//添加事务的控制
System.out.println("事务开始");
//执行目标方法
// target真实的目标对象,method方法对象,args方法参数
result = method.invoke(target,args);
System.out.println("事务提交");
}catch (Exception e){
e.printStackTrace();
System.out.println("事务回滚");
}
return result;
}
});
return proxy;
}
}
CGLIB代理
这个代理是通过ASM字节码生成的框架,对当前类继承后重写方法实现的。这个网上的源码剖析很多,我懒得导那两个包了就不写了。
我们知道越底层的东西越快,所以CGLIB代理的创建速度是比JDK代理慢的,但不受实现类有没有接口的影响。
总结
1、CGLib所创建的动态代理对象在实际运行时候的性能要比JDK动态代理高不少,有研究表明,大概要高10倍;
2、但是CGLib在创建对象的时候所花费的时间却比JDK动态代理要多很多,有研究表明,大概有8倍的差距;
3、因此,对于singleton的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,所以比较适合采用CGLib动态代理,反正,则比较适用JDK动态代理。
Spring中AOP的操作
配置:
@Configuration
@ComponentScan("com.study")
@EnableAspectJAutoProxy(proxyTargetClass=false) //启动AOP注解 创建代理对象
//默认启用JDK动态代理,
//目标对象没有实现接口时,则采用CGLIB
//强制使用cglib proxyTargetClass=true
//JDK代理创建速度快.运行时稍慢
//CGLIB创建时速度较慢,运行时更快
public class SpringConfig {
}
aspect:
面向切面编程,我们要有切入点和切入面,连接点,增强,目标对象,织入的了解。
连接点是我们所有可能会进行该方法的类。(为了便于理解,我们通常加一个注解来表明连接点);
切入点是真正的修饰,例如我们的一个类中的方法加入了连接点,表示该方法需要AOP,但是具体进行那种功能扩展要看他的切入点(在什么地方切入)
增强其实就是要扩展的功能。
切面就是相当于,切点加上增强的部分,面向切面编程指的就是找到切点,并编写增强。
织入是将切面与其他类对象连接的过程。
同时我们应该注意增强的功能不同,切入的时机是不同的,
@Before是调用方法前
@After是调用方法后,无论方法是否发生异常都会执行。
@AfterThrowing是抛出异常后
@AfterReturn是方法返回后
@Arround是以上的整个过程
package com.jt.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
//1.AOP需要被Spring容器管理
//@Component
//2.标识该类为AOP切面
// Spring容器默认不能识别切面注解,需要手动配置
@Aspect
public class SpringAOP {
//面向切面编程 = 切入点表达式(IF判断) + 通知方法
/**
* 切入点表达式练习
* within:
* 1.within(com.jt.*.DeptServiceImpl) 一级包下的类
* 2.within(com.jt..*.DeptServiceImpl) ..代表多级包下的类
* 3.within(com.jt..*) 包下的所有的类
*
* execution(返回值类型 包名.类名.方法名(参数列表))
* 1.execution(* com.jt..*.DeptServiceImpl.add*())
* 注释: 返回值类型任意的, com.jt下的所有包中的DeptServiceImpl的类
* 的add开头的方法 ,并且没有参数.
*
* 2.execution(* com.study..*.*(..))
* 注释: 返回值类型任意,com.jt包下的所有包的所有类的所有方法 任意参数.
*
* 3.execution(int com.study..*.*(int))
* 4.execution(Integer com.study..*.*(Integer))
* 强调: 在Spring表达式中没有自动拆装箱功能! 注意参数类型
*
* @annotation(包名.注解名)
* @Before("@annotation(com.study.anno.Cache)")
* 只拦截特定注解的内容.
*/
//1.定义before通知
//@Before("bean(deptServiceImpl)")
//@Before("within(com.study..*)")
//@Before("execution(* com.study..*.DeptServiceImpl.add*())")
//@Before("@annotation(com.study.anno.Cache)")
//1.定义切入点表达式 if判断
@Pointcut("@annotation(com.study.anno.Cache)")
public void pointcut(){
}
/*Spring为了AOP动态获取目标对象及方法中的数据,则通过joinPoint对象
* 进行数据的传递.
* getSignature : 方法签名 获取方法的参数
* */
@Before("pointcut()")
public void before(JoinPoint joinPoint){
System.out.println("获取目标对象的类型:"+joinPoint.getTarget().getClass());
System.out.println("获取目标对象类名:"+joinPoint.getSignature().getDeclaringTypeName());
System.out.println("获取目标对象方法名:"+joinPoint.getSignature().getName());
System.out.println("获取方法参数:"+ Arrays.toString(joinPoint.getArgs()));
System.out.println("我是before通知");
}
/**
* 记录方法的方法返回值!!!
* pointcut: 关联的切入点表达式
* returning: 将方法的返回值,通过形参result进行传递
* @AfterReturning(pointcut = "pointcut()",returning = "result")
* 注意事项:
* 如果参数中需要添加joinPoint 对象时,参数必须位于第一位.
* Spring在进行参数赋值时,采用index[0] 下标的方式赋值
* 报错提示: ::0xxxx
*/
@AfterReturning(pointcut = "pointcut()",returning = "result")
public void afterReturning(JoinPoint joinPoint,Object result){
System.out.println(Arrays.toString(joinPoint.getArgs()));
System.out.println("用户的返回值结果:"+result);
System.out.println("我是afterReturning通知");
}
/*
* throwing = "e" 动态接收程序运行时的报错信息,
* 利用异常通知进行记录
* */
@AfterThrowing(pointcut = "pointcut()",throwing = "e")
public void afterThrowing(Exception e){
System.out.println("获取异常信息:"+e.getMessage());
System.out.println("获取异常的类型:"+e.getClass());
System.out.println("我是afterThrowing");
}
@After("pointcut()")
public void after(){
System.out.println("我是after通知");
}
/**
* 关于环绕通知的说明
* 作用: 可以控制目标方法是否执行.
* 参数: ProceedingJoinPoint 通过proceed方法控制目标方法执行.
* 注意事项:
* ProceedingJoinPoint 只能适用环绕通知
* @return
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint){
Object result = null;
try {
System.out.println("环绕通知开始");
//1.执行下一个通知 2.执行目标方法 3.接收返回值
Long start = System.currentTimeMillis();
result = joinPoint.proceed();
Long end = System.currentTimeMillis();
System.out.println("耗时:"+(end-start));
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("环绕通知结束");
return result;
}
}
连接点:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//自定义注解 包括元注解
//注解的作用: 配合AOP进行注解类型 案例的训练 标识
//控制注解的生命周期
@Retention(RetentionPolicy.RUNTIME)
//注解的作用对象 方法有效 类有效 TYPE
@Target({ElementType.METHOD,ElementType.TYPE,ElementType.FIELD})
public @interface Cache {
}
切面:
@Service
public class DeptServiceImpl implements DeptService{
@Override
public void addDept() {
System.out.println("添加部门信息");
}
@Override
@Cache //被注解标识
public void updateDept() {
System.out.println("更新部门");
}
@Override
@Cache //标识该方法需要执行切面
public String after(Integer id) {
return "Spring通知的测试";
}
//让该方法执行时 抛出异常
@Override
@Cache
public void afterThrow() {
System.out.println("用户执行目标方法");
//手动抛出算数异常
int a = 1/0;
}
@Override
@Cache //标识执行AOP中的方法
public void doAround() {
System.out.println("实现用户数据的入库操作");
}
@Override
@Cache
public void doOrder() {
System.out.println("测试程序执行的顺序");
}
}