diff --git a/README.md b/README.md index 97fb40f..7e09b2b 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ tiny-spring `tiny-spring`是为了学习Spring的而开发的,可以认为是一个Spring的精简版。Spring的代码很多,层次复杂,阅读起来费劲。我尝试从使用功能的角度出发,参考Spring的实现,一步一步构建,最终完成一个精简版的Spring。有人把程序员与画家做比较,画家有门基本功叫临摹,tiny-spring可以算是一个程序的临摹版本-从自己的需求出发,进行程序设计,同时对著名项目进行参考。 +[点此查看](https://www.zybuluo.com/dugu9sword/note/382745)对本项目的类文件结构和逻辑的分析。 (by @dugu9sword) + ## 功能 1. 支持singleton类型的bean,包括初始化、属性注入、以及依赖bean注入。 @@ -18,8 +20,15 @@ tiny-spring `tiny-spring`是逐步进行构建的,里程碑版本我都使用了git tag来管理。例如,最开始的tag是`step-1-container-register-and-get`,那么可以使用 git checkout step-1-container-register-and-get - + 来获得这一版本。版本历史见[`changelog.md`](https://github.com/code4craft/tiny-spring/blob/master/changelog.md)。 [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/code4craft/tiny-spring/trend.png)](https://bitdeli.com/free "Bitdeli Badge") +## 下面是推广 + +如果觉得代码理解有难度的,可以报名@方老司 的视频教程: + +[60分钟徒手撸出Spring框架:土法造炮篇](https://segmentfault.com/l/1500000013061317?d=be83d672744f2f15b77bb40795505e4b) + +[60分钟徒手撸出Spring框架:高仿版](https://segmentfault.com/l/1500000013110630?d=a09ac8198372f552dc68c572b2b38664) diff --git a/src+/main/java/com/ysj/tinySpring/BeanReference.java b/src+/main/java/com/ysj/tinySpring/BeanReference.java new file mode 100644 index 0000000..5206d7a --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/BeanReference.java @@ -0,0 +1,34 @@ +package com.ysj.tinySpring; + +/** + * 用于代表property标签的ref属性里的对象 + * + */ +public class BeanReference { + + public String name; + + public Object bean; + + public BeanReference(String name){ + this.name = name; + } + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Object getBean() { + return bean; + } + + public void setBean(Object bean) { + this.bean = bean; + } + +} diff --git a/src+/main/java/com/ysj/tinySpring/aop/AbstractAopProxy.java b/src+/main/java/com/ysj/tinySpring/aop/AbstractAopProxy.java new file mode 100644 index 0000000..8076f2e --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/aop/AbstractAopProxy.java @@ -0,0 +1,16 @@ +package com.ysj.tinySpring.aop; + +/** + * 继承了AopProxy接口,有获取代理对象的能力 + * 同时继承此接口有AdvisedSupport的支持 + * + */ +public abstract class AbstractAopProxy implements AopProxy{ + + protected AdvisedSupport advised; + + public AbstractAopProxy(AdvisedSupport advised) { + this.advised = advised; + } + +} diff --git a/src+/main/java/com/ysj/tinySpring/aop/AdvisedSupport.java b/src+/main/java/com/ysj/tinySpring/aop/AdvisedSupport.java new file mode 100644 index 0000000..20d2722 --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/aop/AdvisedSupport.java @@ -0,0 +1,48 @@ +package com.ysj.tinySpring.aop; + +import org.aopalliance.intercept.MethodInterceptor; + +/** + * AdvisedSupport封装了TargetSource, MethodInterceptor和MethodMatcher + * + */ +public class AdvisedSupport { + + // 要拦截的对象 + private TargetSource targetSource; + + /** + * 方法拦截器 + * Spring的AOP只支持方法级别的调用,所以其实在AopProxy里,我们只需要将MethodInterceptor放入对象的方法调用 + */ + private MethodInterceptor methodInterceptor; + + // 方法匹配器,判断是否是需要拦截的方法 + private MethodMatcher methodMatcher; + + + + public TargetSource getTargetSource() { + return targetSource; + } + + public void setTargetSource(TargetSource targetSource) { + this.targetSource = targetSource; + } + + public MethodInterceptor getMethodInterceptor() { + return methodInterceptor; + } + + public void setMethodInterceptor(MethodInterceptor methodInterceptor) { + this.methodInterceptor = methodInterceptor; + } + + public MethodMatcher getMethodMatcher() { + return methodMatcher; + } + + public void setMethodMatcher(MethodMatcher methodMatcher) { + this.methodMatcher = methodMatcher; + } +} diff --git a/src+/main/java/com/ysj/tinySpring/aop/Advisor.java b/src+/main/java/com/ysj/tinySpring/aop/Advisor.java new file mode 100644 index 0000000..af29ced --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/aop/Advisor.java @@ -0,0 +1,18 @@ +package com.ysj.tinySpring.aop; + +import org.aopalliance.aop.Advice; + +/** + * 通知器 + * 用于实现 具体的方法拦截,需要使用者编写,也就对应了 Spring 中的前置通知、后置通知、环切通知等。 + * + */ +public interface Advisor { + + /** + * 获取通知器(方法拦截器) + * @return + */ + Advice getAdvice(); + +} diff --git a/src+/main/java/com/ysj/tinySpring/aop/AopProxy.java b/src+/main/java/com/ysj/tinySpring/aop/AopProxy.java new file mode 100644 index 0000000..6748b6c --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/aop/AopProxy.java @@ -0,0 +1,16 @@ +package com.ysj.tinySpring.aop; + +/** + * AopProxy是个标志型接口 + * 暴露获取aop代理对象方法的接口 + * + */ +public interface AopProxy { + + /** + * 获取代理对象 + * @return + */ + Object getProxy(); + +} diff --git a/src+/main/java/com/ysj/tinySpring/aop/AspectJAroundAdvice.java b/src+/main/java/com/ysj/tinySpring/aop/AspectJAroundAdvice.java new file mode 100644 index 0000000..07de29c --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/aop/AspectJAroundAdvice.java @@ -0,0 +1,52 @@ +package com.ysj.tinySpring.aop; + +import java.lang.reflect.Method; + +import org.aopalliance.aop.Advice; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import com.ysj.tinySpring.beans.factory.BeanFactory; + +/** + * 环绕通知 + * 用AspectJ表达式匹配 + * + */ +public class AspectJAroundAdvice implements Advice, MethodInterceptor { + + private BeanFactory beanFactory; + + private Method aspectJAdviceMethod; + + private String aspectInstanceName; + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + return aspectJAdviceMethod.invoke(beanFactory.getBean(aspectInstanceName), invocation); + } + + public BeanFactory getBeanFactory() { + return beanFactory; + } + + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + public Method getAspectJAdviceMethod() { + return aspectJAdviceMethod; + } + + public void setAspectJAdviceMethod(Method aspectJAdviceMethod) { + this.aspectJAdviceMethod = aspectJAdviceMethod; + } + + public String getAspectInstanceName() { + return aspectInstanceName; + } + + public void setAspectInstanceName(String aspectInstanceName) { + this.aspectInstanceName = aspectInstanceName; + } +} diff --git a/src+/main/java/com/ysj/tinySpring/aop/AspectJAwareAdvisorAutoProxyCreator.java b/src+/main/java/com/ysj/tinySpring/aop/AspectJAwareAdvisorAutoProxyCreator.java new file mode 100644 index 0000000..160eb43 --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/aop/AspectJAwareAdvisorAutoProxyCreator.java @@ -0,0 +1,70 @@ +package com.ysj.tinySpring.aop; + +import java.util.List; + +import org.aopalliance.intercept.MethodInterceptor; + +import com.ysj.tinySpring.beans.BeanPostProcessor; +import com.ysj.tinySpring.beans.factory.AbstractBeanFactory; +import com.ysj.tinySpring.beans.factory.BeanFactory; + +/** + * 实现了BeanFactoryAware接口:这个接口提供了对 BeanFactory 的感知,这样,尽管它是容器中的一个 Bean,却 + * 可以获取容器 的引用,进而获取容器中所有的切点对象,决定对哪些对象的哪些方法进行代理。解决了 为哪些对象 + * 提供 AOP 的植入 的问题。 + * + */ +public class AspectJAwareAdvisorAutoProxyCreator implements BeanPostProcessor, BeanFactoryAware { + + private AbstractBeanFactory beanFactory; + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws Exception { + return bean; + } + + /** + * 可以取看看AbstractBeanFactory的getBean()的实现 + * bean实例化后要进行初始化操作,会经过这个方法满足条件则生成相关的代理类并返回 + */ + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws Exception { + // 如果是切点通知器,则直接返回 + if (bean instanceof AspectJExpressionPointcutAdvisor) { + return bean; + } + // 如果bean是方法拦截器,则直接返回 + if (bean instanceof MethodInterceptor) { + return bean; + } + // 通过getBeansForType方法加载BeanFactory 中所有的 PointcutAdvisor(保证了 PointcutAdvisor 的实例化顺序优于普通 Bean。) + // AspectJ方式实现织入,这里它会扫描所有Pointcut,并对bean做织入。 + List advisors = beanFactory + .getBeansForType(AspectJExpressionPointcutAdvisor.class); + for (AspectJExpressionPointcutAdvisor advisor : advisors) { + // 匹配要拦截的类 + // 使用AspectJExpressionPointcut的matches匹配器,判断当前对象是不是要拦截的类的对象。 + if (advisor.getPointcut().getClassFilter().matches(bean.getClass())) { + // ProxyFactory继承了AdvisedSupport,所以内部封装了TargetSource和MethodInterceptor的元数据对象 + ProxyFactory advisedSupport = new ProxyFactory(); + // 设置切点的方法拦截器 + advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice()); + // 设置切点的方法匹配器 + // 利用AspectJ表达式进行方法匹配 + // AspectJExpressionPointcutAdvisor里的AspectJExpressionPointcut的getMethodMatcher()方法 + advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher()); + // 是要拦截的类, 生成一个 TargetSource(要拦截的对象和其类型)(被代理对象) + TargetSource targetSource = new TargetSource(bean, bean.getClass(), bean.getClass().getInterfaces()); + advisedSupport.setTargetSource(targetSource); + // 交给实现了 AopProxy接口的getProxy方法的ProxyFactory去生成代理对象 + return advisedSupport.getProxy(); + } + } + return bean; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws Exception { + this.beanFactory = (AbstractBeanFactory) beanFactory; + } +} diff --git a/src+/main/java/com/ysj/tinySpring/aop/AspectJExpressionPointcut.java b/src+/main/java/com/ysj/tinySpring/aop/AspectJExpressionPointcut.java new file mode 100644 index 0000000..f5f2042 --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/aop/AspectJExpressionPointcut.java @@ -0,0 +1,91 @@ +package com.ysj.tinySpring.aop; + +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Set; + +import org.aspectj.weaver.tools.PointcutExpression; +import org.aspectj.weaver.tools.PointcutParser; +import org.aspectj.weaver.tools.PointcutPrimitive; +import org.aspectj.weaver.tools.ShadowMatch; + +/** + * 通过AspectJ表达式识别切点 + * + */ +public class AspectJExpressionPointcut implements Pointcut, ClassFilter, MethodMatcher { + + private PointcutParser pointcutParser; + + private String expression; + + private PointcutExpression pointcutExpression; + + private static final Set DEFAULT_SUPPORTED_PRIMITIVES = new HashSet(); + + static { + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET); + } + + public AspectJExpressionPointcut() { + this(DEFAULT_SUPPORTED_PRIMITIVES); + } + + public AspectJExpressionPointcut(Set supportedPrimitives) { + pointcutParser = PointcutParser + .getPointcutParserSupportingSpecifiedPrimitivesAndUsingContextClassloaderForResolution(supportedPrimitives); + } + + protected void checkReadyToMatch() { + if (pointcutExpression == null) { + pointcutExpression = buildPointcutExpression(); + } + } + + private PointcutExpression buildPointcutExpression() { + return pointcutParser.parsePointcutExpression(expression); + } + + public void setExpression(String expression) { + this.expression = expression; + } + + @Override + public ClassFilter getClassFilter() { + return this; + } + + @Override + public MethodMatcher getMethodMatcher() { + return this; + } + + @Override + public boolean matches(Class targetClass) { + checkReadyToMatch(); + return pointcutExpression.couldMatchJoinPointsInType(targetClass); + } + + @Override + public boolean matches(Method method, Class targetClass) { + checkReadyToMatch(); + ShadowMatch shadowMatch = pointcutExpression.matchesMethodExecution(method); + if (shadowMatch.alwaysMatches()) { + return true; + } else if (shadowMatch.neverMatches()) { + return false; + } + // TODO:其他情况不判断了!见org.springframework.aop.aspectj.RuntimeTestWalker + return false; + } + +} diff --git a/src+/main/java/com/ysj/tinySpring/aop/AspectJExpressionPointcutAdvisor.java b/src+/main/java/com/ysj/tinySpring/aop/AspectJExpressionPointcutAdvisor.java new file mode 100644 index 0000000..2f8a252 --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/aop/AspectJExpressionPointcutAdvisor.java @@ -0,0 +1,42 @@ +package com.ysj.tinySpring.aop; + +import org.aopalliance.aop.Advice; + +/** + * AspectJ表达式切点通知器 + * + */ +public class AspectJExpressionPointcutAdvisor implements PointcutAdvisor { + + /** + * AspectJ表达式切点匹配器 + * AspectJ表达式匹配的切点,默认有初始化。也默认有了MethodMatcher(AspectJExpressionPointcut实现了MethodMatcher接口) + */ + private AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); + + /** + * 方法拦截器 + * 这个要用户自己去xml文件里配置方法拦截器(MethodInterceptor继承了Advice接口), + * 在AspectJAwareAdvisorAutoProxyCreator设置代理对象的方法拦截器时将Advispr强转为MethodInterceptor + */ + private Advice advice; + + public void setAdvice(Advice advice) { + this.advice = advice; + } + + public void setExpression(String expression) { + this.pointcut.setExpression(expression); + } + + @Override + public Advice getAdvice() { + return advice; + } + + @Override + public Pointcut getPointcut() { + return pointcut; + } + +} diff --git a/src+/main/java/com/ysj/tinySpring/aop/BeanFactoryAware.java b/src+/main/java/com/ysj/tinySpring/aop/BeanFactoryAware.java new file mode 100644 index 0000000..505908a --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/aop/BeanFactoryAware.java @@ -0,0 +1,17 @@ +package com.ysj.tinySpring.aop; + +import com.ysj.tinySpring.beans.factory.BeanFactory; + +/** + * 实现该接口则有操作beanFactory的能力 + * + * 容器的引用传入到 Bean 中去,这样,Bean 将获取容器的引用,获取对容器操作的权限,也就允许了 编写 + * 扩展 IoC 容器的功能的 Bean。 + * 例如:获取容器中所有的 切点对象,决定对哪些对象的哪些方法进行代理。解决了 为哪些对象提供 AOP 的植入 的问题。 + * + */ +public interface BeanFactoryAware { + + void setBeanFactory(BeanFactory beanFactory) throws Exception; + +} diff --git a/src+/main/java/com/ysj/tinySpring/aop/Cglib2AopProxy.java b/src+/main/java/com/ysj/tinySpring/aop/Cglib2AopProxy.java new file mode 100644 index 0000000..980d2d2 --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/aop/Cglib2AopProxy.java @@ -0,0 +1,94 @@ +package com.ysj.tinySpring.aop; + +import java.lang.reflect.Method; + +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; + +/** + * cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但 + * 因为采用的是继承, 所以不能对final修饰的类进行代理。 + * + */ +public class Cglib2AopProxy extends AbstractAopProxy { + + public Cglib2AopProxy(AdvisedSupport advised) { + super(advised); + } + + @Override + public Object getProxy() { + Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(advised.getTargetSource().getTargetClass()); + enhancer.setInterfaces(advised.getTargetSource().getInterfaces()); + /** Enhancer.java中setCallback方法源码 + * public void setCallback(final Callback callback) { + * setCallbacks(new Callback[]{ callback }); + * } + */ + // 设置回调方法 + enhancer.setCallback(new DynamicAdvisedInterceptor(advised)); + Object enhanced = enhancer.create(); + return enhanced; + } + + /** 注意该类实现的是net.sf.cglib.proxy.MethodInterceptor,不是aopalliance的MethodInterceptor. + * net.sf.cglib.proxy.MethodInterceptor.java: + + * public interface MethodInterceptor extends Callback{ + + // All generated proxied methods call this method instead of the original method. + // The original method may either be invoked by normal reflection using the Method object, + // or by using the MethodProxy (faster). + public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, + MethodProxy proxy) throws Throwable; + + * } + * + */ + 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 { + // 如果advised.getMethodMatcher()为空(编程式的使用aop,例如:Cglib2AopProxyTest.java),拦截该类的所有方法 + // 如果有方法匹配器(声明式的在xml文件里配置了AOP)并且匹配方法成功就拦截指定的方法 + if (advised.getMethodMatcher() == null + || advised.getMethodMatcher().matches(method, advised.getTargetSource().getTargetClass())) { + // delegateMethodInterceptor通过advised.getMethodInterceptor()得到用户写的方法拦截器 + // 返回去调用用户写的拦截器的invoke方法(用户根据需要在调用proceed方法前后添加相应行为,例如:TimerInterceptor.java) + return delegateMethodInterceptor.invoke(new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, args, proxy)); + } + // 有AspectJ表达式,但没有匹配该方法,返回通过methodProxy调用原始对象的该方法 + return new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, args, proxy).proceed(); + } + } + + private static class CglibMethodInvocation extends ReflectiveMethodInvocation { + // 方法代理 + private final MethodProxy methodProxy; + + public CglibMethodInvocation(Object target, Method method, Object[] args, MethodProxy methodProxy) { + super(target, method, args); + this.methodProxy = methodProxy; + } + + // 通过methodProxy调用原始对象的方法 + @Override + public Object proceed() throws Throwable { + return this.methodProxy.invoke(this.target, this.arguments); + } + } + +} diff --git a/src+/main/java/com/ysj/tinySpring/aop/ClassFilter.java b/src+/main/java/com/ysj/tinySpring/aop/ClassFilter.java new file mode 100644 index 0000000..11c0e88 --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/aop/ClassFilter.java @@ -0,0 +1,16 @@ +package com.ysj.tinySpring.aop; + +/** + * 类匹配器 + * + */ +public interface ClassFilter { + + /** + * 用于匹配targetClass是否是要拦截的类 + * @param targetClass + * @return + */ + boolean matches(Class targetClass); + +} diff --git a/src+/main/java/com/ysj/tinySpring/aop/JdkDynamicAopProxy.java b/src+/main/java/com/ysj/tinySpring/aop/JdkDynamicAopProxy.java new file mode 100644 index 0000000..b3be1ea --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/aop/JdkDynamicAopProxy.java @@ -0,0 +1,48 @@ +package com.ysj.tinySpring.aop; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import org.aopalliance.intercept.MethodInterceptor; + +/** + * 一个基于JDK的动态代理 + * 只能针对实现了接口的类生成代理。于是我们就有了基本的织入功能。 + * 注意:实现了InvocationHandler接口,可以通过重写invoke方法进行控制访问 + * + */ +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(Object proxy, Method method, Object[] args) throws Throwable { + // 从AdvisedSupport里获取方法拦截器 + MethodInterceptor methodInterceptor = advised.getMethodInterceptor(); + // 如果方法匹配器存在,且匹配该对象的该方法匹配成功,则调用用户提供的方法拦截器的invoke方法 + if (advised.getMethodMatcher() != null + && advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) { + return methodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(), + method, args)); + } else { + // 否则的话还是调用原对象的相关方法 + return method.invoke(advised.getTargetSource().getTarget(), args); + } + } +} diff --git a/src+/main/java/com/ysj/tinySpring/aop/MethodMatcher.java b/src+/main/java/com/ysj/tinySpring/aop/MethodMatcher.java new file mode 100644 index 0000000..d2d7ed8 --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/aop/MethodMatcher.java @@ -0,0 +1,19 @@ +package com.ysj.tinySpring.aop; + +import java.lang.reflect.Method; + +/** + * 方法匹配器 + * + */ +public interface MethodMatcher { + + /** + * 匹配该方法是否是要拦截的方法 + * @param method + * @param targetClass + * @return + */ + boolean matches(Method method, Class targetClass); + +} diff --git a/src+/main/java/com/ysj/tinySpring/aop/Pointcut.java b/src+/main/java/com/ysj/tinySpring/aop/Pointcut.java new file mode 100644 index 0000000..537a06b --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/aop/Pointcut.java @@ -0,0 +1,25 @@ +package com.ysj.tinySpring.aop; + + +/** + * 切点, 确定是对什么类的什么方法进行AOP(就是确定在哪切) + * + */ +public interface Pointcut { + + /** + * 获取 ClassFilter 对象 + * 类名匹配(用于 筛选要代理的目标对象) + * @return + */ + ClassFilter getClassFilter(); + + + /** + * 获取一个 MethodMatcher 对象 + * 方法名匹配 + * @return + */ + MethodMatcher getMethodMatcher(); + +} diff --git a/src+/main/java/com/ysj/tinySpring/aop/PointcutAdvisor.java b/src+/main/java/com/ysj/tinySpring/aop/PointcutAdvisor.java new file mode 100644 index 0000000..5826617 --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/aop/PointcutAdvisor.java @@ -0,0 +1,16 @@ +package com.ysj.tinySpring.aop; + + +/** + * 切点通知器 + * + */ +public interface PointcutAdvisor extends Advisor{ + + /** + * 获得切点 + * @return + */ + Pointcut getPointcut(); + +} diff --git a/src+/main/java/com/ysj/tinySpring/aop/ProxyFactory.java b/src+/main/java/com/ysj/tinySpring/aop/ProxyFactory.java new file mode 100644 index 0000000..7e3a8b4 --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/aop/ProxyFactory.java @@ -0,0 +1,17 @@ +package com.ysj.tinySpring.aop; + + +public class ProxyFactory extends AdvisedSupport implements AopProxy { + + // 获取代理对象 + @Override + public Object getProxy() { + return createAopProxy().getProxy(); + } + + // 动态代理:通过Cglib代理提供代理对象 + protected final AopProxy createAopProxy() { + return new Cglib2AopProxy(this); + } + +} diff --git a/src+/main/java/com/ysj/tinySpring/aop/ReflectiveMethodInvocation.java b/src+/main/java/com/ysj/tinySpring/aop/ReflectiveMethodInvocation.java new file mode 100644 index 0000000..2f28ea5 --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/aop/ReflectiveMethodInvocation.java @@ -0,0 +1,63 @@ +package com.ysj.tinySpring.aop; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Method; + +import org.aopalliance.intercept.MethodInvocation; + +/** + * 封装被代理对象的方法 + * + */ +public class ReflectiveMethodInvocation implements MethodInvocation { + + // 原对象 + protected Object target; + + protected Method method; + + protected Object[] arguments; + + public ReflectiveMethodInvocation(Object target, Method method, Object[] arguments) { + this.target = target; + this.method = method; + this.arguments = arguments; + } + + @Override + public Object[] getArguments() { + return arguments; + } + + @Override + public AccessibleObject getStaticPart() { + return method; + } + + @Override + public Object getThis() { + return target; + } + + /** + * 调用被拦截对象的方法 + */ + @Override + public Object proceed() throws Throwable { + // tiny-spring这里是调用原始对象的方法 + // 不支持拦截器链 + /* + 为了支持拦截器链,可以做出以下修改: + 将 proceed() 方法修改为调用代理对象的方法 method.invoke(proxy,args)。 + 在代理对象的 InvocationHandler 的 invoke 函数中,查看拦截器列表,如果有拦截器,则调用第一个拦截器并 + 返回,否则调用原始对象的方法。 + */ + return method.invoke(target, arguments); + } + + @Override + public Method getMethod() { + return method; + } + +} diff --git a/src+/main/java/com/ysj/tinySpring/aop/TargetSource.java b/src+/main/java/com/ysj/tinySpring/aop/TargetSource.java new file mode 100644 index 0000000..26a72fa --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/aop/TargetSource.java @@ -0,0 +1,35 @@ +package com.ysj.tinySpring.aop; + +/** + * 封装被代理的对象的类的相关信息 + * + */ +public class TargetSource { + + // 原对象 + private Object target; + + private Class targetClass; + + private Class[] interfaces; + + + public TargetSource(Object target, Class targetClass,Class... interfaces) { + this.target = target; + this.targetClass = targetClass; + this.interfaces = interfaces; + } + + public Class getTargetClass() { + return targetClass; + } + + public Object getTarget() { + return target; + } + + public Class[] getInterfaces() { + return interfaces; + } + +} diff --git a/src+/main/java/com/ysj/tinySpring/beans/AbstractBeanDefinitionReader.java b/src+/main/java/com/ysj/tinySpring/beans/AbstractBeanDefinitionReader.java new file mode 100644 index 0000000..b8eb837 --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/beans/AbstractBeanDefinitionReader.java @@ -0,0 +1,38 @@ +package com.ysj.tinySpring.beans; + +import java.util.HashMap; +import java.util.Map; + +import com.ysj.tinySpring.beans.io.ResourceLoader; + +/** + * 实现 BeanDefinitionReader 接口的抽象类(未具体实现 loadBeanDefinitions,而是规范了 + * BeanDefinitionReader 的基本结构) + */ +public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader { + + /** + * 通过String - beanDefinition 键值对保存IoC容器里的类定义。 + */ + private Map registry; + + /** + * 保存了类加载器,使用时,只需要向其 loadBeanDefinitions() 传入一个资源地址,就可以自动调用其类加载器, + * 并把解析到的 BeanDefinition 保存到 registry 中去。 + */ + private ResourceLoader resourceLoader; + + protected AbstractBeanDefinitionReader(ResourceLoader resourceLoader) { + this.registry = new HashMap(); + this.resourceLoader = resourceLoader; + } + + public Map getRegistry() { + return registry; + } + + public ResourceLoader getResourceLoader() { + return resourceLoader; + } + +} diff --git a/src+/main/java/com/ysj/tinySpring/beans/BeanDefinition.java b/src+/main/java/com/ysj/tinySpring/beans/BeanDefinition.java new file mode 100644 index 0000000..509fb67 --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/beans/BeanDefinition.java @@ -0,0 +1,71 @@ +package com.ysj.tinySpring.beans; + +/** + * Bean在 IoC 容器中的定义, IoC 容器可以根据这个定义来生成实例 的问题 + * + * 以 BeanDefinition 类为核心发散出的几个类,都是用于解决 Bean 的具体定义问题,包括 Bean 的名字是什么、 + * 它的类型是什么,它的属性赋予了哪些值或者引用 + * + */ +public class BeanDefinition { + + private Object bean; + + /** + * bean的类型 + * 根据其 类型 可以生成一个类实例,然后可以把 属性 注入进去。 + */ + private Class beanClass; + + /** + * bean的名字 + */ + private String beanClassName; + + /** + * bean的属性集合 + * 每个属性都是键值对 String - Object + */ + private PropertyValues propertyValues = new PropertyValues(); + + public BeanDefinition() { } + + + public Object getBean() { + return bean; + } + + public void setBean(Object bean) { + this.bean = bean; + } + + public Class getBeanClass() { + return beanClass; + } + + public void setBeanClass(Class beanClass) { + this.beanClass = beanClass; + } + + public String getBeanClassName() { + return beanClassName; + } + + public void setBeanClassName(String beanClassName) { + this.beanClassName = beanClassName; + try { + this.beanClass = Class.forName(beanClassName); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + + public PropertyValues getPropertyValues() { + return propertyValues; + } + + public void setPropertyValues(PropertyValues propertyValues) { + this.propertyValues = propertyValues; + } + +} diff --git a/src+/main/java/com/ysj/tinySpring/beans/BeanDefinitionReader.java b/src+/main/java/com/ysj/tinySpring/beans/BeanDefinitionReader.java new file mode 100644 index 0000000..e649fcd --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/beans/BeanDefinitionReader.java @@ -0,0 +1,17 @@ +package com.ysj.tinySpring.beans; + +/** + * 解析 BeanDefinition 的接口 + * 暴露加载bean定义的方法 + * + */ +public interface BeanDefinitionReader { + + /** + * 根据地址加载bean的定义 + * @param location + * @throws Exception + */ + void loadBeanDefinitions(String location) throws Exception; + +} diff --git a/src+/main/java/com/ysj/tinySpring/beans/BeanPostProcessor.java b/src+/main/java/com/ysj/tinySpring/beans/BeanPostProcessor.java new file mode 100644 index 0000000..15bf511 --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/beans/BeanPostProcessor.java @@ -0,0 +1,15 @@ +package com.ysj.tinySpring.beans; + +/** + * 用于在bean定义初始化时嵌入相关操作 + * 例如:在 postProcessorAfterInitialization 方法中,使用动态代理的方式,返回一个对象的代理对象。解决了 + * 在 IoC 容器的何处植入 AOP 的问题。 + * + */ +public interface BeanPostProcessor { + + Object postProcessBeforeInitialization(Object bean, String beanName) throws Exception; + + Object postProcessAfterInitialization(Object bean, String beanName) throws Exception; + +} diff --git a/src+/main/java/com/ysj/tinySpring/beans/PropertyValue.java b/src+/main/java/com/ysj/tinySpring/beans/PropertyValue.java new file mode 100644 index 0000000..4deffdb --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/beans/PropertyValue.java @@ -0,0 +1,30 @@ +package com.ysj.tinySpring.beans; + +/** + * 封装bean的属性 + * + */ +public class PropertyValue { + + private final String name; + + /** + * 在 Spring 的 XML 中的 property 中,键是 key ,值是 value 或者 ref。对于 value 只要直接注入属性就行 + * 了,但是 ref 要先进行解析,转化为对应的实际 Object。 + */ + private final Object value; + + public PropertyValue(String name, Object value) { + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public Object getValue() { + return value; + } + +} diff --git a/src+/main/java/com/ysj/tinySpring/beans/PropertyValues.java b/src+/main/java/com/ysj/tinySpring/beans/PropertyValues.java new file mode 100644 index 0000000..085e81c --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/beans/PropertyValues.java @@ -0,0 +1,25 @@ +package com.ysj.tinySpring.beans; + +import java.util.ArrayList; +import java.util.List; + +/** + * 封装一个对象所有的PropertyValue。 + * 为什么封装而不是直接用List? 因为可以封装一些操作。 + * + */ +public class PropertyValues { + + private final List propertyValueList = new ArrayList(); + + public PropertyValues() {} + + public void addPropertyValue(PropertyValue pv) { + this.propertyValueList.add(pv); + } + + public List getPropertyValuesList() { + return this.propertyValueList; + } + +} diff --git a/src+/main/java/com/ysj/tinySpring/beans/factory/AbstractBeanFactory.java b/src+/main/java/com/ysj/tinySpring/beans/factory/AbstractBeanFactory.java new file mode 100644 index 0000000..d76100b --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/beans/factory/AbstractBeanFactory.java @@ -0,0 +1,143 @@ +package com.ysj.tinySpring.beans.factory; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.ysj.tinySpring.beans.BeanDefinition; +import com.ysj.tinySpring.beans.BeanPostProcessor; + +/** + * BeanFactory 的一种抽象类实现,规范了 IoC 容器的基本结构。 + * + * IoC 容器的结构:AbstractBeanFactory 维护一个 beanDefinitionMap 哈希表用于保存类的定义信息(BeanDefinition)。 + * + */ +public class AbstractBeanFactory implements BeanFactory{ + + /** + * bean定义的信息和bean的name保存在线程安全的HashMap中 + */ + private Map beanDefinitionMap = new ConcurrentHashMap(); + + /** + * 保存完成注册的bean的name + */ + private final List beanDefinitionNames = new ArrayList(); + + /** + * 增加bean处理程序: + * 例如通过AspectJAwareAdvisorAutoProxyCreator#postProcessAfterInitialization()实现AOP的织入 + */ + private List beanPostProcessors = new ArrayList(); + + /** + * 根据名字获取bean实例(实例化并初始化bean) + */ + @Override + public Object getBean(String name) throws Exception { + // 获取该bean的定义 + BeanDefinition beanDefinition = beanDefinitionMap.get(name); + // 如果没有这个bean的定义就抛异常 + if (beanDefinition == null) { + throw new IllegalArgumentException("No bean named " + name + " is defined"); + } + Object bean = beanDefinition.getBean(); + // 如果还没有装配bean + if (bean == null) { + // 装配bean(实例化并注入属性) + bean = doCreateBean(beanDefinition); + // 初始化bean + // 例如:生成相关代理类,用于实现AOP织入 + bean = initializeBean(bean, name); + beanDefinition.setBean(bean); + } + return bean; + } + + // 装载bean + protected Object doCreateBean(BeanDefinition beanDefinition) throws Exception { + // 实例化bean + Object bean = createBeanInstance(beanDefinition); + beanDefinition.setBean(bean); + // 注入属性的hook方法(参考模板方法设计模式中的hook方法)交给子类去实现 + // 例如:AutowireCapableBeanFactory.java实现了自动装配 + applyPropertyValues(bean, beanDefinition); + return bean; + } + + protected Object createBeanInstance(BeanDefinition beanDefinition) throws Exception { + return beanDefinition.getBeanClass().newInstance(); + } + + protected void applyPropertyValues(Object bean, BeanDefinition beanDefinition) throws Exception { + + } + + /** + * 初始化bean + * 可在此进行AOP的相关操作:例如生成相关代理类,并返回 + * @param bean + * @param name + * @return + * @throws Exception + */ + protected Object initializeBean(Object bean, String name) throws Exception { + for (BeanPostProcessor beanPostProcessor : beanPostProcessors) { + bean = beanPostProcessor.postProcessBeforeInitialization(bean, name); + } + + // 可以看看AutowireCapableBeanFactory的postProcessAfterInitialization()方法实现 + // 返回的可能是代理对象 + for (BeanPostProcessor beanPostProcessor : beanPostProcessors) { + bean = beanPostProcessor.postProcessAfterInitialization(bean, name); + } + return bean; + } + + /** + * 预处理bean的定义,将bean的名字提前存好,实现Ioc容器中存储单例bean + * @throws Exception + */ + public void preInstantiateSingletons() throws Exception { + Iterator it = this.beanDefinitionNames.iterator(); + while (it.hasNext()) { + String beanName = (String) it.next(); + getBean(beanName); + } + } + + /** + * 根据类型获取所有bean实例 + * @param type + * @return + * @throws Exception + */ + public List getBeansForType(Class type) throws Exception { + List beans = new ArrayList(); + for (String beanDefinitionName : beanDefinitionNames) { + /** + * boolean isAssignableFrom(Class cls) + * 判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口。 + */ + if (type.isAssignableFrom(beanDefinitionMap.get(beanDefinitionName).getBeanClass())) { + beans.add(getBean(beanDefinitionName)); + } + } + return beans; + } + + public void registerBeanDefinition(String name, BeanDefinition beanDefinition) throws Exception { + beanDefinitionMap.put(name, beanDefinition); + beanDefinitionNames.add(name); + } + + // 增加bean处理程序,例如AspectJAwareAdvisorAutoProxyCreator#postProcessAfterInitialization() + public void addBeanPostProcessor(BeanPostProcessor beanPostProcessor) throws Exception { + this.beanPostProcessors.add(beanPostProcessor); + } + + +} diff --git a/src+/main/java/com/ysj/tinySpring/beans/factory/AutowireCapableBeanFactory.java b/src+/main/java/com/ysj/tinySpring/beans/factory/AutowireCapableBeanFactory.java new file mode 100644 index 0000000..6ff9332 --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/beans/factory/AutowireCapableBeanFactory.java @@ -0,0 +1,61 @@ +package com.ysj.tinySpring.beans.factory; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import com.ysj.tinySpring.BeanReference; +import com.ysj.tinySpring.aop.BeanFactoryAware; +import com.ysj.tinySpring.beans.BeanDefinition; +import com.ysj.tinySpring.beans.PropertyValue; + +/** + * 可实现自动装配的BeanFactory + * + */ +public class AutowireCapableBeanFactory extends AbstractBeanFactory{ + + /** + * 通过反射自动装配bean的所有属性 + */ + protected void applyPropertyValues(Object bean, BeanDefinition beanDefinition) throws Exception { + if (bean instanceof BeanFactoryAware) { + ((BeanFactoryAware) bean).setBeanFactory(this); + } + for (PropertyValue propertyValue : beanDefinition.getPropertyValues().getPropertyValuesList()) { + Object value = propertyValue.getValue(); + // 如果属性是ref而不是value类型就先实例化那个ref的bean,然后装载到这个value里 + if (value instanceof BeanReference) { + BeanReference beanReference = (BeanReference) value; + // 先实例化ref的bean再装配进去 + value = getBean(beanReference.getName()); + } + + try{ + /** + * 例如: + + + 先获取dog对象的setId方法,然后通过反射调用该方法将value设置进去 + getDeclaredMethod方法的第一个参数是方法名,第二个参数是该方法的参数列表 + */ + Method declaredMethod = bean.getClass().getDeclaredMethod( + // 拼接方法名 + "set" + propertyValue.getName().substring(0, 1).toUpperCase() + + propertyValue.getName().substring(1), + value.getClass()); + + /*System.out.println("set" + propertyValue.getName().substring(0, 1).toUpperCase() + + propertyValue.getName().substring(1));*/ + + declaredMethod.setAccessible(true); + declaredMethod.invoke(bean, value); + } catch (NoSuchMethodException e) { + // 如果该bean没有setXXX的类似方法,就直接将value设置到相应的属性域内 + Field declaredField = bean.getClass().getDeclaredField(propertyValue.getName()); + declaredField.setAccessible(true); + declaredField.set(bean, value); + } + } + } + +} diff --git a/src+/main/java/com/ysj/tinySpring/beans/factory/BeanFactory.java b/src+/main/java/com/ysj/tinySpring/beans/factory/BeanFactory.java new file mode 100644 index 0000000..ce4fe31 --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/beans/factory/BeanFactory.java @@ -0,0 +1,20 @@ +package com.ysj.tinySpring.beans.factory; + +/** + * 标识一个 IoC 容器。 + * + * 以 BeanFactory 接口为核心发散出的几个类,都是用于解决 IoC 容器在 已经获取 Bean 的 定义 的情况下, + * 如何装配、获取 Bean 实例 的问题。 + * + */ +public interface BeanFactory { + + /** + * 通过 getBean(String) 方法来 获取bean实例 + * @param name + * @return + * @throws Exception + */ + Object getBean(String name) throws Exception; + +} diff --git a/src+/main/java/com/ysj/tinySpring/beans/io/Resource.java b/src+/main/java/com/ysj/tinySpring/beans/io/Resource.java new file mode 100644 index 0000000..1f2ecb2 --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/beans/io/Resource.java @@ -0,0 +1,21 @@ +package com.ysj.tinySpring.beans.io; + +import java.io.IOException; +import java.io.InputStream; + +/** + * 标识一个外部资源。 + * 以 Resource 接口为核心发散出的几个类,都是用于解决 IoC 容器中的内容从哪里来的问题,也就是 配置文件 + * 从哪里读取、配置文件如何读取 的问题。 + * + */ +public interface Resource { + + /** + * 通过 getInputStream() 方法 获取资源的输入流 。 + * @return + * @throws IOException + */ + InputStream getInputStream() throws IOException; + +} diff --git a/src+/main/java/com/ysj/tinySpring/beans/io/ResourceLoader.java b/src+/main/java/com/ysj/tinySpring/beans/io/ResourceLoader.java new file mode 100644 index 0000000..ff1db92 --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/beans/io/ResourceLoader.java @@ -0,0 +1,26 @@ +package com.ysj.tinySpring.beans.io; + +import java.net.URL; + +/** + * 资源加载类 + * + */ +public class ResourceLoader { + + /** + * 获取一个 Resouce 对象 + * @param location + * @return + */ + public Resource getResource(String location){ + // 通过给定名称查找资源 + URL resource = this.getClass().getClassLoader().getResource(location); + /* + * 注: 这里在设计上有一定的问题,ResourceLoader 直接返回了一个 UrlResource,更好的方法是声明 + * 一个 ResourceLoader 接口,再实现一个 UrlResourceLoader 类用于加载 UrlResource。 + */ + return new UrlResource(resource); + } + +} diff --git a/src+/main/java/com/ysj/tinySpring/beans/io/UrlResource.java b/src+/main/java/com/ysj/tinySpring/beans/io/UrlResource.java new file mode 100644 index 0000000..23ef055 --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/beans/io/UrlResource.java @@ -0,0 +1,33 @@ +package com.ysj.tinySpring.beans.io; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; + +/** + * 实现 Resource 接口的资源类,通过 URL 获取资源。 + * + */ +public class UrlResource implements Resource{ + + /** + * 通过这个 URL 获取资源 + */ + private final URL url; + + public UrlResource(URL url) { + this.url = url; + } + + /** + * 通过URL获取资源 + */ + @Override + public InputStream getInputStream() throws IOException { + URLConnection urlConnection = url.openConnection(); + urlConnection.connect(); + return urlConnection.getInputStream(); + } + +} diff --git a/src+/main/java/com/ysj/tinySpring/beans/xml/XmlBeanDefinitionReader.java b/src+/main/java/com/ysj/tinySpring/beans/xml/XmlBeanDefinitionReader.java new file mode 100644 index 0000000..657619b --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/beans/xml/XmlBeanDefinitionReader.java @@ -0,0 +1,128 @@ +package com.ysj.tinySpring.beans.xml; + +import java.io.InputStream; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import com.ysj.tinySpring.BeanReference; +import com.ysj.tinySpring.beans.AbstractBeanDefinitionReader; +import com.ysj.tinySpring.beans.BeanDefinition; +import com.ysj.tinySpring.beans.PropertyValue; +import com.ysj.tinySpring.beans.io.ResourceLoader; + +/** + * 从 XML 文件中读取bean的定义。具体实现了 loadBeanDefinitions() 方法 + * 注意: 在loadBeanDefinitions中并没有实例化bean,而只是注册了bean的定义 + * + */ +public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader{ + + public XmlBeanDefinitionReader(ResourceLoader resourceLoader) { + super(resourceLoader); + } + + /** + * 加载并通过bean定义注册bean + */ + @Override + public void loadBeanDefinitions(String location) throws Exception { + // 获取资源输入流 + InputStream inputStream = getResourceLoader().getResource(location).getInputStream(); + /** + * 从xml文件中读取所有bean的定义,并注册到registry中 + * 注意: 此时bean定义里并没有实例化该bean + */ + doLoadBeanDefinitions(inputStream); + } + + protected void doLoadBeanDefinitions(InputStream inputStream) throws Exception { + // 获取工厂 + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + // 获取生成器 + DocumentBuilder docBuilder = factory.newDocumentBuilder(); + // 解析为Document + Document doc = docBuilder.parse(inputStream); + // 解析并注册其中的bean + registerBeanDefinitions(doc); + inputStream.close(); + } + + public void registerBeanDefinitions(Document doc) { + // 获取根标签 + Element root = doc.getDocumentElement(); + + parseBeanDefinitions(root); + } + + protected void parseBeanDefinitions(Element root) { + // 获取所有下的 + NodeList nodeList = root.getChildNodes(); + for (int i = 0; i < nodeList.getLength(); i++) { + Node node = nodeList.item(i); + // 如果该子标签是Element + if (node instanceof Element) { + Element ele = (Element) node; + // 解析bean标签 + processBeanDefinition(ele); + } + } + } + + /** + * 处理(解析)bean标签 + * @param ele + */ + protected void processBeanDefinition(Element ele) { + // 获取bean标签的id属性作为bean的name + String name = ele.getAttribute("id"); + // 获取bean标签的class属性作为bean的className + String className = ele.getAttribute("class"); + + BeanDefinition beanDefinition = new BeanDefinition(); + // 解析bean标签下的property子标签 + processProperty(ele, beanDefinition); + // 设置className的同时,也在内部设置了Class + beanDefinition.setBeanClassName(className); + // 注册类定义 + getRegistry().put(name, beanDefinition); + } + + /** + * 解析bean标签的property子标签 + * @param ele + * @param beanDefinition + */ + private void processProperty(Element ele, BeanDefinition beanDefinition) { + // 获取所有property标签 + NodeList propertyNodes = ele.getElementsByTagName("property"); + for (int i = 0; i < propertyNodes.getLength(); i++) { + Node node = propertyNodes.item(i); + if (node instanceof Element) { + Element propertyEle = (Element) node; + String name = propertyEle.getAttribute("name"); + String value = propertyEle.getAttribute("value"); + // 如果是value型的属性值 + if (value != null && value.length() > 0) { + beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value)); + } else { + // 否则是ref型的属性值 + 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); + // 保存一个ref型属性值的属性 + beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference)); + } + } + } + } + +} diff --git a/src+/main/java/com/ysj/tinySpring/context/AbstractApplicationContext.java b/src+/main/java/com/ysj/tinySpring/context/AbstractApplicationContext.java new file mode 100644 index 0000000..9d037e2 --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/context/AbstractApplicationContext.java @@ -0,0 +1,68 @@ +package com.ysj.tinySpring.context; + +import java.util.List; + +import com.ysj.tinySpring.beans.BeanPostProcessor; +import com.ysj.tinySpring.beans.factory.AbstractBeanFactory; + +/** + * ApplicationContext 的抽象实现 + * + */ +public abstract class AbstractApplicationContext implements ApplicationContext { + + protected AbstractBeanFactory beanFactory; + + public AbstractApplicationContext(AbstractBeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + /** + * 用于实现 BeanFactory 的刷新,也就是告诉 BeanFactory 该使用哪个资源(Resource)加载bean的定义 + * (BeanDefinition),并实例化,初始化bean + * @throws Exception + */ + public void refresh() throws Exception { + // 加载出bean的定义并保存到beanFactory中 + loadBeanDefinitions(beanFactory); + // 从 BeanFactory 中的bean的定义找实现 BeanPostProcessor接口的类(例如:AspectJAwareAdvisorAutoProxyCreator.java) + // 注册到 AbstractBeanFactory 维护的 BeanPostProcessor 列表中去(用于在bean实例化后对其进行一些其他处理, + // 可以看看getBean方法()中的initializeBean()的调用)。 + // 后面调用getBean方法通过AspectJAwareAdvisorAutoProxyCreator#postProcessAfterInitialization()方法中调用了 + // getBeansForType方法保证了 PointcutAdvisor 的实例化顺序优于普通 Bean。 + registerBeanPostProcessors(beanFactory); + // 默认以单例的方式实例化所有 Bean + onRefresh(); + } + + /** + * 由子类决定从哪种形式的Resource中加载出bean的定义,并保存到beanFactory中 + * @param beanFactory + * @throws Exception + */ + protected abstract void loadBeanDefinitions(AbstractBeanFactory beanFactory) throws Exception; + + /** + * 可用于实例化AspectJAwareAdvisorAutoProxyCreator + * @param beanFactory + * @throws Exception + */ + protected void registerBeanPostProcessors(AbstractBeanFactory beanFactory) throws Exception { + List beanPostProcessors = beanFactory.getBeansForType(BeanPostProcessor.class); + for (Object beanPostProcessor : beanPostProcessors) { + beanFactory.addBeanPostProcessor((BeanPostProcessor) beanPostProcessor); + } + } + + // 实现支持singleton(单例)类型的bean + protected void onRefresh() throws Exception{ + beanFactory.preInstantiateSingletons(); + } + + // 从beanFactory中获取bean + @Override + public Object getBean(String name) throws Exception { + return beanFactory.getBean(name); + } + +} diff --git a/src+/main/java/com/ysj/tinySpring/context/ApplicationContext.java b/src+/main/java/com/ysj/tinySpring/context/ApplicationContext.java new file mode 100644 index 0000000..3ff685a --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/context/ApplicationContext.java @@ -0,0 +1,16 @@ +package com.ysj.tinySpring.context; + +import com.ysj.tinySpring.beans.factory.BeanFactory; + +/** + * 以 ApplicationContext 接口为核心发散出的几个类,主要是对前面 Resouce 、 BeanFactory、BeanDefinition + * 进行了功能的封装,解决 根据地址获取资源通过 IoC 容器注册bean定义并实例化bean的问题。 + * + * 通常,要实现一个 IoC 容器时,需要先通过 ResourceLoader 获取一个 Resource,其中包括了容器的配置、Bean + * 的定义信息。接着,使用 BeanDefinitionReader接口暴露的方法读取并注册该 Resource 中的 BeanDefinition 信息。最后, + * 把 BeanDefinition 保存在 BeanFactory 中,容器配置完毕可以使用。 + * + */ +public interface ApplicationContext extends BeanFactory{ + +} diff --git a/src+/main/java/com/ysj/tinySpring/context/ClassPathXmlApplicationContext.java b/src+/main/java/com/ysj/tinySpring/context/ClassPathXmlApplicationContext.java new file mode 100644 index 0000000..b1050b6 --- /dev/null +++ b/src+/main/java/com/ysj/tinySpring/context/ClassPathXmlApplicationContext.java @@ -0,0 +1,53 @@ +package com.ysj.tinySpring.context; + +import java.util.Map; + +import com.ysj.tinySpring.beans.BeanDefinition; +import com.ysj.tinySpring.beans.factory.AbstractBeanFactory; +import com.ysj.tinySpring.beans.factory.AutowireCapableBeanFactory; +import com.ysj.tinySpring.beans.io.ResourceLoader; +import com.ysj.tinySpring.beans.xml.XmlBeanDefinitionReader; + +/** + * 从类路径加载资源的具体实现类 + * + */ +public class ClassPathXmlApplicationContext extends AbstractApplicationContext { + + private String configLocation; + + public ClassPathXmlApplicationContext(String configLocation) throws Exception { + // 默认是自动装载bean + this(configLocation, new AutowireCapableBeanFactory()); + } + + public ClassPathXmlApplicationContext(String configLocation, AbstractBeanFactory beanFactory) throws Exception { + super(beanFactory); + this.configLocation = configLocation; + refresh(); + } + + /** + * 加载出bean的定义,并保存到beanFactory中 + * + * 注意:在 tiny-spring 的实现中,先用 BeanDefinitionReader 读取 BeanDefiniton 后,保存在内置的 + * registry (键值对为 String - BeanDefinition 的哈希表,通过 getRigistry() 获取)中,然后由 + * ApplicationContext 把 BeanDefinitionReader 中 registry 的键值对一个个赋值给 BeanFactory + * 中保存的 beanDefinitionMap。而在 Spring 的实现中,BeanDefinitionReader 直接操作 BeanDefinition + * ,它的 getRegistry() 获取的不是内置的 registry,而是 BeanFactory 的实例。如何实现呢? + * 以 DefaultListableBeanFactory 为例,它实现了一个 BeanDefinitonRigistry 接口,该接口 + * 把 BeanDefinition 的 注册 、获取 等方法都暴露了出来,这样,BeanDefinitionReader 可以直接通过这些 + * 方法把 BeanDefiniton 直接加载到 BeanFactory 中去。 + */ + @Override + protected void loadBeanDefinitions(AbstractBeanFactory beanFactory) throws Exception { + XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader()); + // 从类路径加载xml文件中bean的定义并注册到AbstractBeanDefinitionReader的registry中去 + xmlBeanDefinitionReader.loadBeanDefinitions(configLocation); + // 将加载出的bean定义从registry中注册到beanFactory中 + for (Map.Entry beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) { + beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue()); + } + } + +} diff --git a/src+/test/java/com/ysj/BeanFactoryTest.java b/src+/test/java/com/ysj/BeanFactoryTest.java new file mode 100644 index 0000000..164ffd5 --- /dev/null +++ b/src+/test/java/com/ysj/BeanFactoryTest.java @@ -0,0 +1,54 @@ +package com.ysj; + +import java.util.Map; + +import org.junit.Test; + +import com.ysj.tinySpring.beans.BeanDefinition; +import com.ysj.tinySpring.beans.factory.AbstractBeanFactory; +import com.ysj.tinySpring.beans.factory.AutowireCapableBeanFactory; +import com.ysj.tinySpring.beans.io.ResourceLoader; +import com.ysj.tinySpring.beans.xml.XmlBeanDefinitionReader; + +/** + * @author yihua.huang@dianping.com + */ +public class BeanFactoryTest { + + @Test + public void testLazy() throws Exception { + // 1.读取配置 + XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader()); + xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml"); + + // 2.初始化BeanFactory并注册bean + AbstractBeanFactory beanFactory = new AutowireCapableBeanFactory(); + for (Map.Entry beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) { + beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue()); + } + + // 3.获取bean + HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService"); + helloWorldService.helloWorld(); + } + + @Test + public void testPreInstantiate() throws Exception { + // 1.读取配置 + XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader()); + xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml"); + + // 2.初始化BeanFactory并注册bean + AbstractBeanFactory beanFactory = new AutowireCapableBeanFactory(); + for (Map.Entry beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) { + beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue()); + } + + // 3.初始化bean + beanFactory.preInstantiateSingletons(); + + // 4.获取bean + HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService"); + helloWorldService.helloWorld(); + } +} diff --git a/src+/test/java/com/ysj/BeanInitializeLogger.java b/src+/test/java/com/ysj/BeanInitializeLogger.java new file mode 100644 index 0000000..88223b5 --- /dev/null +++ b/src+/test/java/com/ysj/BeanInitializeLogger.java @@ -0,0 +1,21 @@ +package com.ysj; + +import com.ysj.tinySpring.beans.BeanPostProcessor; + +/** + * 实例化bean后,初始化时会调用该方法 + * @author yihua.huang@dianping.com + */ +public class BeanInitializeLogger implements BeanPostProcessor { + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws Exception { + System.out.println("Initialize bean " + beanName + " start!"); + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws Exception { + System.out.println("Initialize bean " + beanName + " end!"); + return bean; + } +} diff --git a/src+/test/java/com/ysj/HelloWorldService.java b/src+/test/java/com/ysj/HelloWorldService.java new file mode 100644 index 0000000..d4a83a2 --- /dev/null +++ b/src+/test/java/com/ysj/HelloWorldService.java @@ -0,0 +1,9 @@ +package com.ysj; + +/** + * @author yihua.huang@dianping.com + */ +public interface HelloWorldService { + + void helloWorld(); +} diff --git a/src+/test/java/com/ysj/HelloWorldServiceImpl.java b/src+/test/java/com/ysj/HelloWorldServiceImpl.java new file mode 100644 index 0000000..0e3dbc4 --- /dev/null +++ b/src+/test/java/com/ysj/HelloWorldServiceImpl.java @@ -0,0 +1,25 @@ +package com.ysj; + +/** + * @author yihua.huang@dianping.com + */ +public class HelloWorldServiceImpl implements HelloWorldService { + + private String text; + + private OutputService outputService; + + @Override + public void helloWorld() { + outputService.output(text); + } + + public void setText(String text) { + this.text = text; + } + + public void setOutputService(OutputService outputService) { + this.outputService = outputService; + } + +} diff --git a/src+/test/java/com/ysj/OutputService.java b/src+/test/java/com/ysj/OutputService.java new file mode 100644 index 0000000..3a53880 --- /dev/null +++ b/src+/test/java/com/ysj/OutputService.java @@ -0,0 +1,8 @@ +package com.ysj; + +/** + * @author yihua.huang@dianping.com + */ +public interface OutputService { + void output(String text); +} diff --git a/src+/test/java/com/ysj/OutputServiceImpl.java b/src+/test/java/com/ysj/OutputServiceImpl.java new file mode 100644 index 0000000..e96dae6 --- /dev/null +++ b/src+/test/java/com/ysj/OutputServiceImpl.java @@ -0,0 +1,13 @@ +package com.ysj; + +/** + * @author yihua.huang@dianping.com + */ +public class OutputServiceImpl implements OutputService { + + @Override + public void output(String text) { + System.out.println(text); + } + +} diff --git a/src+/test/java/com/ysj/aop/AspectJExpressionPointcutTest.java b/src+/test/java/com/ysj/aop/AspectJExpressionPointcutTest.java new file mode 100644 index 0000000..04b14f2 --- /dev/null +++ b/src+/test/java/com/ysj/aop/AspectJExpressionPointcutTest.java @@ -0,0 +1,33 @@ +package com.ysj.aop; + +import org.junit.Assert; +import org.junit.Test; + +import com.ysj.HelloWorldService; +import com.ysj.HelloWorldServiceImpl; +import com.ysj.tinySpring.aop.AspectJExpressionPointcut; + +/** + * @author yihua.huang@dianping.com + */ +public class AspectJExpressionPointcutTest { + + @Test + public void testClassFilter() throws Exception { + String expression = "execution(* us.codecraft.tinyioc.*.*(..))"; + AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut(); + aspectJExpressionPointcut.setExpression(expression); + boolean matches = aspectJExpressionPointcut.getClassFilter().matches(HelloWorldService.class); + Assert.assertTrue(matches); + } + + @Test + public void testMethodInterceptor() throws Exception { + String expression = "execution(* com.ysj.*.*(..))"; + AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut(); + aspectJExpressionPointcut.setExpression(expression); + boolean matches = aspectJExpressionPointcut.getMethodMatcher() + .matches(HelloWorldServiceImpl.class.getDeclaredMethod("helloWorld"), HelloWorldServiceImpl.class); + Assert.assertTrue(matches); + } +} diff --git a/src+/test/java/com/ysj/aop/Cglib2AopProxyTest.java b/src+/test/java/com/ysj/aop/Cglib2AopProxyTest.java new file mode 100644 index 0000000..1c9a3d7 --- /dev/null +++ b/src+/test/java/com/ysj/aop/Cglib2AopProxyTest.java @@ -0,0 +1,45 @@ +package com.ysj.aop; + +import org.junit.Test; + +import com.ysj.HelloWorldService; +import com.ysj.HelloWorldServiceImpl; +import com.ysj.tinySpring.aop.AdvisedSupport; +import com.ysj.tinySpring.aop.Cglib2AopProxy; +import com.ysj.tinySpring.aop.TargetSource; +import com.ysj.tinySpring.context.ApplicationContext; +import com.ysj.tinySpring.context.ClassPathXmlApplicationContext; + +/** + * @author yihua.huang@dianping.com + */ +public class Cglib2AopProxyTest { + + @Test + public void testInterceptor() throws Exception { + // --------- helloWorldService without AOP + ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml"); + HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService"); + helloWorldService.helloWorld(); + + // --------- helloWorldService with AOP + // 1. 设置被代理对象(Joinpoint) + AdvisedSupport advisedSupport = new AdvisedSupport(); + TargetSource targetSource = new TargetSource(helloWorldService, HelloWorldServiceImpl.class, + HelloWorldService.class); + advisedSupport.setTargetSource(targetSource); + + // 2. 设置拦截器(Advice) + TimerInterceptor timerInterceptor = new TimerInterceptor(); + advisedSupport.setMethodInterceptor(timerInterceptor); + + // 补:没有设置MethodMatcher,所以拦截该类的所有方法 + // 3. 创建代理(Proxy) + Cglib2AopProxy cglib2AopProxy = new Cglib2AopProxy(advisedSupport); + HelloWorldService helloWorldServiceProxy = (HelloWorldService) cglib2AopProxy.getProxy(); + + // 4. 基于AOP的调用 + helloWorldServiceProxy.helloWorld(); + + } +} diff --git a/src+/test/java/com/ysj/aop/JdkDynamicAopProxyTest.java b/src+/test/java/com/ysj/aop/JdkDynamicAopProxyTest.java new file mode 100644 index 0000000..3affd29 --- /dev/null +++ b/src+/test/java/com/ysj/aop/JdkDynamicAopProxyTest.java @@ -0,0 +1,46 @@ +package com.ysj.aop; + +import org.junit.Test; + +import com.ysj.HelloWorldService; +import com.ysj.HelloWorldServiceImpl; +import com.ysj.tinySpring.aop.AdvisedSupport; +import com.ysj.tinySpring.aop.JdkDynamicAopProxy; +import com.ysj.tinySpring.aop.TargetSource; +import com.ysj.tinySpring.context.ApplicationContext; +import com.ysj.tinySpring.context.ClassPathXmlApplicationContext; + +/** + * @author yihua.huang@dianping.com + */ +public class JdkDynamicAopProxyTest { + + @Test + public void testInterceptor() throws Exception { + // --------- helloWorldService without AOP + ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml"); + HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService"); + helloWorldService.helloWorld(); + + // --------- helloWorldService with AOP + // 1. 设置被代理对象(Joinpoint) + AdvisedSupport advisedSupport = new AdvisedSupport(); + TargetSource targetSource = new TargetSource(helloWorldService, HelloWorldServiceImpl.class, + HelloWorldService.class); + advisedSupport.setTargetSource(targetSource); + + // 2. 设置拦截器(Advice) + TimerInterceptor timerInterceptor = new TimerInterceptor(); + advisedSupport.setMethodInterceptor(timerInterceptor); + + // 补:由于用户未设置MethodMatcher,所以通过代理还是调用的原方法(JdkDynamicAopProxy中的invoke方法最后 + // 返回method.invoke(...)而不是methodInterceptor.invoke(...) ) + // 3. 创建代理(Proxy) + JdkDynamicAopProxy jdkDynamicAopProxy = new JdkDynamicAopProxy(advisedSupport); + HelloWorldService helloWorldServiceProxy = (HelloWorldService) jdkDynamicAopProxy.getProxy(); + + // 4. 基于AOP的调用 + helloWorldServiceProxy.helloWorld(); + + } +} diff --git a/src+/test/java/com/ysj/aop/TimerInterceptor.java b/src+/test/java/com/ysj/aop/TimerInterceptor.java new file mode 100644 index 0000000..659e7fd --- /dev/null +++ b/src+/test/java/com/ysj/aop/TimerInterceptor.java @@ -0,0 +1,21 @@ +package com.ysj.aop; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +/** + * @author yihua.huang@dianping.com + */ +public class TimerInterceptor implements MethodInterceptor { + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + long time = System.nanoTime(); + System.out.println("Invocation of Method " + invocation.getMethod().getName() + " start!"); + Object proceed = invocation.proceed(); + System.out.println("Invocation of Method " + invocation.getMethod().getName() + " end! takes " + + (System.nanoTime() - time) + " nanoseconds."); + return proceed; + } + +} diff --git a/src+/test/java/com/ysj/beans/io/ResourceLoaderTest.java b/src+/test/java/com/ysj/beans/io/ResourceLoaderTest.java new file mode 100644 index 0000000..fe8475d --- /dev/null +++ b/src+/test/java/com/ysj/beans/io/ResourceLoaderTest.java @@ -0,0 +1,24 @@ +package com.ysj.beans.io; + +import java.io.IOException; +import java.io.InputStream; + +import org.junit.Assert; +import org.junit.Test; + +import com.ysj.tinySpring.beans.io.Resource; +import com.ysj.tinySpring.beans.io.ResourceLoader; + +/** + * @author yihua.huang@dianping.com + */ +public class ResourceLoaderTest { + + @Test + public void test() throws IOException { + ResourceLoader resourceLoader = new ResourceLoader(); + Resource resource = resourceLoader.getResource("tinyioc.xml"); + InputStream inputStream = resource.getInputStream(); + Assert.assertNotNull(inputStream); + } +} diff --git a/src+/test/java/com/ysj/beans/xml/XmlBeanDefinitionReaderTest.java b/src+/test/java/com/ysj/beans/xml/XmlBeanDefinitionReaderTest.java new file mode 100644 index 0000000..0dfc8b5 --- /dev/null +++ b/src+/test/java/com/ysj/beans/xml/XmlBeanDefinitionReaderTest.java @@ -0,0 +1,24 @@ +package com.ysj.beans.xml; + +import java.util.Map; + +import org.junit.Assert; +import org.junit.Test; + +import com.ysj.tinySpring.beans.BeanDefinition; +import com.ysj.tinySpring.beans.io.ResourceLoader; +import com.ysj.tinySpring.beans.xml.XmlBeanDefinitionReader; + +/** + * @author yihua.huang@dianping.com + */ +public class XmlBeanDefinitionReaderTest { + + @Test + public void test() throws Exception { + XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader()); + xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml"); + Map registry = xmlBeanDefinitionReader.getRegistry(); + Assert.assertTrue(registry.size() > 0); + } +} diff --git a/src+/test/java/com/ysj/context/ApplicationContextTest.java b/src+/test/java/com/ysj/context/ApplicationContextTest.java new file mode 100644 index 0000000..fbca74f --- /dev/null +++ b/src+/test/java/com/ysj/context/ApplicationContextTest.java @@ -0,0 +1,32 @@ +package com.ysj.context; + +import org.junit.Assert; +import org.junit.Test; + +import com.ysj.HelloWorldService; +import com.ysj.OutputService; +import com.ysj.tinySpring.context.ApplicationContext; +import com.ysj.tinySpring.context.ClassPathXmlApplicationContext; + +/** + * @author yihua.huang@dianping.com + */ +public class ApplicationContextTest { + + @Test + public void test() throws Exception { + ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml"); + // OutputService outputService = (OutputService) applicationContext.getBean("outputService"); + HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService"); + + // Assert.assertNotNull(helloWorldService); + helloWorldService.helloWorld(); + } + + @Test + public void testPostBeanProcessor() throws Exception { + ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc-postbeanprocessor.xml"); + HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService"); + helloWorldService.helloWorld(); + } +} diff --git a/src+/test/resources/tinyioc-postbeanprocessor.xml b/src+/test/resources/tinyioc-postbeanprocessor.xml new file mode 100644 index 0000000..34696e1 --- /dev/null +++ b/src+/test/resources/tinyioc-postbeanprocessor.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src+/test/resources/tinyioc.xml b/src+/test/resources/tinyioc.xml new file mode 100644 index 0000000..b7c9528 --- /dev/null +++ b/src+/test/resources/tinyioc.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git "a/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\344\270\244\347\247\215\345\212\250\346\200\201\344\273\243\347\220\206.png" "b/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\344\270\244\347\247\215\345\212\250\346\200\201\344\273\243\347\220\206.png" new file mode 100644 index 0000000..a9e1af2 Binary files /dev/null and "b/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\344\270\244\347\247\215\345\212\250\346\200\201\344\273\243\347\220\206.png" differ diff --git "a/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\344\273\216xml\344\270\255\345\212\240\350\275\275bean\345\256\232\344\271\211\347\232\204\344\270\273\350\246\201\347\233\270\345\205\263\347\261\273.png" "b/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\344\273\216xml\344\270\255\345\212\240\350\275\275bean\345\256\232\344\271\211\347\232\204\344\270\273\350\246\201\347\233\270\345\205\263\347\261\273.png" new file mode 100644 index 0000000..c9791d9 Binary files /dev/null and "b/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\344\273\216xml\344\270\255\345\212\240\350\275\275bean\345\256\232\344\271\211\347\232\204\344\270\273\350\246\201\347\233\270\345\205\263\347\261\273.png" differ diff --git "a/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\344\275\277\347\224\250cglib\345\212\250\346\200\201\344\273\243\347\220\206.png" "b/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\344\275\277\347\224\250cglib\345\212\250\346\200\201\344\273\243\347\220\206.png" new file mode 100644 index 0000000..53fca50 Binary files /dev/null and "b/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\344\275\277\347\224\250cglib\345\212\250\346\200\201\344\273\243\347\220\206.png" differ diff --git "a/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\344\275\277\347\224\250jdk\345\212\250\346\200\201\344\273\243\347\220\206.png" "b/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\344\275\277\347\224\250jdk\345\212\250\346\200\201\344\273\243\347\220\206.png" new file mode 100644 index 0000000..f09cb52 Binary files /dev/null and "b/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\344\275\277\347\224\250jdk\345\212\250\346\200\201\344\273\243\347\220\206.png" differ diff --git "a/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\345\210\207\347\202\271\351\200\232\347\237\245\345\231\250\347\233\270\345\205\263\346\216\245\345\217\243.png" "b/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\345\210\207\347\202\271\351\200\232\347\237\245\345\231\250\347\233\270\345\205\263\346\216\245\345\217\243.png" new file mode 100644 index 0000000..0ccc240 Binary files /dev/null and "b/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\345\210\207\347\202\271\351\200\232\347\237\245\345\231\250\347\233\270\345\205\263\346\216\245\345\217\243.png" differ diff --git "a/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\345\212\240\350\275\275\350\265\204\346\272\220\347\232\204\344\270\273\350\246\201\347\233\270\345\205\263\347\261\273.png" "b/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\345\212\240\350\275\275\350\265\204\346\272\220\347\232\204\344\270\273\350\246\201\347\233\270\345\205\263\347\261\273.png" new file mode 100644 index 0000000..e56eb79 Binary files /dev/null and "b/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\345\212\240\350\275\275\350\265\204\346\272\220\347\232\204\344\270\273\350\246\201\347\233\270\345\205\263\347\261\273.png" differ diff --git "a/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\345\256\236\347\216\260ApplicationContext\346\216\245\345\217\243\347\232\204\347\233\270\345\205\263\347\261\273.png" "b/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\345\256\236\347\216\260ApplicationContext\346\216\245\345\217\243\347\232\204\347\233\270\345\205\263\347\261\273.png" new file mode 100644 index 0000000..c095438 Binary files /dev/null and "b/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\345\256\236\347\216\260ApplicationContext\346\216\245\345\217\243\347\232\204\347\233\270\345\205\263\347\261\273.png" differ diff --git "a/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\346\227\266\345\272\217\345\233\276-\346\231\256\351\200\232bean\347\232\204\345\212\240\350\275\275\350\277\207\347\250\213.png" "b/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\346\227\266\345\272\217\345\233\276-\346\231\256\351\200\232bean\347\232\204\345\212\240\350\275\275\350\277\207\347\250\213.png" new file mode 100644 index 0000000..d93e54a Binary files /dev/null and "b/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\346\227\266\345\272\217\345\233\276-\346\231\256\351\200\232bean\347\232\204\345\212\240\350\275\275\350\277\207\347\250\213.png" differ diff --git "a/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\350\243\205\351\205\215bean\347\232\204\344\270\273\350\246\201\347\233\270\345\205\263\347\261\273.png" "b/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\350\243\205\351\205\215bean\347\232\204\344\270\273\350\246\201\347\233\270\345\205\263\347\261\273.png" new file mode 100644 index 0000000..820fd86 Binary files /dev/null and "b/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\350\243\205\351\205\215bean\347\232\204\344\270\273\350\246\201\347\233\270\345\205\263\347\261\273.png" differ diff --git "a/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\351\200\232\350\277\207AspectJ\350\241\250\350\276\276\345\274\217\345\244\204\347\220\206AOP\347\232\204\344\270\273\350\246\201\347\261\273\345\222\214\345\205\263\347\263\273.png" "b/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\351\200\232\350\277\207AspectJ\350\241\250\350\276\276\345\274\217\345\244\204\347\220\206AOP\347\232\204\344\270\273\350\246\201\347\261\273\345\222\214\345\205\263\347\263\273.png" new file mode 100644 index 0000000..475ca03 Binary files /dev/null and "b/src+/tiny-spring\351\241\271\347\233\256UML\345\210\206\346\236\220/\351\200\232\350\277\207AspectJ\350\241\250\350\276\276\345\274\217\345\244\204\347\220\206AOP\347\232\204\344\270\273\350\246\201\347\261\273\345\222\214\345\205\263\347\263\273.png" differ diff --git "a/src+/\344\270\200\344\272\233\346\200\273\347\273\223.md" "b/src+/\344\270\200\344\272\233\346\200\273\347\273\223.md" new file mode 100644 index 0000000..e41b5d8 --- /dev/null +++ "b/src+/\344\270\200\344\272\233\346\200\273\347\273\223.md" @@ -0,0 +1,26 @@ +## 一些总结: + +- 一层一层的封装,合理运用组合、继承类或接口来赋予、增强类的相应功能 + +- **接口的运用**: + - 通过暴露接口方法来进行非侵入式嵌入(例如:暴露BeanPostProcessor接口,实现该接口的类会优先于普通bean的实例化并可在bean实例化前对bean做一些初始化操作,例:aop织入) + - BeanFactoryAware接口暴露了获取beanFactory的能力,继承该接口的类拥有操作beanFactory的能力,也就能具体的操作bean了。 + +- **模板方法模式以及hook方法的应用**: +  例如: 在AbstractBeanFactory中规范了bean的加载,实例化,初始化,获取的过程。AutowireCapableBeanFactory里实现了hook方法(applyPropertyValues方法),该方法在AbstractBeanFactory#initializeBean方法中调用,AbstractBeanFactory中有默认的hook方法空实现。 + +- **工厂方法模式的应用**:例如:BeanFactory#getBean,由子类决定怎样去获取bean并在获取时进行相关操作。工厂方法把实例化推迟到子类。 + +- **外观(门面)模式的运用**:ClassPathXmlApplicationContext对 Resouce 、 BeanFactory、BeanDefinition +进行了功能的封装,解决 根据地址获取资源通过 IoC 容器注册bean定义并实例化,初始化bean的问题,并提供简单运用他们的方法。 + +- **代理模式的运用**: + - 通过jdk的动态代理:jdk的动态代理是基于接口的,必须实现了某一个或多个任意接口才可以被代理,并且只有这些接口中的方法会被代理。 + - 通过cglib动态代理:cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中的方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。 + +- **单例模式的运用**: + - tiny-spring默认是单例bean,在AbstractApplicationContext#refresh里注册bean定义,初始化后,默认用单例形式实例化bean:preInstantiateSingletons方法里获取beanDefinition的name后通过getBean(name)方法实例化bean,下次再getBean(name)时会先检查该name的beanDefinition里的bean是否已经实例化,如果已经实例化了,则返回那个bean的引用而不是再实例化一个新的bean返回 + - 标准单例模式中一般的实现方式是:第一次通过getInstance(双重检查)实例化该类的对象并保存,下次再getInstance时返回该对象。 + +- **策略模式**: +  这里有个想法,看ClassPathXMLApplicationContext构造方法可以知道是默认用自动装配的策略,在这里可以另外自己写个类继承AbstractBeanFactory,重写applyPropertyValues方法实现装配策略,在初始化的时候就可以选择不同的装配策略了。 \ No newline at end of file diff --git "a/src+/\351\203\250\345\210\206aopalliance\345\210\206\346\236\220/Advice.java" "b/src+/\351\203\250\345\210\206aopalliance\345\210\206\346\236\220/Advice.java" new file mode 100644 index 0000000..2e4cb4f --- /dev/null +++ "b/src+/\351\203\250\345\210\206aopalliance\345\210\206\346\236\220/Advice.java" @@ -0,0 +1,11 @@ +package org.aopalliance.aop; + +/** + * Tag interface for Advice. Implementations can be any type + * of advice, such as Interceptors. + * @author Rod Johnson + * @version $Id: Advice.java,v 1.1 2004/03/19 17:02:16 johnsonr Exp $ + */ +public interface Advice { + +} \ No newline at end of file diff --git "a/src+/\351\203\250\345\210\206aopalliance\345\210\206\346\236\220/Interceptor.java" "b/src+/\351\203\250\345\210\206aopalliance\345\210\206\346\236\220/Interceptor.java" new file mode 100644 index 0000000..04d67c4 --- /dev/null +++ "b/src+/\351\203\250\345\210\206aopalliance\345\210\206\346\236\220/Interceptor.java" @@ -0,0 +1,51 @@ +package org.aopalliance.intercept; + +import org.aopalliance.aop.Advice; + +/** + * This interface represents a generic interceptor. + * + *

A generic interceptor can intercept runtime events that occur + * within a base program. Those events are materialized by (reified + * in) joinpoints. Runtime joinpoints can be invocations, field + * access, exceptions... + * + *

This interface is not used directly. Use the the sub-interfaces + * to intercept specific events. For instance, the following class + * implements some specific interceptors in order to implement a + * debugger: + * + *

 
+ * class DebuggingInterceptor implements MethodInterceptor,  
+ *     ConstructorInterceptor, FieldInterceptor { 
+ * 
+ *   Object invoke(MethodInvocation i) throws Throwable { 
+ *     debug(i.getMethod(), i.getThis(), i.getArgs()); 
+ *     return i.proceed(); 
+ *   } 
+ * 
+ *   Object construct(ConstructorInvocation i) throws Throwable { 
+ *     debug(i.getConstructor(), i.getThis(), i.getArgs()); 
+ *     return i.proceed(); 
+ *   } 
+ *  
+ *   Object get(FieldAccess fa) throws Throwable { 
+ *     debug(fa.getField(), fa.getThis(), null); 
+ *     return fa.proceed(); 
+ *   } 
+ * 
+ *   Object set(FieldAccess fa) throws Throwable { 
+ *     debug(fa.getField(), fa.getThis(), fa.getValueToSet()); 
+ *     return fa.proceed(); 
+ *   } 
+ * 
+ *   void debug(AccessibleObject ao, Object this, Object value) { 
+ *     ... 
+ *   } 
+ * } 
+ * 
+ * + * @see Joinpoint */ + +public interface Interceptor extends Advice { +} \ No newline at end of file diff --git "a/src+/\351\203\250\345\210\206aopalliance\345\210\206\346\236\220/Invocation.java" "b/src+/\351\203\250\345\210\206aopalliance\345\210\206\346\236\220/Invocation.java" new file mode 100644 index 0000000..b0def5d --- /dev/null +++ "b/src+/\351\203\250\345\210\206aopalliance\345\210\206\346\236\220/Invocation.java" @@ -0,0 +1,21 @@ +package org.aopalliance.intercept; + +/** + * This interface represents an invocation in the program. + * + *

An invocation is a joinpoint and can be intercepted by an + * interceptor. + * + * @author Rod Johnson */ + +public interface Invocation extends Joinpoint { + + /** + * Get the arguments as an array object. + * It is possible to change element values within this + * array to change the arguments. + * + * @return the argument of the invocation */ + Object[] getArguments(); + +} \ No newline at end of file diff --git "a/src+/\351\203\250\345\210\206aopalliance\345\210\206\346\236\220/Joinpoint.java" "b/src+/\351\203\250\345\210\206aopalliance\345\210\206\346\236\220/Joinpoint.java" new file mode 100644 index 0000000..258f256 --- /dev/null +++ "b/src+/\351\203\250\345\210\206aopalliance\345\210\206\346\236\220/Joinpoint.java" @@ -0,0 +1,53 @@ +package org.aopalliance.intercept; + +import java.lang.reflect.AccessibleObject; + +/** + * This interface represents a generic runtime joinpoint (in the AOP + * terminology). + * + *

A runtime joinpoint is an event that occurs on a static + * joinpoint (i.e. a location in a the program). For instance, an + * invocation is the runtime joinpoint on a method (static joinpoint). + * The static part of a given joinpoint can be generically retrieved + * using the {@link #getStaticPart()} method. + * + *

In the context of an interception framework, a runtime joinpoint + * is then the reification of an access to an accessible object (a + * method, a constructor, a field), i.e. the static part of the + * joinpoint. It is passed to the interceptors that are installed on + * the static joinpoint. + * + * @see Interceptor */ + +public interface Joinpoint { + + /** + * Proceeds to the next interceptor in the chain. + * + *

The implementation and the semantics of this method depends + * on the actual joinpoint type (see the children interfaces). + * + * @return see the children interfaces' proceed definition. + * + * @throws Throwable if the joinpoint throws an exception. */ + Object proceed() throws Throwable; + + /** + * Returns the object that holds the current joinpoint's static + * part. + * + *

For instance, the target object for an invocation. + * + * @return the object (can be null if the accessible object is + * static). */ + Object getThis(); + + /** + * Returns the static part of this joinpoint. + * + *

The static part is an accessible object on which a chain of + * interceptors are installed. */ + AccessibleObject getStaticPart(); + +} \ No newline at end of file diff --git "a/src+/\351\203\250\345\210\206aopalliance\345\210\206\346\236\220/MethodInterceptor.java" "b/src+/\351\203\250\345\210\206aopalliance\345\210\206\346\236\220/MethodInterceptor.java" new file mode 100644 index 0000000..80f80a9 --- /dev/null +++ "b/src+/\351\203\250\345\210\206aopalliance\345\210\206\346\236\220/MethodInterceptor.java" @@ -0,0 +1,39 @@ +package org.aopalliance.intercept; + +/** + * Intercepts calls on an interface on its way to the target. These + * are nested "on top" of the target. + * + *

The user should implement the {@link #invoke(MethodInvocation)} + * method to modify the original behavior. E.g. the following class + * implements a tracing interceptor (traces all the calls on the + * intercepted method(s)): + * + *

 
+ * class TracingInterceptor implements MethodInterceptor { 
+ *   Object invoke(MethodInvocation i) throws Throwable { 
+ *     System.out.println("method "+i.getMethod()+" is called on "+ 
+ *                        i.getThis()+" with args "+i.getArguments()); 
+ *     Object ret=i.proceed(); 
+ *     System.out.println("method "+i.getMethod()+" returns "+ret); 
+ *     return ret; 
+ *   } 
+ * } 
+ * 
*/ + +public interface MethodInterceptor extends Interceptor { + + /** + * Implement this method to perform extra treatments before and + * after the invocation. Polite implementations would certainly + * like to invoke {@link Joinpoint#proceed()}. + * + * @param invocation the method invocation joinpoint + * @return the result of the call to {@link + * Joinpoint#proceed()}, might be intercepted by the + * interceptor. + * + * @throws Throwable if the interceptors or the + * target-object throws an exception. */ + Object invoke(MethodInvocation invocation) throws Throwable; +} \ No newline at end of file diff --git "a/src+/\351\203\250\345\210\206aopalliance\345\210\206\346\236\220/MethodInvocation.java" "b/src+/\351\203\250\345\210\206aopalliance\345\210\206\346\236\220/MethodInvocation.java" new file mode 100644 index 0000000..bd52f38 --- /dev/null +++ "b/src+/\351\203\250\345\210\206aopalliance\345\210\206\346\236\220/MethodInvocation.java" @@ -0,0 +1,26 @@ +package org.aopalliance.intercept; + +import java.lang.reflect.Method; + +/** + * Description of an invocation to a method, given to an interceptor + * upon method-call. + * + *

A method invocation is a joinpoint and can be intercepted by a method + * interceptor. + * + * @see MethodInterceptor */ +public interface MethodInvocation extends Invocation +{ + + /** + * Gets the method being called. + * + *

This method is a frienly implementation of the {@link + * Joinpoint#getStaticPart()} method (same result). + * + * @return the method being called. + */ + Method getMethod(); + +} \ No newline at end of file diff --git "a/src+/\351\203\250\345\210\206aopalliance\345\210\206\346\236\220/\347\233\270\345\205\263\347\261\273\345\233\276.png" "b/src+/\351\203\250\345\210\206aopalliance\345\210\206\346\236\220/\347\233\270\345\205\263\347\261\273\345\233\276.png" new file mode 100644 index 0000000..b55431b Binary files /dev/null and "b/src+/\351\203\250\345\210\206aopalliance\345\210\206\346\236\220/\347\233\270\345\205\263\347\261\273\345\233\276.png" differ