开发过程中,可能会遇到这种情况,比如你想对某个已经实现好的方法进行扩展或者增强,因为这个方法无法达到你的预期,但是这个方法的实现你又需要用到,你要别人去改代码来达到你自己的需求,这个可能不太现实。这时候你就需要有几种方案去实现方法的扩展或增强。下面就通过一个例子了解如何实现方法增强。
栗子:
自定义接口,
package com.hust.zhang.example;
public interface SuperMarket {
int SUCCESS = 200;
int ERROR = 100;
/**
* 采购商品
*
* @return 返回状态码
*/
int purchasing();
/**
* 售卖商品
*
* @param id 商品Id
* @return 返回状态码
*/
int selling(Long id);
}
接口实现类,
public class SuperMarketImpl implements SuperMarket {
/**
* 商品成本列表
*/
private static final HashMap costMap = new HashMap() {
{
put(1L, 100);
put(2L, 200);
put(3L, 300);
}
};
@Override
public int purchasing() {
System.out.println(this.getClass().getSimpleName() + " commodity ");
return SUCCESS;
}
@Override
public int selling(Long id) {
int price = costMap.get(id);
if (Objects.isNull(price)) {
System.out.println("the commodity id: " + id + " is not exist");
return ERROR;
}
System.out.println(this.getClass().getSimpleName() + " commodity id: " + id + " price: " + price);
return SUCCESS;
}
}
代理模式(Proxy Pattern)是通过代理对象访问目标对象,这样可以在目标对象基础上增强额外的功能,如添加权限,访问控制和审计等功能。
代理分为静态代理和动态代理,静态代理会生成代理类,而动态代理不同于静态代理,它不会生成过多的代理类对象,而是通过Java反射机制生成。
静态代理对象,
public class MarketAgent implements SuperMarket {
/**
* 被代理对象
*/
private SuperMarket target;
/**
* 代理对象 有参构造函数
* @param target
*/
public MarketAgent(SuperMarket target) {
this.target = target;
}
@Override
public int purchasing() {
target.purchasing();
return SUCCESS;
}
@Override
public int selling(Long id) {
target.selling(id);
return SUCCESS;
}
}
静态代理测试类及结果如下,可以看到静态代理每次使用都需要写代理类,被代理类实例化后通过代理类的构造方法传入,再通过代理类进行调用代理方法。
JDK动态代理通过Proxy类的newProxyInstance方法创建代理实例。
package com.hust.zhang.example;
import java.lang.reflect.Proxy;
import java.util.Objects;
public class JdkDynamicProxy {
private static final String PURCHASE = "purchasing";
private static final String SELL = "selling";
public static void main(String[] args) {
SuperMarket market = new SuperMarketImpl();
SuperMarket proxyInstance = (SuperMarket) Proxy.newProxyInstance(market.getClass().getClassLoader(), market.getClass().getInterfaces(), ((object, method, param) -> {
int result = 0;
if (Objects.equals(method.getName(), PURCHASE)) {
System.out.println("代理商完成采购");
result = (int) method.invoke(market, param);
} else if (Objects.equals(method.getName(), SELL)) {
System.out.println("代理商完成售卖");
result = (int) method.invoke(market, param);
}
return result;
}));
proxyInstance.selling(1L);
}
}
上面的方式属于JDK动态代理,主要流程如下:
Cglib动态代理使用Enhancer类对方法进行增强。Enhancer类是一个增强类,它会生成动态子类以启动方法拦截。通过Enhancer.create()方法创建实例。
public class CglibDynamicProxy {
private static final String PURCHASE = "purchasing";
private static final String SELL = "selling";
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SuperMarketImpl.class);
enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
int result = 0;
if (Objects.equals(method.getName(), PURCHASE)) {
System.out.println("代理商完成采购");
result = (int) methodProxy.invokeSuper(o, objects);
} else if (Objects.equals(method.getName(), SELL)) {
System.out.println("代理商完成售卖");
result = (int) methodProxy.invokeSuper(o, objects);
}
return result;
});
SuperMarket market = (SuperMarket) enhancer.create();
market.selling(1L);
}
}
注意:
- 上面的代码里因为SuperMarketImpl类被增强,所以类名加上了EnhancerByCGLIB等信息。
- 如果一个类没有实现接口,那么它就不能使用JDK动态代理,但是可以使用CGLIB动态代理。
本工程使用了Springboot框架,加上@Service注解注入组件,使用自定义注解对方法进行环绕增强,详细可参看AOP原理。
@Service
public class SuperMarketImpl implements SuperMarket {
/**
* 商品成本列表
*/
private static final HashMap costMap = new HashMap() {
{
put(1L, 100);
put(2L, 200);
put(3L, 300);
}
};
@Override
public int purchasing() {
System.out.println(this.getClass().getSimpleName() + " commodity ");
return SUCCESS;
}
@Override
@EnhanceMethod(field = "test static weaving")
public int selling(Long id) {
int price = costMap.get(id);
if (Objects.isNull(price)) {
System.out.println("the commodity id: " + id + " is not exist");
return ERROR;
}
System.out.println(this.getClass().getSimpleName() + " commodity id: " + id + " price: " + price);
return SUCCESS;
}
}
这里对selling动作进行增强,使用无侵入的方式,新建一个注解EnhanceMethod,其中元注解解释如下:
@Target:描述注解的使用范围(即:被修饰的注解可以用在什么地方) @Retention:描述注解保留的时间范围(即:被描述的注解在它所修饰的类中可以被保留到何时) @Documented:描述在使用javadoc工具为类生成帮助文档时是否要保留其注解信息。 @Inherited:使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解)。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface EnhanceMethod {
//注解传入的属性
String field() default "Static Weaving";
}
增加切面类,AOP提供5种类型,这里使用环绕增强,对该方法增强只多打印两行日志(参数从切入点获取):
@Component
@Aspect
public class EnhanceMethodAspect {
private static final String ANNOTATION_PATH = "@annotation(com.hust.zhang.example.EnhanceMethod)";
protected Logger logger = LoggerFactory.getLogger(this.getClass());
@Pointcut(ANNOTATION_PATH)
public void testPointcut() {
}
@SuppressWarnings({"unchecked", "rawtypes"})
@Around(ANNOTATION_PATH)
public Object around(ProceedingJoinPoint pjp) throws Throwable {
//通过反射拿到目标方法的类名、方法名、参数、方法
String clazz = pjp.getTarget().getClass().getSimpleName();
String methodName = pjp.getSignature().getName();
Object[] args = pjp.getArgs();
logger.info("Invoke {}.{} and parameter is {}", clazz, methodName, args);
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
EnhanceMethod myAnnotation = method.getAnnotation(EnhanceMethod.class);
logger.info("my definition annotation is {}", myAnnotation);
return pjp.proceed();
}
}
启动应用,调用对外提供的接口,可以看到在方法上进行了增强,只是通过多增加了一个注解。
本文介绍了对方法增强的几种方式,静态代理、动态代理(JDK和CGLIB)以及静态织入,最后一种方案是通过AOP来实现的,是一种无侵入式的方案,通常对方法加缓存或加日志都采用这种方式。不过具体开发中开发者如何选型可以根据实际情况来决定。