1、spring的AOP面向切面编程的思想
核心思想:在不改变原有代码的基础上,添加其他的功能;即把原来的代码调过来,然后再原来代码的基础上,在其前面或者后面添加额外的功能;
场景:权限控制、缓存、日志处理、事务控制;
拦截器也是aop思想的一种利用;
利用aop的代码中,一般分为两部分核心与非核心;核心部分一般就是原来的代码,非核心就是通过切入点,额外加入的功能;
spring的AOP 本质是一种动态代理,分为JDK动态代理(实现了接口) 和CGlab动态代理两种;
2、核心概念:横切、通知、连接点、切入点、切面
advice通知的类型:
@Before前置通知
@After后置通知
@AfterReturning返回通知
@AfterThrowing异常通知
@Around环绕通知
3、aop的示例
整体而言,要使用aop,首先需要定义一个切面类,在切面类里面定义出切入点和通知,其中切入点就是在什么地方插入,通知就是插入之后在这个位置做什么事情;
切入点需要通过切入点表达式来定义,是一个正则表达式;
通知即前面提到的 before、after等;
示例:在VideoOrderService类中利用AOP编程;
目标类:
VideoOrderService{
//新增订单
addOrder(){ }
//查询订单
findOrderById(){}
//删除订单
delOrder(){}
//更新订单
updateOrder(){}
}
切面编程
//权限切面类 = 切入点+通知
PermissionAspects{
//切入点 定义了什么地方
@Pointcut("execution(public int net.xdclass.sp.service.VideoOrderService.*(..))")
public void pointCut(){
}
//before 通知 表示在目标方法执行前切入, 并指定在哪个方法前切入
//什么时候,做什么事情
@Before("pointCut()")
public void permissionCheck(){
System.out.println("在 xxx 之前执行权限校验");
}
....
}
4、切入点表达式
切入点表达式类似于一个正则表达式;
访问修饰符 返回值类型(必填) 包和类 方法(必填)
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(parampattern) throws-pattern?)
.. 匹配任何数量字符,可以多个,在类型模式中匹配任何数量子包;在方法参数模式中匹配任何数量参数
() 匹配一个不接受任何参数的方法 (..) 匹配一个接受任意数量参数的方法 (*) 匹配了一个接受一个任何类型的参数的方法 (*,Integer) 匹配了一个接受两个参数的方法,其中第一个参数是任意类型,第二个参数必须是Integer类型
5、spring动态代理和静态代理
静态代理 --- 接口
动态代理-- 反射实现--- JDK动态代理+ CGlab动态代理;
5.1 JDK动态代理的实现:
通过反射机制在程序运行的时候动态实现的,需要目标类实现了一个接口;
public class JdkProxy implements InvocationHandler {
//目标类,需要代理的类
private Object targetObject;
//获取代理对象 将代理类和实际的目标类绑定
public Object newProxyInstance(Object targetObject){
this. targetObject = targetObject;
//绑定关系,也就是和具体的哪个实现类关联
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(),this);
}
public Object invoke(Object proxy, Method method, Object[] args) {
Object result = null;
try{
System.out.println("通过JDK动态代理调用 "+method.getName() +", 打印日志 begin");
result = method.invoke(targetObject,args); // 这一句实现的是被代理的方法原来的逻辑,可以在此处对原来返回的结果做进一步的修改
System.out.println("通过JDK动态代理调用 "+method.getName() +", 打印日志 end");
}catch (Exception e){
e.printStackTrace();
}
return result;
}
}
其核心是实现InvocationHandler这个接口,并且重写里面的invoke方法,这个方法需要传入三个参数,第一个是需要代理的类,第二个是这个类的里面的一个方法,第三个是方法里面的参数。
// 使用上面的动态代理
JdkProxy jdkProxy = new JdkProxy();
// 获取代理类对象 传入的参数就是targetObject ,也就是需要被代理的类
PayService payServiceProxy = (PayService)jdkProxy.newProxyInstance(new PayServiceImpl());
// 调用目标方法
payServiceProxy.callback("aaa"); // 此时调用目标方法后还会执行 result = method.invoke(targetObject,args); 这一句前后的内容
使用Jdk
动态代理的时候,可以套用上面的模板,只需要修改 result = method.invoke(targetObject,args); 这一句前后的内容,加上需要的逻辑,本质跟静态代理一样,都是在目标方法加入一定的逻辑,只是动态代理是通过反射实现
第二行就是 方法被代理前返回的结果,第一和第三行 是新加入的内容
5.2 CGlab动态代理点实现
如果代理类没有实现接口的时候,用CGlab动态代理;
这个代理需要用了spring的包才可以用,或者单独导入cglab的包;
需要实现springfromwork包下的 MethodInterceptor接口;
public class CglibProxy implements MethodInterceptor {
//目标类
private Object targetObject;
//绑定关系
public Object newProxyInstance(Object targetObject){
this.targetObject = targetObject;
Enhancer enhancer = new Enhancer();
//设置代理类的父类(目标类)
enhancer.setSuperclass(this.targetObject.getClass());
//设置回调函数
enhancer.setCallback(this);
//创建子类(代理对象 serviceImpl)
return enhancer.create();
}
/**
通过拦截实现*/
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object result = null;
try{
System.out.println("通过CGLIB动态代理调用 "+method.getName() +", 打印日志 begin");
result = methodProxy.invokeSuper(o,args); // 调用父类,即执行的是父类中的方法
System.out.println("通过CGLIB动态代理调用 "+method.getName() +", 打印日志 begin");
}catch (Exception e){
e.printStackTrace();
}
return result;
}
}
CglibProxy cglabProxy = new CglibProxy();
OayService payservice = (PayService) cglibProxy.newProxyInstance(new PayServiceImpl());
// 调用目标方法
payservice.save()
两种动态代理的区别:
Spring AOP中的代理使用的默认策略:
6、利用注解实现AOP面向切面编程
一般是通过单独写一个切面类,然后再切面类里面写入点和通知;
简易模板:
@Component
//告诉spring,这个一个切面类,里面可以定义切入点和通知
@Aspect
public class LogAdvice {
//切入点表达式
@Pointcut("execution(* net.xdclass.sp.service.VideoServiceImpl.*(..))")
public void aspect(){
}
//前置通知
@Before("aspect()")
// @Before("execution(* net.xdclass.sp.service.VideoServiceImpl.*(..))")
public void beforeLog(JoinPoint joinPoint){
System.out.println("LogAdvice beforeLog");
}
//后置通知
@After("aspect()")
public void afterLog(JoinPoint joinPoint){
System.out.println("LogAdvice afterLog");
}
/**
* 环绕通知
* @param joinPoint
*/
@Around("aspect()")
public void around(JoinPoint joinPoint){
Object target = joinPoint.getTarget().getClass().getName();
System.out.println("调用者="+target);
//目标方法签名
System.out.println("调用方法="+joinPoint.getSignature());
//通过joinPoint获取参数
Object [] args = joinPoint.getArgs();
System.out.println("参数="+args[0]);
long start = System.currentTimeMillis();
System.out.println("环绕通知 环绕前=========");
//执行连接点的方法
try {
((ProceedingJoinPoint)joinPoint).proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("环绕通知 环绕后=========");
System.out.println("调用方法总耗时 time = " + (end - start) +" ms");
}
}
@EnableAspectJAutoProxy //启动类上开启spring对aspect的支持
可以通过切面来统计目标方法的性能;