diff --git a/README.md b/README.md index c4aa73a..03e8522 100644 --- a/README.md +++ b/README.md @@ -2,5 +2,5 @@ * Support IOC(DONE) * Support Spring Event(DONE) -* Support Spring AOP(TODO) +* Support Spring AOP(DOING) * Support Annotation Driven(TODO) \ No newline at end of file diff --git a/pom.xml b/pom.xml index 99d58f3..c2abcf9 100644 --- a/pom.xml +++ b/pom.xml @@ -55,5 +55,18 @@ cglib 3.3.0 + + + org.aspectj + aspectjrt + 1.9.21.1 + + + + org.aspectj + aspectjweaver + 1.8.9 + + \ No newline at end of file diff --git a/src/main/java/cn/abelib/springframework/aop/Advisor.java b/src/main/java/cn/abelib/springframework/aop/Advisor.java new file mode 100644 index 0000000..5697dae --- /dev/null +++ b/src/main/java/cn/abelib/springframework/aop/Advisor.java @@ -0,0 +1,17 @@ +package cn.abelib.springframework.aop; + +import org.aopalliance.aop.Advice; + +/** + * @author abel.huang + * @version 1.0 + * @date 2024/3/31 下午 10:35 + */ +public interface Advisor { + + /** + * Return the advice part of this aspect. An advice may be an + * interceptor, a before advice, a throws advice, etc. + */ + Advice getAdvice(); +} diff --git a/src/main/java/cn/abelib/springframework/aop/BeforeAdvice.java b/src/main/java/cn/abelib/springframework/aop/BeforeAdvice.java new file mode 100644 index 0000000..88c4895 --- /dev/null +++ b/src/main/java/cn/abelib/springframework/aop/BeforeAdvice.java @@ -0,0 +1,14 @@ +package cn.abelib.springframework.aop; + +import org.aopalliance.aop.Advice; + +/** + * Common marker interface for before advice + * + * @author abel.huang + * @version 1.0 + * @date 2024/3/31 下午 10:27 + */ +public interface BeforeAdvice extends Advice { + +} \ No newline at end of file diff --git a/src/main/java/cn/abelib/springframework/aop/ClassFilter.java b/src/main/java/cn/abelib/springframework/aop/ClassFilter.java new file mode 100644 index 0000000..db4deaf --- /dev/null +++ b/src/main/java/cn/abelib/springframework/aop/ClassFilter.java @@ -0,0 +1,17 @@ +package cn.abelib.springframework.aop; + +/** + * Filter that restricts matching of a pointcut or introduction to + * a given set of target classes. + * + * @author abel.huang + * @version 1.0 + * @date 2024/3/19 0:04 + */ +public interface ClassFilter { + + /** + * Should the pointcut apply to the given interface or target class? + */ + boolean matches(Class clazz); +} diff --git a/src/main/java/cn/abelib/springframework/aop/MethodBeforeAdvice.java b/src/main/java/cn/abelib/springframework/aop/MethodBeforeAdvice.java new file mode 100644 index 0000000..6d5906d --- /dev/null +++ b/src/main/java/cn/abelib/springframework/aop/MethodBeforeAdvice.java @@ -0,0 +1,20 @@ +package cn.abelib.springframework.aop; + +import java.lang.reflect.Method; + +/** + * + * Advice invoked before a method is invoked. Such advices cannot + * prevent the method call proceeding, unless they throw a Throwable. + * + * @author abel.huang + * @version 1.0 + * @date 2024/3/31 下午 10:33 + */ +public interface MethodBeforeAdvice extends BeforeAdvice { + + /** + * Callback before a given method is invoked. + */ + void before(Method method, Object[] args, Object target) throws Throwable; +} diff --git a/src/main/java/cn/abelib/springframework/aop/MethodMatcher.java b/src/main/java/cn/abelib/springframework/aop/MethodMatcher.java new file mode 100644 index 0000000..1f6f409 --- /dev/null +++ b/src/main/java/cn/abelib/springframework/aop/MethodMatcher.java @@ -0,0 +1,33 @@ +package cn.abelib.springframework.aop; + +import java.lang.reflect.Method; + +/** + * Part of a {@link Pointcut}: Checks whether the target method is eligible for advice. + * + * @author abel.huang + * @version 1.0 + * @date 2024/3/19 0:05 + */ +public interface MethodMatcher { + + /** + * Perform static checking whether the given method matches. If this + * returns {@code false} or if the {@link #isRuntime()} method + * returns {@code false}, no runtime check (i.e. no. + */ + boolean matches(Method method, Class targetClass); + + /** + * Is this MethodMatcher dynamic, that is, must a final call be made on the + * {@link #matches(java.lang.reflect.Method, Class, Object[])} method at + * runtime even if the 2-arg matches method returns {@code true} + */ + boolean isRuntime(); + + /** + * Check whether there a runtime (dynamic) match for this method, + * which must have matched statically. + */ + boolean matches(Method method, Class targetClass, Object... args); +} diff --git a/src/main/java/cn/abelib/springframework/aop/Pointcut.java b/src/main/java/cn/abelib/springframework/aop/Pointcut.java new file mode 100644 index 0000000..fe870c1 --- /dev/null +++ b/src/main/java/cn/abelib/springframework/aop/Pointcut.java @@ -0,0 +1,21 @@ +package cn.abelib.springframework.aop; + +/** + * Core Spring pointcut abstraction. + * + * @author abel.huang + * @version 1.0 + * @date 2024/3/19 0:03 + */ +public interface Pointcut { + + /** + * Return the ClassFilter for this pointcut. + */ + ClassFilter getClassFilter(); + + /** + * Return the MethodMatcher for this pointcut. + */ + MethodMatcher getMethodMatcher(); +} diff --git a/src/main/java/cn/abelib/springframework/aop/PointcutAdvisor.java b/src/main/java/cn/abelib/springframework/aop/PointcutAdvisor.java new file mode 100644 index 0000000..a082344 --- /dev/null +++ b/src/main/java/cn/abelib/springframework/aop/PointcutAdvisor.java @@ -0,0 +1,18 @@ +package cn.abelib.springframework.aop; + +/** + * Superinterface for all Advisors that are driven by a pointcut. + * This covers nearly all advisors except introduction advisors, + * for which method-level matching doesn't apply. + * + * @author abel.huang + * @version 1.0 + * @date 2024/3/31 下午 10:37 + */ +public interface PointcutAdvisor extends Advisor { + + /** + * Get the Pointcut that drives this advisor. + */ + Pointcut getPointcut(); +} diff --git a/src/main/java/cn/abelib/springframework/aop/TargetSource.java b/src/main/java/cn/abelib/springframework/aop/TargetSource.java new file mode 100644 index 0000000..bf4bb22 --- /dev/null +++ b/src/main/java/cn/abelib/springframework/aop/TargetSource.java @@ -0,0 +1,33 @@ +package cn.abelib.springframework.aop; + +/** + * @author abel.huang + * @version 1.0 + * @date 2024/3/25 23:41 + */ +public class TargetSource { + + private final Object target; + + public TargetSource(Object target) { + this.target = target; + } + + /** + * Return the type of targets returned by this {@link TargetSource}. + *

Can return null, although certain usages of a + * TargetSource might just work with a predetermined + * target class. + */ + public Class[] getTargetClass(){ + return this.target.getClass().getInterfaces(); + } + + /** + * Return a target instance. Invoked immediately before the + * AOP framework calls the "target" of an AOP method invocation. + */ + public Object getTarget(){ + return this.target; + } +} diff --git a/src/main/java/cn/abelib/springframework/aop/aspectj/AspectJExpressionPointcut.java b/src/main/java/cn/abelib/springframework/aop/aspectj/AspectJExpressionPointcut.java new file mode 100644 index 0000000..a83ce29 --- /dev/null +++ b/src/main/java/cn/abelib/springframework/aop/aspectj/AspectJExpressionPointcut.java @@ -0,0 +1,69 @@ +package cn.abelib.springframework.aop.aspectj; + +import cn.abelib.springframework.aop.ClassFilter; +import cn.abelib.springframework.aop.MethodMatcher; +import cn.abelib.springframework.aop.Pointcut; +import org.aspectj.weaver.tools.PointcutExpression; +import org.aspectj.weaver.tools.PointcutParser; +import org.aspectj.weaver.tools.PointcutPrimitive; + +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Set; + +/** + * @author abel.huang + * @version 1.0 + * @date 2024/3/19 22:44 + */ +public class AspectJExpressionPointcut implements Pointcut, ClassFilter, MethodMatcher { + private static final Set SUPPORTED_PRIMITIVES = new HashSet<>(); + + static { + SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION); + } + + private final PointcutExpression pointcutExpression; + + public AspectJExpressionPointcut(String expression) { + PointcutParser pointcutParser = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(SUPPORTED_PRIMITIVES, this.getClass().getClassLoader()); + pointcutExpression = pointcutParser.parsePointcutExpression(expression); + } + + @Override + public boolean matches(Class clazz) { + return pointcutExpression.couldMatchJoinPointsInType(clazz); + } + + @Override + public boolean matches(Method method, Class targetClass) { + return pointcutExpression.matchesMethodExecution(method).alwaysMatches(); + } + + @Override + public boolean isRuntime() { + return this.pointcutExpression.mayNeedDynamicTest(); + } + + /** + * TODO + * @param method + * @param targetClass + * @param args + * @return + */ + @Override + public boolean matches(Method method, Class targetClass, Object... args) { + return false; + } + + @Override + public ClassFilter getClassFilter() { + return this; + } + + @Override + public MethodMatcher getMethodMatcher() { + return this; + } +} diff --git a/src/main/java/cn/abelib/springframework/aop/aspectj/AspectJExpressionPointcutAdvisor.java b/src/main/java/cn/abelib/springframework/aop/aspectj/AspectJExpressionPointcutAdvisor.java new file mode 100644 index 0000000..5ca3d7e --- /dev/null +++ b/src/main/java/cn/abelib/springframework/aop/aspectj/AspectJExpressionPointcutAdvisor.java @@ -0,0 +1,43 @@ +package cn.abelib.springframework.aop.aspectj; + +import cn.abelib.springframework.aop.Pointcut; +import cn.abelib.springframework.aop.PointcutAdvisor; +import org.aopalliance.aop.Advice; + +/** + * @author abel.huang + * @version 1.0 + * @date 2024/3/31 下午 10:38 + */ +public class AspectJExpressionPointcutAdvisor implements PointcutAdvisor { + + private AspectJExpressionPointcut pointcut; + + private Advice advice; + + private String expression; + + public void setExpression(String expression){ + this.expression = expression; + } + + public String getExpression() { + return expression; + } + + @Override + public Pointcut getPointcut() { + if (null == pointcut) { + pointcut = new AspectJExpressionPointcut(expression); + } + return pointcut; + } + + public Advice getAdvice() { + return advice; + } + + public void setAdvice(Advice advice){ + this.advice = advice; + } +} diff --git a/src/main/java/cn/abelib/springframework/aop/framework/AdvisedSupport.java b/src/main/java/cn/abelib/springframework/aop/framework/AdvisedSupport.java new file mode 100644 index 0000000..aa86a16 --- /dev/null +++ b/src/main/java/cn/abelib/springframework/aop/framework/AdvisedSupport.java @@ -0,0 +1,53 @@ +package cn.abelib.springframework.aop.framework; + +import cn.abelib.springframework.aop.MethodMatcher; +import cn.abelib.springframework.aop.TargetSource; +import org.aopalliance.intercept.MethodInterceptor; + +/** + * @author abel.huang + * @version 1.0 + * @date 2024/3/19 22:43 + */ +public class AdvisedSupport { + // ProxyConfig + private boolean proxyTargetClass = false; + + private TargetSource targetSource; + + private MethodInterceptor methodInterceptor; + + private MethodMatcher methodMatcher; + + public boolean isProxyTargetClass() { + return proxyTargetClass; + } + + public void setProxyTargetClass(boolean proxyTargetClass) { + this.proxyTargetClass = proxyTargetClass; + } + + 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/cn/abelib/springframework/aop/framework/AopProxy.java b/src/main/java/cn/abelib/springframework/aop/framework/AopProxy.java new file mode 100644 index 0000000..eded90a --- /dev/null +++ b/src/main/java/cn/abelib/springframework/aop/framework/AopProxy.java @@ -0,0 +1,22 @@ +package cn.abelib.springframework.aop.framework; + +/** + * Delegate interface for a configured AOP proxy, allowing for the creation + * of actual proxy objects. + * + * @author abel.huang + * @version 1.0 + * @date 2024/3/19 22:44 + */ +public interface AopProxy { + + /** + * Create a new proxy object. + */ + Object getProxy(); + + /** + * Create a new proxy object. + */ + Object getProxy(ClassLoader classLoader); +} diff --git a/src/main/java/cn/abelib/springframework/aop/framework/CglibAopProxy.java b/src/main/java/cn/abelib/springframework/aop/framework/CglibAopProxy.java new file mode 100644 index 0000000..cb18fc3 --- /dev/null +++ b/src/main/java/cn/abelib/springframework/aop/framework/CglibAopProxy.java @@ -0,0 +1,71 @@ +package cn.abelib.springframework.aop.framework; + +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; + +import java.lang.reflect.Method; +import java.util.Objects; + +/** + * @author abel.huang + * @version 1.0 + * @date 2024/3/19 22:46 + */ +public class CglibAopProxy implements AopProxy { + + private final AdvisedSupport advised; + + public CglibAopProxy(AdvisedSupport advised) { + this.advised = advised; + } + + @Override + public Object getProxy() { + return this.getProxy(null); + } + + @Override + public Object getProxy(ClassLoader classLoader) { + Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(advised.getTargetSource().getTarget().getClass()); + enhancer.setInterfaces(advised.getTargetSource().getTargetClass()); + if (Objects.nonNull(classLoader)) { + enhancer.setClassLoader(classLoader); + } + enhancer.setCallback(new DynamicAdvisedInterceptor(advised)); + return enhancer.create(); + } + + private static class DynamicAdvisedInterceptor implements MethodInterceptor { + private final AdvisedSupport advised; + + public DynamicAdvisedInterceptor(AdvisedSupport advised) { + this.advised = advised; + } + + @Override + public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { + CglibMethodInvocation methodInvocation = new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, objects, methodProxy); + if (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) { + return advised.getMethodInterceptor().invoke(methodInvocation); + } + return methodInvocation.proceed(); + } + } + + private static class CglibMethodInvocation extends ReflectiveMethodInvocation { + + private MethodProxy methodProxy; + + public CglibMethodInvocation(Object target, Method method, Object[] objects, MethodProxy methodProxy) { + super(target, method, objects); + this.methodProxy = methodProxy; + } + + @Override + public Object proceed() throws Throwable { + return this.methodProxy.invoke(this.target, this.arguments); + } + } +} diff --git a/src/main/java/cn/abelib/springframework/aop/framework/JdkDynamicAopProxy.java b/src/main/java/cn/abelib/springframework/aop/framework/JdkDynamicAopProxy.java new file mode 100644 index 0000000..a9704d7 --- /dev/null +++ b/src/main/java/cn/abelib/springframework/aop/framework/JdkDynamicAopProxy.java @@ -0,0 +1,40 @@ +package cn.abelib.springframework.aop.framework; + +import org.aopalliance.intercept.MethodInterceptor; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * @author abel.huang + * @version 1.0 + * @date 2024/3/19 22:46 + */ +public class JdkDynamicAopProxy implements AopProxy, InvocationHandler { + + private final AdvisedSupport advised; + + public JdkDynamicAopProxy(AdvisedSupport advised) { + this.advised = advised; + } + + @Override + public Object getProxy() { + return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), advised.getTargetSource().getTargetClass(), this); + } + + @Override + public Object getProxy(ClassLoader classLoader) { + return Proxy.newProxyInstance(classLoader, advised.getTargetSource().getTargetClass(), this); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) { + MethodInterceptor methodInterceptor = advised.getMethodInterceptor(); + return methodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(), method, args)); + } + return method.invoke(advised.getTargetSource().getTarget(), args); + } +} diff --git a/src/main/java/cn/abelib/springframework/aop/framework/ProxyFactory.java b/src/main/java/cn/abelib/springframework/aop/framework/ProxyFactory.java new file mode 100644 index 0000000..b3b1527 --- /dev/null +++ b/src/main/java/cn/abelib/springframework/aop/framework/ProxyFactory.java @@ -0,0 +1,28 @@ +package cn.abelib.springframework.aop.framework; + +/** + * @author abel.huang + * @version 1.0 + * @date 2024/4/6 下午 11:43 + */ +public class ProxyFactory { + + private AdvisedSupport advisedSupport; + + public ProxyFactory(AdvisedSupport advisedSupport) { + this.advisedSupport = advisedSupport; + } + + public Object getProxy() { + return createAopProxy().getProxy(); + } + + private AopProxy createAopProxy() { + if (advisedSupport.isProxyTargetClass()) { + return new CglibAopProxy(advisedSupport); + } + + return new JdkDynamicAopProxy(advisedSupport); + } + +} diff --git a/src/main/java/cn/abelib/springframework/aop/framework/ReflectiveMethodInvocation.java b/src/main/java/cn/abelib/springframework/aop/framework/ReflectiveMethodInvocation.java new file mode 100644 index 0000000..7b7f2fc --- /dev/null +++ b/src/main/java/cn/abelib/springframework/aop/framework/ReflectiveMethodInvocation.java @@ -0,0 +1,45 @@ +package cn.abelib.springframework.aop.framework; + +import org.aopalliance.intercept.MethodInvocation; + +import java.lang.reflect.Method; + +/** + * @author abel.huang + * @version 1.0 + * @date 2024/3/25 23:57 + */ +public class ReflectiveMethodInvocation implements MethodInvocation { + + protected final Object target; + + protected final Method method; + + protected final Object[] arguments; + + public ReflectiveMethodInvocation(Object target, Method method, Object[] arguments) { + this.target = target; + this.method = method; + this.arguments = arguments; + } + + @Override + public Method getMethod() { + return this.method; + } + + @Override + public Object[] getArguments() { + return this.arguments; + } + + @Override + public Object proceed() throws Throwable { + return method.invoke(target, arguments); + } + + @Override + public Object getThis() { + return target; + } +} diff --git a/src/main/java/cn/abelib/springframework/aop/framework/adapter/MethodBeforeAdviceInterceptor.java b/src/main/java/cn/abelib/springframework/aop/framework/adapter/MethodBeforeAdviceInterceptor.java new file mode 100644 index 0000000..8cf2533 --- /dev/null +++ b/src/main/java/cn/abelib/springframework/aop/framework/adapter/MethodBeforeAdviceInterceptor.java @@ -0,0 +1,33 @@ +package cn.abelib.springframework.aop.framework.adapter; + +import cn.abelib.springframework.aop.MethodBeforeAdvice; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +/** + * @author abel.huang + * @version 1.0 + * @date 2024/3/31 下午 10:40 + */ +public class MethodBeforeAdviceInterceptor implements MethodInterceptor { + private MethodBeforeAdvice advice; + + public MethodBeforeAdviceInterceptor(){} + + /** + * Create a new MethodBeforeAdviceInterceptor for the given advice. + */ + public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) { + this.advice = advice; + } + + @Override + public Object invoke(MethodInvocation methodInvocation) throws Throwable { + this.advice.before(methodInvocation.getMethod(), methodInvocation.getArguments(), methodInvocation.getThis() ); + return methodInvocation.proceed(); + } + + public void setAdvice(MethodBeforeAdvice advice) { + this.advice = advice; + } +} \ No newline at end of file diff --git a/src/main/java/cn/abelib/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java b/src/main/java/cn/abelib/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java new file mode 100644 index 0000000..b424e5a --- /dev/null +++ b/src/main/java/cn/abelib/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java @@ -0,0 +1,86 @@ +package cn.abelib.springframework.aop.framework.autoproxy; + +import cn.abelib.springframework.aop.Advisor; +import cn.abelib.springframework.aop.ClassFilter; +import cn.abelib.springframework.aop.Pointcut; +import cn.abelib.springframework.aop.TargetSource; +import cn.abelib.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor; +import cn.abelib.springframework.aop.framework.AdvisedSupport; +import cn.abelib.springframework.aop.framework.ProxyFactory; +import cn.abelib.springframework.beans.BeansException; +import cn.abelib.springframework.beans.factory.BeanFactory; +import cn.abelib.springframework.beans.factory.BeanFactoryAware; +import cn.abelib.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; +import cn.abelib.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.aopalliance.aop.Advice; +import org.aopalliance.intercept.MethodInterceptor; + +import java.util.Collection; + +/** + * @author abel.huang + * @version 1.0 + * @date 2024/3/31 下午 10:40 + */ +public class DefaultAdvisorAutoProxyCreator implements InstantiationAwareBeanPostProcessor, BeanFactoryAware { + + private DefaultListableBeanFactory beanFactory; + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = (DefaultListableBeanFactory) beanFactory; + } + + /** + * @param beanClass + * @param beanName + * @return + * @throws BeansException + */ + @Override + public Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException { + if (isInfrastructureClass(beanClass)) { + return null; + } + + Collection advisors = beanFactory.getBeansOfType(AspectJExpressionPointcutAdvisor.class).values(); + + for (AspectJExpressionPointcutAdvisor advisor : advisors) { + ClassFilter classFilter = advisor.getPointcut().getClassFilter(); + if (!classFilter.matches(beanClass)) continue; + + AdvisedSupport advisedSupport = new AdvisedSupport(); + + TargetSource targetSource = null; + try { + targetSource = new TargetSource(beanClass.getDeclaredConstructor().newInstance()); + } catch (Exception e) { + e.printStackTrace(); + } + advisedSupport.setTargetSource(targetSource); + advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice()); + advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher()); + advisedSupport.setProxyTargetClass(false); + + return new ProxyFactory(advisedSupport).getProxy(); + } + + return null; + } + + private boolean isInfrastructureClass(Class beanClass) { + return Advice.class.isAssignableFrom(beanClass) + || Pointcut.class.isAssignableFrom(beanClass) + || Advisor.class.isAssignableFrom(beanClass); + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + return bean; + } +} \ No newline at end of file diff --git a/src/main/java/cn/abelib/springframework/beans/factory/AbstractAutowireCapableBeanFactory.java b/src/main/java/cn/abelib/springframework/beans/factory/AbstractAutowireCapableBeanFactory.java index 5ea0094..bb9b9f1 100644 --- a/src/main/java/cn/abelib/springframework/beans/factory/AbstractAutowireCapableBeanFactory.java +++ b/src/main/java/cn/abelib/springframework/beans/factory/AbstractAutowireCapableBeanFactory.java @@ -4,6 +4,8 @@ import cn.abelib.springframework.beans.PropertyValue; import cn.abelib.springframework.beans.PropertyValues; import cn.abelib.springframework.beans.factory.config.AutowireCapableBeanFactory; +import cn.abelib.springframework.beans.factory.config.BeanDefinition; +import cn.abelib.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; import cn.abelib.springframework.beans.factory.support.AbstractBeanDefinition; import cn.abelib.springframework.beans.factory.config.BeanPostProcessor; import cn.abelib.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy; @@ -29,13 +31,19 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac protected Object createBean(String beanName, AbstractBeanDefinition beanDefinition, Object[] args) throws BeansException { Object bean; try { + bean = resolveBeforeInstantiation(beanName, beanDefinition); + if (null != bean) { + return bean; + } + bean = createBeanInstance(beanDefinition, beanName, args); applyPropertyValues(beanName, bean, beanDefinition); // initialize Bean bean = initializeBean(beanName, bean, beanDefinition); } catch (Exception e) { - throw new BeansException("Instantiation of bean failed", e); + System.err.printf("Instantiation of bean failed: %s%n", beanName); + throw new BeansException(String.format("Instantiation of bean failed: %s", beanName), e); } // 注册实现了 DisposableBean 接口的 Bean 对象 @@ -50,6 +58,29 @@ protected Object createBean(String beanName, AbstractBeanDefinition beanDefiniti return bean; } + /** + * todo 缺失property + * Apply before-instantiation post-processors, resolving whether there is a + * before-instantiation shortcut for the specified bean. + */ + protected Object resolveBeforeInstantiation(String beanName, AbstractBeanDefinition beanDefinition) { + Object bean = applyBeanPostProcessorsBeforeInstantiation(beanDefinition.getBeanClass(), beanName); + if (null != bean) { + bean = applyBeanPostProcessorsAfterInitialization(bean, beanName); + } + return bean; + } + + protected Object applyBeanPostProcessorsBeforeInstantiation(Class beanClass, String beanName) { + for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) { + if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) { + Object result = ((InstantiationAwareBeanPostProcessor) beanPostProcessor).postProcessBeforeInstantiation(beanClass, beanName); + if (null != result) return result; + } + } + return null; + } + protected void registerDisposableBeanIfNecessary(String beanName, Object bean, AbstractBeanDefinition beanDefinition) { // 非Singleton Bean不处理 if (!beanDefinition.isSingleton()) { diff --git a/src/main/java/cn/abelib/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java b/src/main/java/cn/abelib/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java new file mode 100644 index 0000000..c1bc087 --- /dev/null +++ b/src/main/java/cn/abelib/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java @@ -0,0 +1,17 @@ +package cn.abelib.springframework.beans.factory.config; + +import cn.abelib.springframework.beans.BeansException; + +/** + * @author abel.huang + * @version 1.0 + * @date 2024/4/7 下午 11:00 + */ +public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor { + /** + * Apply this BeanPostProcessor before the target bean gets instantiated. + * The returned bean object may be a proxy to use instead of the target bean, + * effectively suppressing default instantiation of the target bean. + */ + Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException; +} diff --git a/src/main/java/cn/abelib/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java b/src/main/java/cn/abelib/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java index 9d12898..e3ad981 100644 --- a/src/main/java/cn/abelib/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java +++ b/src/main/java/cn/abelib/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java @@ -23,7 +23,9 @@ public int hashCode() { return super.hashCode(); } }); - if (null == ctor) return enhancer.create(); + if (null == ctor) { + return enhancer.create(); + } return enhancer.create(ctor.getParameterTypes(), args); } } diff --git a/src/main/java/org/aopalliance/aop/Advice.java b/src/main/java/org/aopalliance/aop/Advice.java new file mode 100644 index 0000000..d36ee72 --- /dev/null +++ b/src/main/java/org/aopalliance/aop/Advice.java @@ -0,0 +1,13 @@ +package org.aopalliance.aop; + +/** + * + * Tag interface for Advice. Implementations can be any type + * of advice, such as Interceptors. + * + * @author abel.huang + * @version 1.0 + * @date 2024/3/31 下午 10:31 + */ +public interface Advice { +} diff --git a/src/main/java/org/aopalliance/intercept/Interceptor.java b/src/main/java/org/aopalliance/intercept/Interceptor.java new file mode 100644 index 0000000..f7ab400 --- /dev/null +++ b/src/main/java/org/aopalliance/intercept/Interceptor.java @@ -0,0 +1,14 @@ +package org.aopalliance.intercept; + +import org.aopalliance.aop.Advice; + +/** + * This interface represents a generic interceptor. + * + * @author abel.huang + * @version 1.0 + * @date 2024/4/6 下午 11:39 + */ +public interface Interceptor extends Advice { + +} \ No newline at end of file diff --git a/src/main/java/org/aopalliance/intercept/Invocation.java b/src/main/java/org/aopalliance/intercept/Invocation.java new file mode 100644 index 0000000..e3452c9 --- /dev/null +++ b/src/main/java/org/aopalliance/intercept/Invocation.java @@ -0,0 +1,16 @@ +package org.aopalliance.intercept; + +/** + * @author abel.huang + * @version 1.0 + * @date 2024/4/6 下午 7:44 + */ +public interface Invocation extends Joinpoint { + + /** + * Get the arguments as an array object. + * It is possible to change element values within this + */ + Object[] getArguments(); + +} \ No newline at end of file diff --git a/src/main/java/org/aopalliance/intercept/Joinpoint.java b/src/main/java/org/aopalliance/intercept/Joinpoint.java new file mode 100644 index 0000000..41e7be1 --- /dev/null +++ b/src/main/java/org/aopalliance/intercept/Joinpoint.java @@ -0,0 +1,22 @@ +package org.aopalliance.intercept; + +/** + * This interface represents a generic runtime joinpoint (in the AOP + * terminology). + * @author abel.huang + * @version 1.0 + * @date 2024/4/6 下午 11:34 + */ +public interface Joinpoint { + + /** + * Proceed to the next interceptor in the chain. + */ + Object proceed() throws Throwable; + + /** + * Return the object that holds the current joinpoint's static part. + */ + Object getThis(); + +} diff --git a/src/main/java/org/aopalliance/intercept/MethodInterceptor.java b/src/main/java/org/aopalliance/intercept/MethodInterceptor.java new file mode 100644 index 0000000..8ee8410 --- /dev/null +++ b/src/main/java/org/aopalliance/intercept/MethodInterceptor.java @@ -0,0 +1,18 @@ +package org.aopalliance.intercept; + +/** + * Intercepts calls on an interface on its way to the target. These + * are nested "on top" of the target. + * + * @author abel.huang + * @version 1.0 + * @date 2024/4/6 下午 11:39 + */ +public interface MethodInterceptor extends Interceptor { + /** + * Implement this method to perform extra treatments before and + * after the invocation. + */ + Object invoke(MethodInvocation invocation) throws Throwable; + +} diff --git a/src/main/java/org/aopalliance/intercept/MethodInvocation.java b/src/main/java/org/aopalliance/intercept/MethodInvocation.java new file mode 100644 index 0000000..121d55d --- /dev/null +++ b/src/main/java/org/aopalliance/intercept/MethodInvocation.java @@ -0,0 +1,17 @@ +package org.aopalliance.intercept; + +import java.lang.reflect.Method; + +/** + * @author abel.huang + * @version 1.0 + * @date 2024/4/6 下午 7:43 + */ +public interface MethodInvocation extends Invocation { + + /** + * Get the method being called. + */ + Method getMethod(); + +} diff --git a/src/main/resources/spring_aop.xml b/src/main/resources/spring_aop.xml new file mode 100644 index 0000000..44ad062 --- /dev/null +++ b/src/main/resources/spring_aop.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/cn/abelib/springframework/HelloAopService.java b/src/test/java/cn/abelib/springframework/HelloAopService.java new file mode 100644 index 0000000..6622eee --- /dev/null +++ b/src/test/java/cn/abelib/springframework/HelloAopService.java @@ -0,0 +1,35 @@ +package cn.abelib.springframework; + +/** + * @author abel.huang + * @version 1.0 + * @date 2024/4/10 下午 11:30 + */ +public class HelloAopService implements IHelloService { + private String name; + + public HelloAopService() {} + + public HelloAopService(String name) { + this.name = name; + } + + @Override + public void sayHello() { + System.out.println("Hello " + name); + } + + @Override + public String hello() { + return "Hello " + name; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/src/test/java/cn/abelib/springframework/HelloServiceAdvice.java b/src/test/java/cn/abelib/springframework/HelloServiceAdvice.java new file mode 100644 index 0000000..54f4028 --- /dev/null +++ b/src/test/java/cn/abelib/springframework/HelloServiceAdvice.java @@ -0,0 +1,19 @@ +package cn.abelib.springframework; + +import cn.abelib.springframework.aop.MethodBeforeAdvice; + +import java.lang.reflect.Method; + +/** + * @author abel.huang + * @version 1.0 + * @date 2024/4/8 下午 11:13 + */ +public class HelloServiceAdvice implements MethodBeforeAdvice { + public HelloServiceAdvice() {} + + @Override + public void before(Method method, Object[] args, Object target) throws Throwable { + System.out.println("Intercept method name: " + method.getName()); + } +} diff --git a/src/test/java/cn/abelib/springframework/HelloServiceInterceptor.java b/src/test/java/cn/abelib/springframework/HelloServiceInterceptor.java new file mode 100644 index 0000000..cfcc0c3 --- /dev/null +++ b/src/test/java/cn/abelib/springframework/HelloServiceInterceptor.java @@ -0,0 +1,27 @@ +package cn.abelib.springframework; + + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +/** + * @author abel.huang + * @version 1.0 + * @date 2024/3/26 22:55 + */ +public class HelloServiceInterceptor implements MethodInterceptor { + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + long start = System.currentTimeMillis(); + try { + return invocation.proceed(); + } finally { + System.out.println("AOP start"); + System.out.println("Method Name:" + invocation.getMethod()); + System.out.println("Spent Time:" + (System.currentTimeMillis() - start) + "ms"); + System.out.println("AOP end\r\n"); + } + } +} + diff --git a/src/test/java/cn/abelib/springframework/aop/AopTest.java b/src/test/java/cn/abelib/springframework/aop/AopTest.java new file mode 100644 index 0000000..4b09371 --- /dev/null +++ b/src/test/java/cn/abelib/springframework/aop/AopTest.java @@ -0,0 +1,38 @@ +package cn.abelib.springframework.aop; + +import cn.abelib.springframework.HelloService; +import cn.abelib.springframework.HelloServiceInterceptor; +import cn.abelib.springframework.IHelloService; +import cn.abelib.springframework.aop.aspectj.AspectJExpressionPointcut; +import cn.abelib.springframework.aop.framework.AdvisedSupport; +import cn.abelib.springframework.aop.framework.CglibAopProxy; +import cn.abelib.springframework.aop.framework.JdkDynamicAopProxy; +import org.junit.Test; + +/** + * @author abel.huang + * @version 1.0 + * @date 2024/3/26 22:59 + */ +public class AopTest { + + @Test + public void testDynamicAop() { + // 目标对象 + HelloService helloService = new HelloService("Abel"); + + // 组装代理信息 + AdvisedSupport advisedSupport = new AdvisedSupport(); + advisedSupport.setTargetSource(new TargetSource(helloService)); + advisedSupport.setMethodInterceptor(new HelloServiceInterceptor()); + advisedSupport.setMethodMatcher(new AspectJExpressionPointcut("execution(* cn.abelib.springframework.IHelloService.*(..))")); + + // JdkDynamicAopProxy + IHelloService jdkProxy = (IHelloService) new JdkDynamicAopProxy(advisedSupport).getProxy(); + System.out.println("jdkProxy result: " + jdkProxy.hello()); + + // Cglib2AopProxy + IHelloService cglibProxy = (IHelloService) new CglibAopProxy(advisedSupport).getProxy(); + cglibProxy.sayHello(); + } +} diff --git a/src/test/java/cn/abelib/springframework/aop/SpringAopTest.java b/src/test/java/cn/abelib/springframework/aop/SpringAopTest.java new file mode 100644 index 0000000..a198134 --- /dev/null +++ b/src/test/java/cn/abelib/springframework/aop/SpringAopTest.java @@ -0,0 +1,22 @@ +package cn.abelib.springframework.aop; + +import cn.abelib.springframework.HelloAopService; +import cn.abelib.springframework.IHelloService; +import cn.abelib.springframework.context.support.ClassPathXmlApplicationContext; +import org.junit.Test; + +/** + * @author abel.huang + * @version 1.0 + * @date 2024/4/8 下午 11:22 + */ +public class SpringAopTest { + + @Test + public void testAop() { + ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring_aop.xml"); + IHelloService helloService = applicationContext.getBean("helloService", HelloAopService.class); + + System.out.println("result: " + helloService.hello()); + } +}