青涩知夏 4 years ago
parent
commit
b81e37bc6c

+ 17 - 0
src/main/java/cn/nosum/demo/aspect/LogAspect.java

@@ -0,0 +1,17 @@
+package cn.nosum.demo.aspect;
+
+
+/**
+ * 切面类
+ */
+public class LogAspect {
+    public void before(){
+        System.err.println("Invoker Before Method!!!");
+    }
+    public void after(){
+        System.err.println("Invoker After Method!!!");
+    }
+    public void afterThrowing(){
+        System.err.println("Invoker Exception Method!!!");
+    }
+}

+ 42 - 0
src/main/java/cn/nosum/framework/aop/aspect/Advice.java

@@ -0,0 +1,42 @@
+package cn.nosum.framework.aop.aspect;
+
+
+import java.lang.reflect.Method;
+
+public class Advice {
+    // 切面代理实例,比如 LogAspect
+    private Object aspect;
+    // 切面方法
+    private Method adviceMethod;
+    // 异常名称,比如 Exception
+    private String throwName;
+
+    public Advice(Object aspect, Method adviceMethod) {
+        this.aspect = aspect;
+        this.adviceMethod = adviceMethod;
+    }
+
+    public Object getAspect() {
+        return aspect;
+    }
+
+    public void setAspect(Object aspect) {
+        this.aspect = aspect;
+    }
+
+    public Method getAdviceMethod() {
+        return adviceMethod;
+    }
+
+    public void setAdviceMethod(Method adviceMethod) {
+        this.adviceMethod = adviceMethod;
+    }
+
+    public String getThrowName() {
+        return throwName;
+    }
+
+    public void setThrowName(String throwName) {
+        this.throwName = throwName;
+    }
+}

+ 59 - 0
src/main/java/cn/nosum/framework/aop/config/AopConfig.java

@@ -0,0 +1,59 @@
+package cn.nosum.framework.aop.config;
+
+
+public class AopConfig {
+    private String pointCut;
+    private String aspectClass;
+    private String aspectBefore;
+    private String aspectAfter;
+    private String aspectAfterThrow;
+    private String aspectAfterThrowingName;
+
+    public String getPointCut() {
+        return pointCut;
+    }
+
+    public void setPointCut(String pointCut) {
+        this.pointCut = pointCut;
+    }
+
+    public String getAspectClass() {
+        return aspectClass;
+    }
+
+    public void setAspectClass(String aspectClass) {
+        this.aspectClass = aspectClass;
+    }
+
+    public String getAspectBefore() {
+        return aspectBefore;
+    }
+
+    public void setAspectBefore(String aspectBefore) {
+        this.aspectBefore = aspectBefore;
+    }
+
+    public String getAspectAfter() {
+        return aspectAfter;
+    }
+
+    public void setAspectAfter(String aspectAfter) {
+        this.aspectAfter = aspectAfter;
+    }
+
+    public String getAspectAfterThrow() {
+        return aspectAfterThrow;
+    }
+
+    public void setAspectAfterThrow(String aspectAfterThrow) {
+        this.aspectAfterThrow = aspectAfterThrow;
+    }
+
+    public String getAspectAfterThrowingName() {
+        return aspectAfterThrowingName;
+    }
+
+    public void setAspectAfterThrowingName(String aspectAfterThrowingName) {
+        this.aspectAfterThrowingName = aspectAfterThrowingName;
+    }
+}

+ 7 - 0
src/main/java/cn/nosum/framework/aop/proxy/AopProxy.java

@@ -0,0 +1,7 @@
+package cn.nosum.framework.aop.proxy;
+
+
+public interface AopProxy {
+    Object getProxy();
+    Object getProxy(ClassLoader classLoader);
+}

+ 52 - 0
src/main/java/cn/nosum/framework/aop/proxy/JdkDynamicAopProxy.java

