https://github.com/code4craft/tiny-spring
https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247484247&idx=1&sn=e228e29e344559e469ac3ecfa9715217&chksm=ebd74256dca0cb40059f3f627fc9450f916c1e1b39ba741842d91774f5bb7f518063e5acf5a0#rd
https://www.zhihu.com/question/23277575
依赖,可以粗暴地理解为import,如果代码中import了某个类,那这段代码就依赖了这个类。面向接口编程时,逻辑都是接口逻辑(例如接口IA,有方法doX,doY,接口逻辑例如是main中实例化了IA后,顺序执行了doX和doY),但具体实例化的对象是IA接口的实现(例如类CA实现了IA,重写了方法doX,doY)。如果不用工厂,直接new,那么main文件里面就必须import了CA,也就是main“依赖”了CA这个实现。而面向接口编程中,main应该跟CA解耦(就是不直接依赖CA,不会看到import CA)。工厂方法就是解决这种import CA的解决途径之一,简单工厂为例,原本main里面的IA a = new CA(),就变成了IA a = AFactory.getA(“CA”),并且getA的具体实现中,可以通过如果是字符串“CA”就new CA()返回了。这样子的话,main里面就不用import CA了(但是要import AFactory),也即是不“依赖”CA,与CA解耦了。依赖注入,就是把上面的工厂,获取CA对象的方式,变成反射(也还是根据字符串来生成对象,不过就不用简单工厂if-else那么粗暴了,多一个if又要改一遍工厂的实现,多累啊),根据配置来生成对象。不用import某个实际类,但是也把依赖(逻辑过程实际执行还是CA来做的)给注入(放到main中)了。(上述的main指代任意一个逻辑执行过程,不一定是main函数)
https://github.com/code4craft/tiny-spring
Spring中Bean实例的生成是由容器控制的,而不是由用户,因此Bean对象的创建要放在BeanFactory中。为了仿照Spring,因此抽象出FactoryBean接口,AbstractBeanFactory模板类。模板类中最重要的是protected doCreateBean()。
在注册的时候通过反射调用doCreateBean方法创建对象,并放入BeanDefinition包装类中。doCreateBean相当于是个动态工厂,根据string类型的全类名反射出一个Object对象。
public void registerBeanDefinition(String name,BeanDefinition beanDefinition){
Object bean = doCreateBean(beanDefinition);
beanDefinition.setBean(bean);
beanDefinitionMap.put(name,beanDefinition);
}
到这一步就实现了BeanFactory的实现类可以通过全类名创建一个对象。
public class BeanFactoryTest {
@Test
public void test(){
BeanFactory beanFactory = new AutowireCapableBeanFactory();
BeanDefinition beanDefinition = new BeanDefinition();//创建一个包装类
beanDefinition.setBeanClassName("beans.Car");//通过反射创建,要求必须有无参构造函数
beanFactory.registerBeanDefinition("audi",beanDefinition);//注册到hashmap中,注册之前先调用doCreateObject方法创建对象,实现了在Facoty中创建对象
System.out.println((Car)beanFactory.getBean("audi"));
}
}
在看上述代码,我们要传给factory什么?1.全类名,即beans.Car。2.实例化后的实例名称,即audi。这两项显然我们都能在配置的xml中获取,这在第四步中完成。其次,我们目前创建出的对象还是一个依靠无参构造函数创建的,因此内部成员变量均为null,所以下一步是对成员变量进行赋值。
这一步有两个类,PropertyValues和PropertyValue。PV类相当于是C++中的Pair
protected void applyPropertyValues(Object bean,BeanDefinition mbd) throws NoSuchFieldException, IllegalAccessException {
for(PropertyValue propertyValue:mbd.getPropertyValues().getPropertyValues()){
Field declaredField = bean.getClass().getDeclaredField(propertyValue.getName());
declaredField.setAccessible(true);
declaredField.set(bean,propertyValue.getValue());
}
}
怎么实现ref?
这个问题好解决。判断xml中是ref还是value,如果是value(本项目目前value如果是基本类型,只允许是String)则直接用PV(PropertyValue)封装,如果是ref,就用BeanReference{name,bean}封装一下然后再用PV封装。
private void processProperty(Element ele, BeanDefinition beanDefinition) {
NodeList propertyNode = ele.getElementsByTagName("property");
for (int i = 0; i < propertyNode.getLength(); i++) {
Node node = propertyNode.item(i);
if (node instanceof Element) {
Element propertyEle = (Element) node;
String name = propertyEle.getAttribute("name");
String value = propertyEle.getAttribute("value");
if (value != null && value.length() > 0) {
beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
} else {
String ref = propertyEle.getAttribute("ref");
if (ref == null || ref.length() == 0) {
throw new IllegalArgumentException("Configuration problem: element for property '"
+ name + "' must specify a ref or value");
}
BeanReference beanReference = new BeanReference(ref);
beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference));
}
}
}
}
在调用applyPropertyValues()方法——通过反射装填实例的成员变量时,如果该变量是BeanReference,则该变量有可能需要创建一下。
protected void applyPropertyValues(Object bean, BeanDefinition mbd) throws Exception {
for (PropertyValue propertyValue : mbd.getPropertyValues().getPropertyValues()) {
Field declaredField = bean.getClass().getDeclaredField(propertyValue.getName());
declaredField.setAccessible(true);
Object value = propertyValue.getValue();
if (value instanceof BeanReference) {
BeanReference beanReference = (BeanReference) value;
value = getBean(beanReference.getName());
}
declaredField.set(bean, value);
}
}
注意上述代码中的value=getBean(beanReference.getName())。实例的创建过程有可能就在此刻完成。这里需要明确的是下图:
读取xml后,所有的类信息都在XmlBeanDefinitionReader实例中,但是XmlBDFR中的beanDefinition们并没有创建实例,即空有类信息(className,PropertyValues),但是bean为null。此时,如果遇到A实例a的b字段ref C实例c,但是此刻C实例c还未初始化,在装配A实例a的b字段的时候,就会用getBean创建c。(为什么能创建c呢?因为在创建工厂后,紧接着的操作就是把xmlBDFR中的所有beanDefinition写入工厂的ConcurrentHashMap中,即工厂也有了全部的信息,因此可以创建c。)
BeanFactory beanFactory = new AutowireCapableBeanFactory();
for(Map.Entry<String,BeanDefinition> beanDefinitionEntry:xmlBeanDefinitionReader.getRegistry().entrySet()){
beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(),beanDefinitionEntry.getValue());
}
((AutowireCapableBeanFactory) beanFactory).preInstantiateSingletons();
通过getBean时创建实例的这种lazy-init方式,实现了不依靠xml中顺序。这样再创建实例的时候如果实例的依赖还没有创建,就先创建依赖。
所谓循环依赖是类似以下的情况
在doCreateBean中,创建完空的bean(空的bean表示空构造函数构造出的bean)后,就放入beanDefinition中,这样a ref b,b ref a时,a ref b因此b先创建并指向a,此时的a还不是完全体,但是引用已经连上了,然后创建好了b。然后b ref a的时候,a已经创建完毕。
public void refresh() throws Exception {
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
xmlBeanDefinitionReader.loadBeanDefinitions(configLocation);
for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) {
beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
}
}![](http://img.sonihr.com/6a51bce1-149f-4ad0-8d9b-2b4f33c6399b.jpg)
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml");
((ClassPathXmlApplicationContext) applicationContext).refresh();
System.out.println(applicationContext.getBean("car2"));
https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247483954&idx=1&sn=b34e385ed716edf6f58998ec329f9867&chksm=ebd74333dca0ca257a77c02ab458300ef982adff3cf37eb6d8d2f985f11df5cc07ef17f659d4#rd
静态代理模式
通过构造函数注入的方式,将被代理类B的实例b注入Proxy中,然后Proxy实现A接口a方法时,在调用b.a()之前之后都可以写自己的代理逻辑代码。
动态代理模式
将接口A的字节码文件+一个构造器,这个构造器继承自Proxy,就构成了代理类的基本字节码。Prxoy构造器中必然依赖InvocationHandler实例,这个InovocationHandler实例要重写invoke方法以实现1.Proxy中所有A接口方法全部使用handler.invoke。2.hanler.invoke()调用被代理实例的a(),并且可以在其中写代理逻辑。3.Proxy的a方法调用的invoke,则内部就代理a方法。
分解操作:
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//指定被代理类实例
Car target = new Car();
//指定类加载器和接口
Class carClazz = Proxy.getProxyClass(target.getClass().getClassLoader(),Drivebale.class);
//创建构造函数
Constructor constructor = carClazz.getConstructor(InvocationHandler.class);
//反射创建代理类实例
Drivebale car = (Drivebale) constructor.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前");
method.invoke(target,args);
System.out.println("后");
return null;
}
});
car.running();
}
一句话版本:
public static void main(String[] args) {
Car target = new Car();
Drivebale car = (Drivebale) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("这就是JDK动态代理的正确用法");
method.invoke(target,args);
System.out.println("结束");
return null;
}
});
car.running();
}
本质区别,静态代理是在运行期之前就编译好的class文件,动态代理是运行期中生成的class文件。
代理模式和装饰模式的区别
代理模式的应用
静态代理:Thread implements Runnable,Thread(Runnable target),thread.run{target.run}。我们只用关心Runnable的业务逻辑,而不用关系线程创建,销毁等具体的事情。
动态代理:MyBatis中的Mapper。
https://blog.csdn.net/xiaokang123456kao/article/details/76228684
SqlSession session = sqlSessionFactory.openSession();
//获取mapper接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//调用代理对象方法
User user = userMapper.findUserById(27);
比如UserMapper这个接口,如果要用静态代理,就必须手动写一个实现该接口的代理类,如果你有很多个接口,就要写很多个代理类,工作量很大。但是采用动态代理后,XXXMapperProxy通过反射实现XxxMapper接口内方法并创建构造函数,创建后在invoke中实现逻辑。
https://blog.csdn.net/javazejian/article/details/56267036
为什么要有AOP?
参数检查,日志,异常处理,事务开始和提交,这些都是重复代码,怎么解决呢?面向切面编程,将这些功能抽取出来,然后定义point cut(切入点),在point cut上进功能的weaving织入,从而形成一个aspect切面。
专属名词
join point:Spring中每个方法都可以是join point
point cut:我们想要切入的那些join point
advice:通知,即代理逻辑代码
aspect:point cut+advice等于一个切面
weaving:切面应用到目标函数的过程
https://zhuanlan.zhihu.com/p/24565766
Spring aop和Aspect不是一个东西
Aspect是一套独立的面向切面编程的实现方案,通过编译器实现静态织入.
Spring AOP基于动态代理设计模式的动态织入,基础技术为jdk动态代理和CGLIB技术,前者基于反射技术且只应用于接口,后者基于继承了用于类。
Spring AOP使用了Aspect的部分内容(主要是实现XML配置解析和类似 Aspectj 注解方式的时候,借用了 aspectjweaver.jar 中定义的一些annotation 和 class),但是并没有使用其编译器和织入器,可以认为是Aspect风格的,但是实现完全不同。
AOP Alliance 是AOP的接口标准,定义了 AOP 中的基础概念(Advice、CutPoint、Advisor等),目标是为各种AOP实现提供统一的接口,本身并不是一种 AOP 的实现。Spring AOP, GUICE等都采用了AOP Alliance中定义的接口,因而这些lib都需要依赖 aopalliance.jar。
这一步我们就是利用之前说到的动态代理模式,几乎一模一样的完成织入。想一下,我们实现动态代理要用Proxy.newInstance,我们可以封装一个动态代理类,就叫做JdkDynamicAopProxy implements InvocationHandler。由之前的动态代理知识可知,实现了InvocationHandler就必须实现invoke方法,那我们这样写:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// MethodInterceptor methodInterceptor = advised.getMethodInterceptor();
// return methodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(),method,args));
System.out.println("方法开始");
Object result = method.invoke(advised.getTargetSource().getTarget(),args);
System.out.println("方法结束");
return result;
}
这样就可以将所有代理方法前后打印两句话了。我们通过getProxy返回构造好的代理类:return Proxy(getClass.getClassLoader,new Class[]{target.getClass},this
。因为本类就是InvocationHandler的实现类,因此最后一个用this即可。
我们知道想成功代理一个实例需要2个要素1.被代理的实例2.被代理的接口。我们用AdvisedSupport进行封装,包括target、targetClass(其实应该是targetInterface)(前两个被封装进TargetSource,而TargetSource被封装进AdvisedSupport)、methodInterceptor.等一下,methodInterceptor是个什么吊参数?
聪明的小朋友已经发现了,按照我们这样写,功能只能是对被代理的方法前后加一句话而已,那有没有一种方式能让我们能定制对方法调用时进行的控制?有,就是MethodInterceptor,即方法拦截器类。
http://aopalliance.sourceforge.net/doc/org/aopalliance/intercept/MethodInterceptor.html
与上文采用的动态代理不同,我们可以通过配置拦截器来配置不同的代理逻辑。但是注意methodInterceptor.invoke方法中还有个methodInvovation,这个类用于调用我们的target的方法,因此这个类需要target实例,method和args。
所以其实啊MethodInvocation就是point cut,而MethodInterceptor就是advice,Invocation负责调用target方法即切点方法,Interceptor负责代理逻辑即advice。
这一步到此为止可以做到:1.写一个实现MethodInterceptor的实现类,实现增强功能。2.实现对接口方法的代理。
// 1. 设置被代理对象(Joinpoint)
AdvisedSupport advisedSupport = new AdvisedSupport();
TargetSource targetSource = new TargetSource(car,Driveable.class);
advisedSupport.setTargetSource(targetSource);
// 2. 设置拦截器(Advice)
TimerInterceptor timerInterceptor = new TimerInterceptor();
advisedSupport.setMethodInterceptor(timerInterceptor);
// 3. 创建代理(Proxy)
JdkDynamicAopProxy jdkDynamicAopProxy = new JdkDynamicAopProxy(advisedSupport);
Driveable carProxy = (Driveable)jdkDynamicAopProxy.getProxy();
// 4. 基于AOP的调用
carProxy.running();
给出一个AOP采用的动态代理方式的小demo
class ReflectMethodInvocation implements MethodInvocation{
private Method method;
private Object target;
private Object[] args;
public ReflectMethodInvocation(Method method, Object target, Object[] args) {
this.method = method;
this.target = target;
this.args = args;
}
@Override
public Method getMethod() {
return method;
}
@Override
public Object[] getArguments() {
return args;
}
@Override
public Object proceed() throws Throwable {
return method.invoke(target,args);
}
@Override
public Object getThis() {
return target;
}
@Override
public AccessibleObject getStaticPart() {
return method;
}
}
public class JdkAopNew {
public static void main(String[] args) {
Car car = new Car();
MethodInterceptor methodInterceptor = new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("拦截器方式动态代理前");
methodInvocation.proceed();
System.out.println("后");
return null;
}
};
Drivebale drivebale = (Drivebale) Proxy.newProxyInstance(car.getClass().getClassLoader(), car.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return methodInterceptor.invoke(new ReflectMethodInvocation(method,car,args));
}
});
drivebale.running();
}
}
第7步解决了怎么织入的问题,下面就是在哪里织入?Spring采用了AspectJ风格的标示性语句来表示在哪些位置进行织入,即哪些位置是point cut。类似下面的语句
。Spring可以对类和方法做插入,因此我们也要实现对类和方法表示point cut的功能。
先写出ClassFilter接口和MethodMathcer接口,望文生义的说前者是类过滤器,后者是方法匹配器。具体怎么匹配呢?就在我们的AspectJExpressionPointcut中。
AspectJExpressionPintcut中要做这样几件事1.获得String expression即AspectJ风格表达式2.创建PonitcutParser,即解析AspectJ风格表达式的解析器。3.expression被解析后就变成了pointcutExpression。即expression是输入,pointcutParser是输出,pointcutParser是解析器,将输入解析成输出。这个解析器怎么创建呢?直接new一个行不行啊?不行。正确的创建方式为:pointcutParser = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingContextClassloaderForResolution(supportedPrimitives);
后面的supportedPrimitives指的是执行的AspectJ语言风格的关键字,是一个set。那请问支持哪些关键字呢?去org.aspectj.weaver.tools包内的PointPrimitive就可以看奥。
可以看出pointcutExpression是对expression的封装。
pointcutExpression是创建好了,但是有什么用呢?这个类可以用于匹配方法和类。
//匹配类
pointcutExpression.couldMatchJoinPointsInType(targetClass);
//匹配方法
ShadowMatch shadowMatch = pointcutExpression.matchesMethodExecution(method);
所以其实AspectJ包已经帮你做好了解析和匹配的事儿,只不过你不会用他的编译器,你用动态代理的方式实现了织入。
AspectJExpressionPointcutAdvisor封装了pointcut和advice,实现了一个完整的切面,切面=切点+advice。p.s.advice就是代理逻辑代码。
第7和第8步我们已经完成了AOP的point识别和识别后的织入,但是两个功能没有整合,同时也没有和Spring的IOC整合起来。目的是为了,IOC给我们的容器已经不再是我们自己写的实例,而是被织入了advice的实例——如果该类在pointcut则返回new JdkDynamicAopProxy,否则返回bean。
public Object postProcessAfterInitialization(Object bean, String beanName) throws Exception {
if (bean instanceof AspectJExpressionPointcutAdvisor) {
return bean;
}
if (bean instanceof MethodInterceptor) {
return bean;
}
List<AspectJExpressionPointcutAdvisor> advisors = beanFactory
.getBeansForType(AspectJExpressionPointcutAdvisor.class);
for (AspectJExpressionPointcutAdvisor advisor : advisors) {
if (advisor.getPointcut().getClassFilter().matches(bean.getClass())) {
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice());
advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher());
TargetSource targetSource = new TargetSource(bean, bean.getClass().getInterfaces());
advisedSupport.setTargetSource(targetSource);
return new JdkDynamicAopProxy(advisedSupport).getProxy();
}
}
return bean;
}
第一幕:和9.1非常类似的,仅有标红出不同。因为AspectJAwareAdvisorAutoProxyCreator implements BBP,BeanFactoryWare,因此不同仅仅是,因为实现了BeanFactoryAware接口,因此调用setFactory方法。这一步的目的是为了是的AspectAwareAdvisorAutoProxyCreator中具有beanFactory,方便从中获取AspectJExpressionPointcutAdvisor.class类的实例。
第二幕:这一幕是目前为止最复杂也最重量级的。相当于把第9.1步和7,8两步合起来了,归纳如下:
第一幕创建BBP实例,以编译对所有非BBP实例进行before、after操作。第二幕通过判断该类是否为point cut从而确认返回原实例还是代理实例,到第二幕已经将实例创建完毕。第三幕指的是,当实例调用接口方法时,如果该方法是pointcut,则会调用如下流程:
invocationHandler.invoke(proxy,method,args)调用methodInterceptor.invoke(methodInvocation),methodInterceptor内部进行1.代理编码的实现2.函数参数methodInvocation调用proceed,从而执行被代理实例的method方法。因为methodInvocation要可以调用被代理实例的method,因此methodInvocation当你想要实现这个接口时,必须要指定被代理实例target,被代理实例的方法method和参数args。
https://github.com/code4craft/tiny-spring/issues/10
问题是:在进行测试的时候,发现调用非BPP实例的接口方法时,并没有被代理。
来自Github原项目的Issues中:
https://github.com/code4craft/tiny-spring/issues/17
问题是:如果a ref b,b ref a,且顺序也是这样。
这个问题很奇怪。我们看看在实际Spring中的实验效果:
这些类望文知意我就不解释了。
实验过程:
可以看出,单独的calculator和book都可以被正常代理。当然,在TinySpring中也是符合的。
在Spring中,接口实现类根本不用考虑这个问题,因为根本无法运行。逻辑在于,你获得的A和B本质上都是代理类,代理类只实现了代理接口,因此无法强转为某一个具体的实现类。所以A.B.b()和B.A.a()从本质上根本就不会强转成功。
实验三:
怎么样才能正确进行这个实验呢?上一个回答说到,不能进行实验的本质是因为只能代理接口而不能代理类,所以Spring通过Cglib实现类代理。
通过proxy-target-class标记为true后,强制开启cglib,此时再看实验结果。
成功!
小结论实验三证明,强制开启Chlib后,可以进行本实验,且Spring解决了循环依赖的问题。那原作者的tiny spring是不是进行第10步之后,就解决了呢?答案是没有,因为Cglib只是让我们的实验可以正常进行,不代表能解决这个问题。Spring是通过三级缓存解决的。
上图是第10步做完后的效果,发现问题还未解决。
CGlib的原理是通过对字节码的操作,可以动态的生成一个目标实例类的子类,这个子类和目标实例的子类相同的基础上,还增加了代理代码或者叫advice。代理类 = 被代理类+增强逻辑
class Student{
private String name = "zhang san";
public String getName() {
System.out.println(name);
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class CglibMthodTwo implements MethodInterceptor {
public Object getProxy(Class clazz){
Enhancer en = new Enhancer();
en.setSuperclass(clazz);
en.setCallback(this);
Object proxy = en.create();
return proxy;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("前");
Object res = methodProxy.invokeSuper(o,objects);
System.out.println("后");
return res;
}
public static void main(String[] args) {
CglibMthodTwo cglibMthodTwo = new CglibMthodTwo();
((Student)cglibMthodTwo.getProxy(Student.class)).getName();
}
}
public class JdkDynamicAopProxy extends AbstractAopProxy implements InvocationHandler {
public JdkDynamicAopProxy(AdvisedSupport advised) {
super(advised);
}
@Override
public Object getProxy() {
return Proxy.newProxyInstance(getClass().getClassLoader(), advised.getTargetSource().getInterfaces(), this);
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
MethodInterceptor methodInterceptor = advised.getMethodInterceptor();
Object res = null;
if (advised.getMethodMatcher() != null
&& advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
res = methodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(),
method, args));
} else {
res = method.invoke(advised.getTargetSource().getTarget(), args);
}
return res;
}
}
与JDK动态代理的区别
为什么说运行方法上的差异很重要呢,因为这会导致步骤9的代码不可复用。因为我们原来写的都是JDK代理类实例的那一套代码,如果用CGlib的话,就无法通过注入org.aopalliance.intercept.MethodInterceptor的方式实现增强,而是注入cglib的MethodInterceptor,通过setCallback可以设置不同methodInterceptor。有没有一种办法,让我们配置一种org.aopalliance.intercept.MethodInterceptor,在CGlib的情况下也可以调用它呢?
有啊,只要我们在cglib的methodInterceptor接口实现的intercept方法中调用org.aopalliance.intercept.MethodInterceptor不就好了。
private static class DynamicAdvisedInterceptor implements MethodInterceptor {
private AdvisedSupport advised;
private org.aopalliance.intercept.MethodInterceptor delegateMethodInterceptor;
private DynamicAdvisedInterceptor(AdvisedSupport advised) {
this.advised = advised;
this.delegateMethodInterceptor = advised.getMethodInterceptor();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (advised.getMethodMatcher() == null
|| advised.getMethodMatcher().matches(method, advised.getTargetSource().getTargetClass())) {
// return delegateMethodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(),method, args));
return delegateMethodInterceptor.invoke(new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, args, proxy));
}
// return method.invoke(advised.getTargetSource().getTarget(),args);可以这么写,
return new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, args, proxy).proceed();
}
}
现在只剩下一个疑问了,因为么要写一个ReflectMothodInvocation的子类?因为intercept有4个入参,所以我们交给下一步处理的时候也要有4个入参,相当于增强了一下功能,当然你这边不改也没问题,就当做JDK那个版本就行。
废话少说,先看结果
@Test
public void testXuhuanyilai() throws Exception {
// --------- helloWorldService without AOP
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml");
Car car = (Car) applicationContext.getBean("car");
car.getAddress().living();
Address address = (Address)applicationContext.getBean("address");
address.getCar().running();
}
//测试结果
Invocation of Method setCar start!
Invocation of Method setCar end! takes 123111 nanoseconds.
Invocation of Method setAddress start!
Invocation of Method setAddress end! takes 38666 nanoseconds.
Invocation of Method running start!
car is running
Invocation of Method running end! takes 45777 nanoseconds.
Invocation of Method living start!
address is living
Invocation of Method living end! takes 56000 nanoseconds.
实验结果表示,我已经解决了第9.3步中说到的AOP情况下,循环依赖导致a ref b, b ref a时,创建实例时,b.a指向的是空实例a,而不是代理实例a。
解决方法。
三层缓存。
protected Map secondCache = new HashMap();
protected Map thirdCache = new HashMap<>();
protected Map firstCache = new HashMap<>();
thirdCache是当空构造函数创建一个实例时,就放入其中。
protected Object doCreateBean(String name,BeanDefinition beanDefinition) throws Exception {
Object bean = createBeanInstance(beanDefinition);
thirdCache.put(name,bean);//thirdCache中放置的全是空构造方法构造出的实例
beanDefinition.setBean(bean);
applyPropertyValues(bean,beanDefinition);
return bean;
}
a ref b, b ref a情况下,在b创建时,a还只是空构造实例,因此用secondCache去保存所有field中指向空实例的那些实例,即保存b。
for(PropertyValue propertyValue:mbd.getPropertyValues().getPropertyValues()){
Object value = propertyValue.getValue();
if(value instanceof BeanReference){//如果是ref,就创建这个ref
BeanReference beanReference = (BeanReference)value;
value = getBean(beanReference.getName());
String refName = beanReference.getName();
if(thirdCache.containsKey(refName)&&!firstCache.containsKey(refName)){//说明当前是循环依赖状态
secondCache.put(beanReference.getName(),bean);//标注a ref b,b ref a中,b是后被循环引用的
}
}
firstCache用于保存所有最终被生成的实例.
initializeBean():
if(thirdCache.containsKey(name)){//空构造实例如果被AOP成代理实例,则放入三级缓存,说明已经构建完毕
firstCache.put(name,bean);
}
因此,当执行完方法beanFactory.preInstantiateSingletons();后,thirdCache保存了所有空构造实例及名称,secondCache保存了所有可能需要重新设置ref的实例及名称,first保存了所有最终生成的实例和名称。在firstcache与third中,必然存放了所有的bean,在second中只存放因循环依赖所以创建时ref了不完整对象的那些。在创建了所有实力后,通过checkoutAll方法对secondCache中的实例进行重置依赖。
protected void onRefresh() throws Exception{
beanFactory.preInstantiateSingletons();
checkoutAll();
}
private void checkoutAll(){
Map<String,Object> secondCache = beanFactory.getSecondCache();
Map<String,BeanDefinition> beanDefinitionMap = beanFactory.getBeanDefinitionMap();
for(Map.Entry<String,Object> entry:secondCache.entrySet()){
String invokeBeanName = entry.getKey();
BeanDefinition beanDefinition = beanDefinitionMap.get(invokeBeanName);
try {
resetReference(invokeBeanName,beanDefinition);
} catch (Exception e) {
e.printStackTrace();
}
}
}
//重置依赖,这边用到了动态类型转换。因为原类型的setter在代理类中已经无法使用了。
private void resetReference(String invokeBeanName,BeanDefinition beanDefinition) throws Exception {
Map<String,Object> thirdCache = beanFactory.getThirdCache();
Map<String,Object> secondCache = beanFactory.getSecondCache();
Map<String,Object> firstCache = beanFactory.getFirstCache();
Map<String,BeanDefinition> beanDefinitionMap = beanFactory.getBeanDefinitionMap();
for (PropertyValue propertyValue : beanDefinition.getPropertyValues().getPropertyValues()) {
String refName = propertyValue.getName();
if (firstCache.containsKey(refName)) {//如果是ref,就创建这个ref
Object exceptedValue = firstCache.get(refName);
Object invokeBean = beanDefinition.getBean();
Object realClassInvokeBean = thirdCache.get(invokeBeanName);
Object realClassRefBean = thirdCache.get(refName);
try{
Method declaredMethod = realClassInvokeBean.getClass().getDeclaredMethod("set" + propertyValue.getName().substring(0, 1).toUpperCase()
+ propertyValue.getName().substring(1), realClassRefBean.getClass());
declaredMethod.setAccessible(true);
declaredMethod.invoke((realClassInvokeBean.getClass().cast(invokeBean)), (realClassRefBean.getClass().cast(exceptedValue)));
}catch (NoSuchMethodException e){
Field declaredField = realClassInvokeBean.getClass().getDeclaredField(propertyValue.getName());
System.out.println(declaredField);
declaredField.setAccessible(true);
declaredField.set((realClassInvokeBean.getClass().cast(invokeBean)), (realClassRefBean.getClass().cast(exceptedValue)));
}
}
}
}
正如在9.3中说的那样,只有在开启全局cglib的情况下才可以完成本实验,如果开启jdk代理模式或者jdk代理+cglib都不会解决本bug。