动态代理:
动态代理可以在类的所有方法之前增加业务逻辑。jdk中的类想要被动态代理,必须实现接口(假设Target实现了接口ITarget)。使用proxy与实现InvocationHandler接口,来实现动态代理。PHandler实现InvocationHandler接口,其中含有private的Object(被代理对象target)。在自己的invoke方法(参见反射)中先写入自己的代理前业务逻辑,再通过反射方法调用被代理对象的对应方法(即method.invoke(target,args)),最后加上代理后业务逻辑。如果针对不同的业务逻辑增加不同的业务,可以将method传入PHandler的beforeMethod()方法,进而判断不同的method写入不同的beforeMethod方法内容。
实现动态代理的代码逻辑:
先产生一个被代理对象target,产生一个PHandler,将target用setter传给PHandler。然后使用ITarget targetProxy = Proxy.newProxyInstance(arg0, arg1, arg2),将target与handler传入。传入方法为arg0:target.getClass().getClassLoader(),arg1:new Class[]{Target.class},arg2:PHandler implements InvocationHandler。PHandler中含有我们想加入的业务逻辑,target为被代理对象,含有原来的业务逻辑。最后执行targetProxy.业务逻辑(业务参数);执行时先执行handler的begin逻辑,再执行target,最后执行handler的end逻辑。
以实战代码为例,希望能在运行时,根据业务传入情况替换Spring bean时,可按此思路实现
@Bean
@Primary
@Autowired
public ITargetService getTargetService(TargetServiceA aService, TargetServiceB bService) {
return (ITargetService) Proxy.newProxyInstance(
this.getClass().getClassLoader(),
new Class[]{ITargetService.class},
(proxy, method, args) -> {
BizTypeEnum bizType = getBizType(args);
switch(bizType) {
case A:
return aService;
case B:
default:
return bService;
}
}
)
}
// 根据参数,得出目前应该使用哪类bizType。
private BizTypeEnum getBizType(Object[] args) {
return Arrays.asStream(args)
.filter(Objects:nonNull)
.filter(arg -> BizTypeClass.isAssignableFrom(arg.getClass())) // 此处也可用arg instanceOf BizTypeClass替代
.map(arg -> (BizTypeClass)arg.getBizType())
.findAny.orElse(null);
}
// 也可以根据上下文直接判断等
private BizTypeEnum getBizType(Object[] args) {
return BizThreadLocal.get().getBizType();
}
Spring中的AOP实现,对有实现接口的类,背后使用的就是动态代理的实现。对没有实现接口的类直接更改二进制码(cglib)。
AOP的使用:
java中实现AOP,需要在Spring的.xml中增加xmlns=“http://.../aop” ,xsi:schemalocation中加schema/aop,schema/aop/spring-aop-2.5.xsd(可以在spring的samples中寻找例程,或者dist/resources寻找aop的.xsd)(在IDE的XML Catalog中增加后一个.xsd可以获得IDE的提示功能)。在.xml正文中增加<aop:aspectj-autoproxy/>,即可使用@AspectJ标签(程序会扫描@AspectJ标签并进行动态代理)。
AspectJ:AspectJ是一个专门用于产生动态代理功能的项目。Spring也使用了AspectJ进行AOP实现。使用AJ时要引入AspectJ的两个.jar包。
在类名前添加@Aspect标签(表示此类可以作为切面进行织入),并加入@Component(将此类交给Spring初始化,用于织入工作)。在想要在原逻辑前织入的业务逻辑(如beforeMethod())(其实也有将类,属性织入的,暂不讨论)上方加@Before标签(或任何Advice),即实现了将beforeMethod()织入原对象逻辑。
JoinPoint:
切入点,即在原逻辑中的哪些点需要织入切面逻辑;也即在从前往后的业务逻辑中插入切面时,在原逻辑中被织入的位置。
PointCut:
切入点的集合。
@Pointcut("execution(* com.company.someapp.service..(..))")
public void someMethod(){}
代表在com.company.someapp.service下的任何类的任何方法都是someMethod()的切入点。
someMethod可以是空实现,作为Advice的参数。在此之后可以在任何Advice的参数中写someMethod(),即可在此切入点集合中执行。
Advice:
代表在切面上的建议逻辑。如@Before,@After都是切入点上的建议。
Target:
被代理对象(被织入对象)。
Weave:
织入。
AOP支持的AspectJ语法: execution(public * *(..)):公开的任何返回值的任何方法
execution(* set*(..)):任何返回值的任何方法名为set打头的方法
execution(* com.comp.service..(..)):任何返回值的service包下的任何类的任何方法
execution(* com.comp.service...(..)):任何返回值的service包下及其子包(无限层)的任何类的任何方法
AOP自成语法: within(com.comp.service.*):
within(com.comp.service..*):
this(com.comp.service.SomeService):
target(com.comp.service.SomeService):
args(java.io.Serializable):
Advice类型: @before("execution()")或者Before(someMethod)[参见上PointCut解释]:
@AfterReturning:于目标方法正常返回后执行。
@AfterThrowing:于目标方法抛出异常后执行。
@After:即finally,不论异常或正常返回均执行。
@Around:环绕。被环绕修饰的方法public Object doBasicProfiling(方法名可换),参数为ProcedingJoinPoint pjp(不可换),throws Throwable。
在此方法中先写before织入逻辑,再写Object retVal = pjp.proceed();,再写after织入逻辑,最后 return retVal;。(可参责任链模式的视频)
Advice的参数可以是execution,也可以是someMethod[参见PointCut解释]