@@ -0,0 +1,52 @@
+package cn.nosum.framework.aop.proxy;
+
+
+import cn.nosum.framework.aop.aspect.Advice;
+import cn.nosum.framework.aop.support.AdvisedSupport;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+public class JdkDynamicAopProxy implements AopProxy,InvocationHandler {
+
+    private AdvisedSupport config;
+
+    public JdkDynamicAopProxy(AdvisedSupport config) {
+        this.config = config;
+    }
+
+    @Override
+    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+        Object returnValue;
+        try {
+            invokeAdvice(config.getAdvices(method).get("before"));
+            returnValue = method.invoke(this.config.getTarget(),args);
+            invokeAdvice(config.getAdvices(method).get("after"));
+        }catch (Exception e){
+            invokeAdvice(config.getAdvices(method).get("afterThrow"));
+            throw e;
+        }
+        return returnValue;
+    }
+
+    private void invokeAdvice(Advice advice) {
+        try {
+            advice.getAdviceMethod().invoke(advice.getAspect());
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public Object getProxy() {
+        return getProxy(this.config.getTargetClass().getClassLoader());
+    }
+
+    @Override
+    public Object getProxy(ClassLoader classLoader) {
+        return Proxy.newProxyInstance(classLoader,this.config.getTargetClass().getInterfaces(),this);
+    }
+}

+ 127 - 0
src/main/java/cn/nosum/framework/aop/support/AdvisedSupport.java

@@ -0,0 +1,127 @@
+package cn.nosum.framework.aop.support;
+
+
+import cn.nosum.framework.aop.aspect.Advice;
+import cn.nosum.framework.aop.config.AopConfig;
+import cn.nosum.util.StringUtils;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+public class AdvisedSupport {
+
+    // 保存读取到的配置
+    private AopConfig config;
+    private Object target;
+    private Class targetClass;
+    private Pattern pointCutClassPattern;
+
+    private Map<Method,Map<String,Advice>> methodCache;
+
+    public AdvisedSupport(AopConfig config) {
+        this.config = config;
+    }
+
+    // 解析配置文件的方法
+    private void parse() {
+        // 把 Spring 的 Expression 变成Java能够识别的正则表达式
+        String pointCut = config.getPointCut()
+                .replaceAll("\\.", "\\\\.")
+                .replaceAll("\\\\.\\*", ".*")
+                .replaceAll("\\(", "\\\\(")
+                .replaceAll("\\)", "\\\\)");
+
+
+        // 保存专门匹配 Class 的正则
+        String pointCutForClassRegex = pointCut.substring(0, pointCut.lastIndexOf("\\(") - 4);
+        pointCutClassPattern = Pattern.compile("class " + pointCutForClassRegex.substring(pointCutForClassRegex.lastIndexOf(" ") + 1));
+        if (pointCutMath()){
+            // 缓存
+            this.methodCache = new HashMap<Method, Map<String, Advice>>();
+            // 保存专门匹配方法的正则
+            Pattern pointCutPattern = Pattern.compile(pointCut);
+            try{
+                // 切面类,用户自己定义
+                Class aspectClass = Class.forName(this.config.getAspectClass());
+                Map<String,Method> aspectMethods = new HashMap<String, Method>();
+                // 取得切面类中的所有方法
+                for (Method method : aspectClass.getMethods()) {
+                    aspectMethods.put(method.getName(),method);
+                }
+                // 取得目标代理对象的所有方法
+                for (Method method : this.targetClass.getMethods()) {
+                    String methodString = method.toString();
+                    // 截取方法名称
+                    if(methodString.contains("throws")){
+                        methodString = methodString.substring(0,methodString.lastIndexOf("throws")).trim();
+                    }
+                    // 判断是否在切面的范围
+                    Matcher matcher = pointCutPattern.matcher(methodString);
+                    if(matcher.matches()){
+                        Map<String,Advice> advices = new HashMap<String, Advice>();
+                        // 增加方法执行前的切面
+                        if(StringUtils.isNotEmpty(config.getAspectBefore())){
+                            advices.put("before",new Advice(aspectClass.newInstance(),aspectMethods.get(config.getAspectBefore())));
+                        }
+                        // 增加方法执行后的切面
+                        if(StringUtils.isNotEmpty(config.getAspectAfter())){
+                            advices.put("after",new Advice(aspectClass.newInstance(),aspectMethods.get(config.getAspectAfter())));
+                        }
+                        // 增加方法执行异常的切面
+                        if(StringUtils.isNotEmpty(config.getAspectAfterThrow())){
+                            Advice advice = new Advice(aspectClass.newInstance(),aspectMethods.get(config.getAspectAfterThrow()));
+                            // 设置要捕获的异常
+                            advice.setThrowName(config.getAspectAfterThrowingName());
+                            advices.put("afterThrow",advice);
+                        }
+                        // 跟目标代理类的业务方法和 Advices 建立一对多关联关系,以便在 Proxy 类中获得
+                        this.methodCache.put(method,advices);
+                    }
+                }
+            }catch(Exception e){
+                e.printStackTrace();
+            }
+        }
+    }
+
+
+
+    // 根据一个目标代理类的方法,获得其对应的通知
+    public Map<String,Advice> getAdvices(Method method) throws NoSuchMethodException {
+        //享元设计模式的应用
+        Map<String,Advice> cache = methodCache.get(method);
+        if(null == cache){
+            // 如果是通过父类的接口调用,无法根据key进行获取,此时,取得子类方法,并且调用
+            Method m = targetClass.getMethod(method.getName(),method.getParameterTypes());
+            cache = methodCache.get(m);
+            // 缓存
+            this.methodCache.put(method,cache);
+        }
+        return cache;
+    }
+
+    // IOC 中的对象初始化时调用,决定要不要生成代理类的逻辑
+    public boolean pointCutMath() {
+        return pointCutClassPattern.matcher(this.targetClass.toString()).matches();
+    }
+
+    public void setTargetClass(Class<?> targetClass) {
+        this.targetClass = targetClass;
+        parse();
+    }
+
+    public void setTarget(Object target) {
+        this.target = target;
+    }
+
+    public Class getTargetClass() {
+        return targetClass;
+    }
+
+    public Object getTarget() {
+        return target;
+    }
+}

+ 23 - 0
src/main/java/cn/nosum/framework/context/ApplicationContext.java

@@ -3,6 +3,9 @@ package cn.nosum.framework.context;
 import cn.nosum.framework.annotation.Autowired;
 import cn.nosum.framework.annotation.Controller;
 import cn.nosum.framework.annotation.Service;
+import cn.nosum.framework.aop.config.AopConfig;
+import cn.nosum.framework.aop.proxy.JdkDynamicAopProxy;
+import cn.nosum.framework.aop.support.AdvisedSupport;
 import cn.nosum.framework.beans.BeanDefinition;
 import cn.nosum.framework.beans.BeanWrapper;
 import cn.nosum.framework.beans.support.BeanDefinitionReader;
@@ -117,6 +120,14 @@ public class ApplicationContext {
             }else{
                 Class<?> clazz=Class.forName(className);
                 instance=clazz.getDeclaredConstructor().newInstance();
+                // AOP
+                AdvisedSupport config=instantiateAopConfig(beanDefinition);
+                config.setTarget(instance);
+                config.setTargetClass(clazz);
+                // 判断是否需要生成代理对象
+                if(config.pointCutMath()){
+                    instance = new JdkDynamicAopProxy(config).getProxy();
+                }
                 this.factoryBeanObjectCache.put(className,instance);
                 this.factoryBeanObjectCache.put(beanDefinition.getFactoryBeanName(),instance);
             }
@@ -124,6 +135,18 @@ public class ApplicationContext {
         return instance;
     }
 
+
+    private AdvisedSupport instantiateAopConfig(BeanDefinition beanDefinition) {
+        AopConfig config = new AopConfig();
+        config.setPointCut(this.reader.getConfig().getProperty("pointCut"));
+        config.setAspectClass(this.reader.getConfig().getProperty("aspectClass"));
+        config.setAspectBefore(this.reader.getConfig().getProperty("aspectBefore"));
+        config.setAspectAfter(this.reader.getConfig().getProperty("aspectAfter"));
+        config.setAspectAfterThrow(this.reader.getConfig().getProperty("aspectAfterThrow"));
+        config.setAspectAfterThrowingName(this.reader.getConfig().getProperty("aspectAfterThrowingName"));
+        return new AdvisedSupport(config);
+    }
+
     public String[] getBeanDefinitionNames(){
         return this.beanDefinitionMap.keySet().toArray(new String[getBeanDefinitionCount()]);
     }

+ 15 - 1
src/main/resources/application.properties

@@ -1,2 +1,16 @@
 scanPackage=cn.nosum.demo
-templateRoot=layouts
+templateRoot=layouts
+
+
+# \u5207\u9762\u8868\u8FBE\u5F0F expression
+pointCut=public .* cn.nosum.demo.service..*Service..*(.*)
+# \u5207\u9762\u7C7B
+aspectClass=cn.nosum.demo.aspect.LogAspect
+# \u5207\u9762\u524D\u7F6E\u901A\u77E5
+aspectBefore=before
+# \u5207\u9762\u540E\u7F6E\u901A\u77E5
+aspectAfter=after
+# \u5207\u9762\u5F02\u5E38\u901A\u77E5
+aspectAfterThrow=afterThrowing
+# \u5207\u9762\u5F02\u5E38\u7C7B\u578B
+aspectAfterThrowingName=java.lang.Exception