注解开发AOP制作步骤:
在XML格式基础上
- 导入坐标(伴随spring-context坐标导入已经依赖导入完成
- 开启AOP注解支持
- 配置切面@Aspect
- 定义专用的切入点方法,并配置切入点@Pointcut
- 为通知方法配置通知类型及对应切入点@Before
注解开发AOP注意事项:
- 切入点最终体现为一个方法,无参无返回值,无实际方法体内容,但不能是抽象方法
- 引用切入点时必须使用方法调用名称,方法后面的()不能省略
- 切面类中定义的切入点只能在当前类中使用,如果想引用其他类中定义的切入点使用“类名.方法名()”引用
- 可以在通知类型注解后添加参数,实现XML配置中的属性,例如after-returning后的returning属性
AOP注解详解:
@Aspect
-
名称:@Aspect
-
类型:注解
-
位置:类定义上方
-
作用:设置当前类为切面类
-
格式:
@Aspect
public class AopAdvice { }
- 说明:一个beans标签中可以配置多个aop:config标签
@Pointcut
-
名称:@Pointcut
-
类型:注解
-
位置:方法定义上方
-
作用:使用当前方法名作为切入点引用名称
-
格式:
@Pointcut("execution(* *(..))")public void pt() {}
- 说明:被修饰的方法忽略其业务功能,格式设定为无参无返回值的方法,方法体内空实现(非抽象)
@Before
-
名称:@Before
-
类型:注解
-
位置:方法定义上方
-
作用:标注当前方法作为前置通知
-
格式:
@Before("pt()")public void before() {}
-
特殊参数:
- 无
@After
-
名称:@After
-
类型:注解
-
位置:方法定义上方
-
作用:标注当前方法作为后置通知
-
格式:
@After("pt()")public void after() {}
-
特殊参数:
- 无
@AfterReturning
-
名称:@AfterReturning
-
类型:注解
-
位置:方法定义上方
-
作用:标注当前方法作为返回后通知
-
格式:
@AfterReturning(value = "pt()", returning = "ret")public void afterReturning(Object ret) {}
-
特殊参数:
- returning :设定使用通知方法参数接收返回值的变量名
@AfterThrowing
-
名称:@AfterThrowing
-
类型:注解
-
位置:方法定义上方
-
作用:标注当前方法作为异常后通知
-
格式:
@AfterThrowing(value = "pt()", throwing = "t")public void afterThrowing(Throwable t) {}
-
特殊参数:
- throwing :设定使用通知方法参数接收原始方法中抛出的异常对象名
@Around
-
名称:@Around
-
类型:注解
-
位置:方法定义上方
-
作用:标注当前方法作为环绕通知
-
格式:
@Around("pt()")public Object around(ProceedingJoinPoint pjp) throws Throwable {Object ret = pjp.proceed();return ret;}
配置文件加载注解:
public class AopPointcut {@Pointcut("execution(* *..ABC(..))")public void pt(){}
}
@Before("AopPointcut.pt()")public void before(){}
AOP注解开发通知执行顺序控制
1.AOP使用XML配置情况下,通知的执行顺序由配置顺序决定,在注解情况下由于不存在配置顺序的概念的概念,参照通知所配置的方法名字符串对应的编码值顺序,可以简单理解为字母排序
-
同一个通知类中,相同通知类型以方法名排序为准
-
不同通知类中,以类名排序为准
-
使用@Order注解通过变更bean的加载顺序改变通知的加载顺序
2.企业开发经验
-
通知方法名由3部分组成,分别是前缀、顺序编码、功能描述
-
前缀为固定字符串,例如baidu、itzhuzhu等,无实际意义
-
顺序编码为6位以内的整数,通常3位即可,不足位补0
-
功能描述为该方法对应的实际通知功能,例如exception、strLenCheck
-
制通知执行顺序使用顺序编码控制,使用时做一定空间预留
-
003使用,006使用,预留001、002、004、005、007、008
-
使用时从中段开始使用,方便后期做前置追加或后置追加
-
最终顺序以运行顺序为准,以测试结果为准,不以设定规则为准
-
AOP注解驱动
-
名称:@EnableAspectJAutoProxy
-
类型:注解
-
位置:Spring注解配置类定义上方
-
作用:设置当前类开启AOP注解驱动的支持,加载AOP注解
-
格式:
@Configuration
@ComponentScan("com.itzhuzhu")
@EnableAspectJAutoProxy
public class SpringConfig {
}
AOP底层原理:
- 静态代理
- 动态代理——Proxy
- 动态代理——CGLIB
- 织入形式
静态代理:
装饰者模式(Decorator Pattern):在不惊动原始设计的基础上,为其添加功能
public class UserServiceDecorator implements UserService{private UserService userService;public UserServiceDecorator(UserService userService) {this.userService = userService;}public void save() {//原始调用userService.save();//增强功能(后置)System.out.println("刮大白");}
}
动态代理——JDK Proxy:
JDKProxy动态代理是针对对象做代理,要求原始对象具有接口实现,并对接口方法进行增强
public class UserServiceJDKProxy {public UserService createUserServiceJDKProxy(final UserService userService){//获取被代理对象的类加载器ClassLoader classLoader = userService.getClass().getClassLoader();//获取被代理对象实现的接口Class[] classes = userService.getClass().getInterfaces();//对原始方法执行进行拦截并增强InvocationHandler ih = new InvocationHandler() {public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//前置增强内容Object ret = method.invoke(userService, args);//后置增强内容System.out.println("刮大白2");return ret;}};//使用原始被代理对象创建新的代理对象UserService proxy = (UserService) Proxy.newProxyInstance(classLoader,classes,ih);return proxy;}
}
动态代理——CGLIB:
- CGLIB(Code Generation Library),Code生成类库
- CGLIB动态代理不限定是否具有接口,可以对任意操作进行增强
- CGLIB动态代理无需要原始被代理对象,动态创建出新的代理对象
- 可以动态生成字节码文件
public class UserServiceImplCglibProxy {public static UserServiceImpl createUserServiceCglibProxy(Class clazz){//创建Enhancer对象(可以理解为内存中动态创建了一个类的字节码)Enhancer enhancer = new Enhancer();//设置Enhancer对象的父类是指定类型UserServerImplenhancer.setSuperclass(clazz);Callback cb = new MethodInterceptor() {public Object intercept(Object o, Method m, Object[] a, MethodProxy mp) throws Throwable {Object ret = mp.invokeSuper(o, a);if(m.getName().equals("save")) {System.out.println("刮大白");}return ret;}};//设置回调方法enhancer.setCallback(cb);//使用Enhancer对象创建对应的对象return (UserServiceImpl)enhancer.create();}
}
代理模式的选择:
Spirng可以通过配置的形式控制使用的代理形式,默认使用jdkproxy,通过配置可以修改为使用cglib
- XML配置
<!--XMP配置AOP-->
<aop:config proxy-target-class="false"></aop:config>
- XML注解支持
<!--注解配置AOP-->
<aop:aspectj-autoproxy proxy-target-class="false"/>
- 注解驱动
//注解驱动
@EnableAspectJAutoProxy(proxyTargetClass = true)
综合案例:
对项目进行业务层接口执行监控,测量业务层接口的执行效率
public interface AccountService {void save(Account account);void delete(Integer id);void update(Account account);List<Account> findAll();Account findById(Integer id);
}
案例分析:
-
测量接口执行效率:接口方法执行前后获取执行时间,求出执行时长
- System.currentTimeMillis( )
-
对项目进行监控:项目中所有接口方法,AOP思想,执行期动态织入代码
-
环绕通知
-
proceed()方法执行前后获取系统时间
-
案例制作步骤:
-
定义切入点(务必要绑定到接口上,而不是接口实现类上)
-
制作AOP环绕通知,完成测量功能
-
注解配置AOP
-
开启注解驱动支持
案例制作核代码:
@Component
@Aspect
public class RunTimeMonitorAdvice {//切入点,监控业务层接口@Pointcut("execution(* com.itzhuzhu.service.*Service.find*(..))")public void pt() {}@Around("pt()")public Object runtimeAround(ProceedingJoinPoint pjp) throws Throwable {//获取执行签名信息Signature signature = pjp.getSignature();//通过签名获取执行类型(接口名)String className = signature.getDeclaringTypeName();//通过签名获取执行操作名称(方法名)String methodName = signature.getName();//执行时长累计值long sum = 0L;for (int i = 0; i < 10000; i++) {//获取操作前系统时间beginTimelong startTime = System.currentTimeMillis();//原始操作调用pjp.proceed(pjp.getArgs());//获取操作后系统时间endTimelong endTime = System.currentTimeMillis();sum += endTime - startTime;}//打印信息System.out.println(className + ":" + methodName + " (万次)run:" + sum + "ms");return null;}
}
案例后续思考与设计:
-
测量真实性
-
开发测量是隔离性反复执行某个操作,是理想情况,上线测量差异过大
-
上线测量服务器性能略低于单机开发测量
-
上线测量基于缓存的性能查询要优于数据库查询测量
-
上线测量接口的性能与最终对外提供的服务性能差异过大
-
当外部条件发生变化(硬件),需要进行回归测试,例如数据库迁移
-
-
测量结果展示
-
测量结果无需每一个都展示,需要设定检测阈值
-
阈值设定要根据业务进行区分,一个复杂的查询与简单的查询差异化很大
-
阈值设定需要做独立的配置文件或通过图形工具配置(工具级别的开发)
-
配合图形界面展示测量结果
-