编程范式概览
- 面向过程编程
- 面向对象编程
- 函数式编程
- 事件驱动编程
- 面向切面编程
AOP是什么
- 是一种编程范式,不是编程语言
- 解决特定问题,不能解决所有问题
- 是OOP的补充,不是替代
AOP的初衷
- DRY:Don't Repeat Yourself 解决代码重复性问题
- SoC:Separation of Concerns 解决关注点问题
- 水平分离:展示层 --> 服务层 --> 持久层
- 垂直分离:模块划分(订单、库存等)
- 切面分离:分离功能性需求与非功能性需求(把非功能性需求从功能性需求中分离出来,从而实现DRY)
使用AOP的好处
- 集中处理某一关注点/横切逻辑
- 可以很方便地添加/删除关注点
- 侵入性少,增强代码可读性及可维护性
AOP应用场景
- 权限控制
- 缓存控制
- 事务控制
- 审计日志
- 性能监控
- 分布式追踪
- 异常处理
支持AOP的编程语言
- Java
- .Net
- C/C++
- Ruby
- Python
- PHP
注解:
- @Aspect - 申明成切面类
- @Pointcut("@annotation(AdminOnly)") - 申明表达式,拦截有AdminOnly的注解;
- @Before("adminOnly()") - 在adminOnly()方法执行之前,执行该注解方法。
Spring AOP使用方式
- XML配置
- 注解方式
主要注解
- AspectJ 注解
- @Aspect:用来标注说明这个Java类是一个切面配置类
- @Pointcut:描述在哪些类的哪些方法进行植入你的代码
- Advice:你想要在这些方法的执行什么时机进行植入
Pointcut expression:切面表达式
- designators:execution() 、 ...
- wildcards:* / .. / +
- operators:&& / || / !
Wildcards(通配符)
- * :匹配任意数量的字符
- + :匹配指定类及其子类
- .. :一般用于匹配任意数的子包或参数
Operators(运算符)
- && :与操作符
- || :或操作符
- ! :非操作符
designators
- 匹配方法:execution()
- 匹配注解:
- @target()
- @args()
- @within()
- @annotation()
- 匹配包/类型
- within()
- 匹配对象
- this()
- bean()
- target()
- 匹配参数
- args()
匹配包/类型
// 匹配ProductService类里头的所有方法
@Pointcut("within(com.imooc.service.ProductService)")
public void matchType(){}
// 匹配com.imooc包及子包下所有类的方法
@Pointcut("within(com.imooc..*)")
public void matchPackage(){}
匹配对象
// 匹配AOP对象的目标对象为指定类型的方法,即DemoDao的aop代理对象的方法
@Pointcut("this(com.imooc.DemoDao)")
public void thisDemo(){}
// 匹配实现IDao接口的目标对象(而不是aop代理后的对象)的方法,这里即DemoDao的方法
@Pointcut("target(com.imooc.IDao)")
public void targetDemo(){}
// 匹配所有以Service结尾的bean里头的方法
@Pointcut("bean(*Service)")
public void beanDemo(){}
参数匹配
// 匹配任何以find开头而且只有一个Long参数的方法
@Pointcut("execution(* *..find*(Long))")
public void argsDemo1(){}
// 匹配任何只有一个Long参数的方法
@Pointcut("args(Long)")
public void argsDemo2(){}
// 匹配任何以find开头的而且第一个参数为Long型的方法
@Pointcut("execution(* *..find*(Long,..))")
public void argsDemo3(){}
// 匹配第一个参数为Long型的方法
@Pointcut("args(Long,..)")
public void argsDemo4(){}
匹配注解
// 匹配方法标注有AdminOnly的注解的方法
@Pointcut("@annotation(com.imooc.demo.security.AdminOnly)")
public void annoDemo(){}
// 匹配标注有Beta的类底下的方法,要求的annotation的RetentionPolicy级别为CLASS
@Pointcut("@within(com.google.common.annotations.Beta)")
public void annoWithinDemo(){}
// 匹配标注有Repository的类底下的方法,要求的annotation的RetentionPolicy级别为RUNTIME
@Pointcut("@target(org.springframework.stereotype.Repository)")
public void annoTargetDemo(){}
// 匹配传入的参数类标注有Repository注解的方法
@Pointcut("@args(org.springframework.stereotype.Repository)")
public void annoArgsDemo(){}
execution()表达式
格式
execution(
modifier-pattern? - 修饰符
ret-type-pattern - 返回值
declaring-type-pattern? - 描述包名
name-pattern(param-pattern) - 描述方法名(方法参数)
throws-pattern? - 匹配方法抛出的异常
)
标注?表示可省略的!
5种Advice注解
1、@Before,前置通知
2、@After(finally),后置通知,方法执行完之后
3、@AfterReturning,返回通知,成功执行之后
4、@AfterThrowing,异常通知,抛出异常之后
5、@Around,环绕通知
Advice中的参数及结果绑定
// 拦截获取方法的参数进行校验
@Before(value="annoTargetVsWithinDemo() && within(com.imooc..*) && args(userId)")
public void beforeWithArgs(JoinPoint joinPoint, Long userId){
System.out.println("before, args:"+userId) ;
}
// 打印方法的返回值
@AfterReturning(value="annoTargetVsWithinDemo() && within(com.imooc..*)",returning="returnValue")
public void getReulst(Obgect returnValue){
if(returnValue != null){
System.out.println("after return, result:"+returnValue) ;
}
}
原理概述:织入的时机
1、编译期(AspectJ)
2、类加载时(AspectJ 5+)
3、运行时(Spring AOP)
原理概述:运行时织入
- 运行时织入是怎么实现的? 代理对象
- 从静态代理到动态代理
- 静态代理的缺点:每当代理的方法越多时,重复的逻辑也越多。
- 动态代理的两类实现:基于接口代理与基于继承代理。
- 两大实现的代表:JDK代理与Cglib代理。
- 基于接口代理与基于继承代理
-
JDK实现要点
- 类:java.lang.reflect.Proxy
- 接口:InvocationHandler
- 只能基于接口进行动态代理
-
JDK代理源码解析
- Proxy.newProxyInstance:生成代理类的实现类
- getProxyClass0:生成指定的代理类、ProxyClassFactory、ProxyGenerator
- newInstance
Cglib实现
static class DemoCglibInterceptor implements MethodInterceptor{
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable{
System.out.println("before...");
Object result = methodProxy.invokeSuper(obj, args);
System.out.println("after...");
return result ;
}
}
public static void main(String[] args){
Subject subject = (Subject)getProxy(RealSubject.class, new DemoCglibInterceptor());
subject.request();
}
JDK与Cglib代理对比
- JDK只能针对有接口的类的接口方法进行动态代理
- Cglib基于继承来实现代理,无法对static、final类进行代理
- Cglib基于继承来实现代理,无法对private、static方法进行代理
Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。
DefaultAopProxyFactory
@SuppressWarnings("serial")
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable{
@Override
public AopProxy creatAopProxy(AdvisedSupport config) throws AopConfigException{
if(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)){
Class<?> targetClass = config.getTargetClass();
if(targetClass == null){
throw new AopConfigException("TargetSource cannot determine target class " +
"Either an interface or a target is required for proxy creation.")
}
if(targetClass.isInterface() || Proxy.isProxyClass(targetClass)){
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}else{
return new JdkDynamicAopProxy(config);
}
}
}
Spring创建代理的规则为:
1、如果目标对象实现了接口,则默认才用JDK动态代理。
2、如果目标对象没有实现接口,则才用CGLIB动态代理。
3、如果目标对象实现了接口,并指定为CGLIB代理,则使用CGLIB动态代理。
强制使用Cglib代理
@SpringBootApplication
// 强制使用cglib代理
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class AopDemoApplication{
public static void main(String[] args){
SpringApplication.run(AopDemoApplication.class, args);
}
}
安全校验@PreAuthorize
- MethodSecurityInterceptor
- PreInvocationAuthorizationAdviceVoter
- ExpressionBasedPreInvocationAdvice
缓存@Cacheable
- AnnotationCacheAspect
- CacheInterceptor
- CacheAspectSupport