[TOC]
AOP简介
理解
AOP(Aspect-Oriented Programming), 即 面向切面编程,其基本思想是在极少影响原程序的代码的前提下,在程序中的某些地方,使用某些方式,不可见的(即不在原程序中添加其他代码)为原程序切入一些额外的功能。
优点:
- 减少代码间的耦合性,使功能具有拔插性,保证自己代码的清洁性。
- 能够让你只关注自己的代码,不需要关注切面是如何实现的。
术语
通知(advice)
其定义了切点什么时候去增强,是在方法调用前,还是调用之后,还是前后都是,还是抛出异常时。
- Before 某方法调用之前发出通知。
- After 某方法完成之后发出通知,不考虑方法运行的结果。
- After-returning 将通知放置在被通知的方法成功执行之后。
- After-throwing 将通知放置在被通知的方法抛出异常之后。
- Around 通知包裹在被通知的方法的周围,在方法调用之前和之后发出通知
连接点(join point)
可以被作为切点的地方,都可以被认为是链接点。
切点(point cut)
按照规则被选中的链接点,可以被称作为切点。
Aspect(切面)
aspect
由 pointcount
和 advice
组成, 它既包含了横切逻辑的定义, 也包括了连接点的定义. Spring AOP就是负责实施切面的框架, 它将切面所定义的横切逻辑织入到切面所指定的连接点中.
目标对象(Target)
织入 advice 后的目标对象. 目标对象也被称为 advised object
.
引入(introductions):
- 引入允许你添加一个新的方法给已经存在的类。
Spring对AOP的支持
- Spring建议在Java中书写AOP
- Spring是在运行阶段才将切面编织进bean中,是使用代理类。
- Spring只支持方法级别的连接点。
AOP应用
XML形式的AOP
proxy-target-class="true"
指定使用GCLIB代理,如果proxy-target-class="false"
或者没设置,则默认使用动态代理,但是如果代理类没有实现接口,则依然会使用GCLIB代理。
aop:pointcut
指定了切点。
aop:advisor
指定了通知时机,同样的还有aop:before
aop:after
。
需要注意的是spiritCommonInterceptor实现了MethodInterceptor接口
<bean id="spiritCommonInterceptor" class="com.mogujie.stable.spirit.point.methond.CommonInterceptor"/>
<aop:config proxy-target-class="true">
<aop:pointcut id="modulePoint" expression="@target(com.mogujie.stable.spirit.point.annotation.ClassSpirit) and @annotation(com.mogujie.stable.spirit.point.annotation.MethodSpirit)"/>
<aop:advisor advice-ref="spiritCommonInterceptor" pointcut-ref="modulePoint"/>
</aop:config>
public class CommonInterceptor implements MethodInterceptor {
private static final Logger LOG = LoggerFactory.getLogger(CommonInterceptor.class);
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method executed = invocation.getMethod();
Class<?> clazz = invocation.getThis().getClass();
ClassSpirit classSpirit = clazz.getAnnotation(ClassSpirit.class);
MethodSpirit methodSpirit = executed.getAnnotation(MethodSpirit.class);
// 不做限流降级处理
if (executed.getName().equals("toString") || executed.getName().equals("hashCode") || executed.getName().equals("equals") || (null != classSpirit && !classSpirit.trace()) || (null == classSpirit && null != methodSpirit && !methodSpirit.trace()) || ((null != classSpirit && classSpirit.trace()) && (null == methodSpirit || !methodSpirit.trace()))) {
return invocation.proceed();
}
Entry entry = null;
try {
String methodName = MethodUtil.getMethodName(executed);
// 初始化Context
ContextUtil.enter(methodName);
// 初始化Entry
entry = EntryUtil.entry(executed);
// 执行方法
Object result = invocation.proceed();
return result;
} catch (Throwable e) {
throw ExceptionUtil.dealProxyException(e);
} finally {
if (entry != null) {
entry.exit();
}
ContextUtil.exit();
}
}
}
Annotation形式的AOP
Spring除了支持Schema方式配置AOP,还支持注解方式:使用@Aspect来配置。但Spring默认不支持@Aspect风格的切面声明,通过如下配置开启@Aspect支持:
<aop:aspectj-autoproxy/>
package com.sxit;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class AspectStyle {
@Pointcut("execution(* com.sxit..*.*(..))")
public void init(){
}
@Before(value="init()")
public void before(){
System.out.println("方法执行前执行.....");
}
@AfterReturning(value="init()")
public void afterReturning(){
System.out.println("方法执行完执行.....");
}
@AfterThrowing(value="init()")
public void throwss(){
System.out.println("方法异常时执行.....");
}
@After(value="init()")
public void after(){
System.out.println("方法最后执行.....");
}
@Around(value="init()")
public Object around(ProceedingJoinPoint pjp){
System.out.println("方法环绕start.....");
Object o = null;
try {
o = pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("方法环绕end.....");
return o;
}
}
一个Annotation与AOP结合的例子
要实现一个aop的功能,关键在于三个地方。
- 通知(Advice) 定义了
何时
切。比如:before、around等。 - 切点(PointCut) 定义了
何处
切。比如:execution(* com.mogujie.houston.openapi.api.impl..*(..))
- 连接点(JoinPoint) 连接点是在应用执行过程中能够插入切面的一个点。能够利用它拿到应用的方法和参数等。
aspect
@Aspect
@Component
public class ValidatorAspect implements ApplicationContextAware {
private static Logger logger = LoggerFactory.getLogger(ValidatorAspect.class);
protected static ApplicationContext context;
@Around("execution(* com.mogujie.houston.openapi.api.impl..*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
try {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
if (method.getDeclaringClass().isInterface()) {
try {
method = joinPoint.getTarget().getClass().getDeclaredMethod(joinPoint.getSignature().getName(),
method.getParameterTypes());
} catch (final SecurityException exception) {
}
}
// check passport
Object[] args = joinPoint.getArgs();
Validator validatorClass = method.getAnnotation(Validator.class);
if (null != validatorClass) {
ValidationHandler validationHandler = validatorClass.handler().newInstance();
HoustonOpenApiResult result = validationHandler.validate(args);
if (!result.isSuccess()) {
return result;
}
}
TokenValidator tokenValidatorClass = method.getAnnotation(TokenValidator.class);
if (tokenValidatorClass != null) {
TokenValidationHandler tokenValidationHandler = tokenValidatorClass.handler().newInstance();
HoustonOpenApiResult result = tokenValidationHandler.check(args, context);
if (!result.isSuccess()) {
return result;
}
}
} catch (Exception e) {
logger.error("ValidatorAspect验证出现异常", e);
return HoustonOpenApiResult.error(OpenApiResultCode.INNER_ERROR, "系统异常 请@Houston答疑, error:" + e.getMessage());
}
try {
return joinPoint.proceed();
} catch (Exception e) {
logger.error("Service服务出现异常", e);
return HoustonOpenApiResult.error(OpenApiResultCode.INNER_ERROR, "Service出现异常 请@Houston答疑, error:" + e.getMessage());
}
}
public static ApplicationContext getContext() {
return context;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ValidatorAspect.context = applicationContext;
}
}
@TokenValidator
@Documented
@Target({ElementType.METHOD})//只能在方法上使用
@Retention(RetentionPolicy.RUNTIME)//运行时使用
public @interface TokenValidator {
Class<? extends TokenValidationHandler> handler() default TokenValidationHandler.class;//定义了一个接口类
}
TokenValidationHandler
public interface TokenValidationHandler {
HoustonOpenApiResult check(Object[] args, ApplicationContext applicationContext);
HoustonOpenApiResult getGroupResult(Object[] args, DefaultGroupBiz defaultGroupBiz);
}
一个Handler的实现
public abstract class BaseTVHandler implements TokenValidationHandler {
@Override
public HoustonOpenApiResult check(Object[] args, ApplicationContext context) {
if (args.length >= 2) {
Token token = (Token) args[0];
DefaultGroupBiz defaultGroupBiz = context.getBean(DefaultGroupBiz.class);
HoustonOpenApiResult<Group> groupResult = getGroupResult(args, defaultGroupBiz);
if (!groupResult.isSuccess()) {
return groupResult;
}
if (TokenUtil.check(token, groupResult.getData().getKeyName())) {
return HoustonOpenApiResult.success(true);
}
return new HoustonOpenApiResult(OpenApiResultCode.TOKEN_ILLEGAL);
} else {
return new HoustonOpenApiResult(OpenApiResultCode.TOKEN_PARAM_ERROR);
}
}
}
public class ConfigValueTokenValidator {
public static class DetailHandler extends BaseTVHandler {
@Override
public HoustonOpenApiResult getGroupResult(Object[] args, DefaultGroupBiz defaultGroupBiz) {
ConfigValueDetail configValueDetail = (ConfigValueDetail) args[1];
return defaultGroupBiz.queryByConfigId(configValueDetail.getConfigId());
}
}
}
AOP原理
Spring AOP使用了两种代理机制:一种是基于JDK的动态代理;另一种是基于CGLib的动态代理。之所以需要两种代理机制,很大程度上是因为JDK本身只提供接口的代理,而不支持类的代理。
JDK动态代理
步骤
- 通过实现InvocationHandler接口创建自己的调用处理器
- 通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理类
- 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型
- 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入
public class DynamicTest implements InvocationHandler {
private Test target;
private DynamicTest(Test target) {
this.target = target;
}
public static Test newProxyInstance(Test test) {
return (Test) Proxy.newProxyInstance(test.getClass().getClassLoader(), test.getClass().getInterfaces(), new DynamicTest(test));
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target, args);
}
}
基于CGLIB的动态代理
CGLIB直接生成代理目标类的子类,不能对目标类中的final方法进行代理。
-
查找A上的所有非final 的public类型的方法定义;
-
将这些方法的定义转换成字节码;
-
将组成的字节码转换成相应的代理的class对象;
-
实现 MethodInterceptor接口,用来处理 对代理类上所有方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色是一样的)
public class CglibTest implements MethodInterceptor {
private CglibTest() {
}
public static <T extends Test> Test newProxyInstance(Class<T> targetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(new CglibTest());
return (Test) enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o, objects);
}
}
ASM(介绍)
ASM 是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
不过ASM在创建class字节码的过程中,操纵的级别是底层JVM的汇编指令级别,这要求ASM使用者要对class组织结构和JVM汇编指令有一定的了解。
Javassist(介绍)
Javassist是一款字节码编辑工具,可以直接编辑和生成Java生成的字节码,以达到对.class文件进行动态修改的效果。熟练使用这套工具,可以让Java编程更接近与动态语言编程。
JDK动态代理与CGLIB性能比较
- 被代理接口
public interface Test {
public int test(int i);
}
- 实现类
public class TestImpl implements Test {
@Override
public int test(int i) {
return i + 1;
}
public void print() {
System.out.println("111111");
}
}
- JDK代理类
public class DynamicTest implements InvocationHandler {
private Test target;
private DynamicTest(Test target) {
this.target = target;
}
public static Test newProxyInstance(Test test) {
return (Test) Proxy.newProxyInstance(test.getClass().getClassLoader(), test.getClass().getInterfaces(), new DynamicTest(test));
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target, args);
}
}
- CGLIB代理类
public class CglibTest implements MethodInterceptor {
private CglibTest() {
}
public static <T extends Test> Test newProxyInstance(Class<T> targetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(new CglibTest());
return (Test) enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o, objects);
}
}
- 测试类
public class ProxyPerfTester {
public static void main(String[] args) {
//创建测试对象;
Test nativeTest = new TestImpl();
Test dynamicProxy = DynamicTest.newProxyInstance(nativeTest);
Test cglibProxy = CglibTest.newProxyInstance(TestImpl.class);
//预热一下;
int preRunCount = 100000;
runWithoutMonitor(nativeTest, preRunCount);
runWithoutMonitor(cglibProxy, preRunCount);
runWithoutMonitor(dynamicProxy, preRunCount);
//执行测试;
Map<String, Test> tests = new LinkedHashMap<String, Test>();
tests.put("Native ", nativeTest);
tests.put("Dynamic ", dynamicProxy);
tests.put("Cglib ", cglibProxy);
int repeatCount = 3;
int runCount = 1000000;
runTest(repeatCount, runCount, tests);
runCount = 50000000;
runTest(repeatCount, runCount, tests);
}
private static void runTest(int repeatCount, int runCount, Map<String, Test> tests){
System.out.println(String.format("\n==================== run test : [repeatCount=%s] [runCount=%s] [java.version=%s] ====================", repeatCount, runCount, System.getProperty("java.version")));
for (int i = 0; i < repeatCount; i++) {
System.out.println(String.format("\n--------- test : [%s] ---------", (i+1)));
for (String key : tests.keySet()) {
runWithMonitor(tests.get(key), runCount, key);
}
}
}
private static void runWithoutMonitor(Test test, int runCount) {
for (int i = 0; i < runCount; i++) {
test.test(i);
}
}
private static void runWithMonitor(Test test, int runCount, String tag) {
long start = System.currentTimeMillis();
for (int i = 0; i < runCount; i++) {
test.test(i);
}
long end = System.currentTimeMillis();
System.out.println("["+tag + "] Elapsed Time:" + (end-start) + "ms");
}
}
- 结果
Create Native Proxy:1ms
Create Dynamic Proxy17ms
Create Cglib Proxy521ms
==================== run test : [repeatCount=3] [runCount=1000000] [java.version=1.7.0_79] ====================
--------- test : [1] ---------
[Native ] Elapsed Time:7ms
[Dynamic ] Elapsed Time:289ms
[Cglib ] Elapsed Time:93ms
--------- test : [2] ---------
[Native ] Elapsed Time:7ms
[Dynamic ] Elapsed Time:12ms
[Cglib ] Elapsed Time:51ms
--------- test : [3] ---------
[Native ] Elapsed Time:6ms
[Dynamic ] Elapsed Time:14ms
[Cglib ] Elapsed Time:45ms
==================== run test : [repeatCount=3] [runCount=50000000] [java.version=1.7.0_79] ====================
--------- test : [1] ---------
[Native ] Elapsed Time:468ms
[Dynamic ] Elapsed Time:1855ms
[Cglib ] Elapsed Time:1577ms
--------- test : [2] ---------
[Native ] Elapsed Time:165ms
[Dynamic ] Elapsed Time:418ms
[Cglib ] Elapsed Time:807ms
--------- test : [3] ---------
[Native ] Elapsed Time:161ms
[Dynamic ] Elapsed Time:484ms
[Cglib ] Elapsed Time:889ms
可见在JDK1.7下:
- 运行速度,Native是最快的,JDK动态代理稍次之,CGLIB最慢。
- 创建速度,Native是最快的,JDK动态代理稍次之,CGLIB最慢。
网友评论