Spring自研框架复习

1.@WebServlet("/")不会拦截指向jsp的转发,而@WebServlet("/*")会拦截指向jsp的转发。拦截之后不断重复执行@WebServlet("/")里面的内容,不断拦截里面的jsp,导致异常

反射依赖于Class和

java.lang.reflect包

  • Field:表示类中的成员变量
  • Method:类中的方法
  • Constructor:表示类的构造方法
  • Array:提供动态创建数组和访问数组元素的静态方法

Class用来表示运行时类型信息的对应类

  • 每个类都有一个唯一与之对应的Class对象
  • Class类为类类型
    • 是类的一种
    • 只有一个私有的构造函数,只有JVM能创建Class类的实例

获取Class对象的方式

  • getClass() 方法
  • Class.forName(全路径) 方法
  • .class

反射用法

  • 获取类的构造方法并使用(返回类型Constructor)

    • 获取所有共有的构造方法:getConstructors()

    • 获取所有的构造方法(公有、私有、受保护、默认):getDeclaredConstructors()

    • 获取单个带参数的公有构造方法:getConstructor(String.class,int.class)

    • 获取某个任意修饰符的构造方法(公有、私有、受保护、默认):getDeclaredConstructor(String.class)

      • 创建实例:

        Constructor con = clazz.getDeclaredConstructors(String.class);
        
        RelectTarget refl = con.newInstance();
        
      • 创建私有构造方法的实例:

        Constructor con = clazz.getDeclaredConstructors(String.class);
        con.setAccessible(true);
        RelectTarget refl = con.newInstance();
        
  • 获取成员变量

    • 取所有共有的字段:getFields()

    • 获取所有的字段(公有、私有、受保护、默认):getDeclaredFields()

    • 获取某个公有字段:getField(String fieldName)

    • 获取某个任意修饰符的字段(公有、私有、受保护、默认):getDeclaredField(String fieldName)

      • 设置值:

        要给字段赋值需要两步:
        1.获取字段:
        Class reflectTar = Class.forname("demo.reflect.ReflectTarget");
        //获取某个特定值
        Field f = reflectTar.getDeclaredFields("name");
        //获取对象实例
        Reflact ref = reflectTar.getConstructor().newInstance();
        //设置值
        f.set(ref,"Tom");
        

        若成员变量为私有:

        1.获取字段:
        Class reflectTar = Class.forname("demo.reflect.ReflectTarget");
        //获取某个特定值
        Field f = reflectTar.getDeclaredFields("name");
        //获取对象实例
        Reflact ref = reflectTar.getConstructor().newInstance();
        //设置为可访问
        f.setAccessible(true);
        //设置值
        f.set(ref,"Tom");
        

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ybeepKvB-1599660561967)(D:\JavaProject\SpringFramework\1598010924434.png)]

  • 获取成员方法

    • 取所有共有的字段:getMethods()

    • 获取所有的字段(公有、私有、受保护、默认):getDeclaredMethods()

    • 获取某个公有字段:getMethod(String name,Class<?>…parameterTypes)

    • 获取某个任意修饰符的字段(公有、私有、受保护、默认):getDeclaredMethod(String name,Class<?>…parameterTypes)

      • 方法执行:
      Class reflectTar = Class.forname("demo.reflect.ReflectTarget");
      //获取单个公有方法(方法名,参数类类型)
      Method m = reflectTar.getMethod("show1",String.class);
      //执行方法要求实例对象
      Reflact ref = reflectTat.getConstructor().newInstance();
      //执行方法 invoke(实例对象,参数值)
      m.invoke(ref,"good");
      

元注解:修饰注解的注解

  • @Target:描述所修饰的注解的适用范围(类、方法、变量)

  • @Rentention:标注注解被保留时间的长短(注解的生命周期)

  • @Documented:注解是否应当被包含在JavaDoc文档中

  • @Inherited:是否允许子类继承该注解

自研究框架实现IOC步骤

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EKlacK5l-1599660561969)(D:\JavaProject\SpringFramework\1598065808301.png)]

IOC思想

Spring核心思想就是IOC控制翻转,IOC可以让我们从耦合的创建对象方式中解脱出来,通过依赖注入的方式建立对象之间的联系。在IOC思想出现之前(当然IOC并不是Spring提出来的,Spring只是实现了基于IOC思想的IOC容器,但这个也已经很了不起了)。传统的设计思路是上层建筑依赖于下层建筑,就好比说我要造一个行李箱,按照传统思想,我就需要先造轮子,根据轮子的尺寸造底盘,根据底盘造行李箱。但是这样有一个问题就是,如果我修改了轮子的尺寸,那么轮子上层所有的建筑都需要进行修改。在代码中也是一样,上层的类中需要自己去new一个轮子,并且传入轮子的尺寸参数,当参数改变时,整个代码都得改,不利于维护。但是如果我们按照IOC思想,通过依赖注入的方式来进行设计,就不存在这样的问题。依赖注入的原理和之前传统方式截然相反,依赖注入是一种自上而下的设计理念,我上层 需要什么,那么你下层传入就行了。控制权由下层转移到了上层。而IOC容器在这种思想的基础上进行完善,他讲控制权掌握在自己手里,他负责托管所有的上下层的类,当上层需要下层的组件的时候,IOC容器就在自己托管的类中创建对象传递给上层,减少了代码的耦合,维护性很高。

创建注解

Spring中实现IOC的方法主要是xml配置文件和注解,此处选择注解的方式

既然要通过注解的方式,那么就需要自定义注解。因此创建

  • @Service 标记Service层的类
  • @Controller 标记Controller层的类
  • @Repository 标记Repository层的类
  • @Conponent 被标记的类为通用的被Spring管理的类,可以代表上面三个标签。上前三个标签是他的拓展,有自己特定的功能

提取注解标记对象

思路:通过遍历的方式,获得被标记的对象

而遍历则需要确定遍历的范围,而范围则是由使用者决定,因此可以通过使用者传入的包路径来确定范围。

1.获取范围里所有的类

​ 可以通过一个工具类中的方法得到所有类的对象。

​ 例如:用户输入的是“com.imooc.service”,我们需要查找包下所有的类。

​ 需要注意的是,光通过包名是找不到项目所在的位置的。我们需要的是绝对路径。但是对于用户来说传入绝对地址是很不友好的,因为不同机器之间路径可能不同。因此最好的办法是通过类加载器来获取。因此步骤如下:

  1. 获取类加载器;

  2. 获取到类加载器之后,通过类加载器中的方法getResource,传入包路径获取到相应的URL绝对定位符;

  3. 得到定位符之后进行判断,因为我们需要的是file类型的文件,因此调用url.getProtocol()方法判断是否为file类型

  4. 确定为file类型之后将用户输入的“com.imooc.service”转换为“com\imooc\service”,因为URL获取绝对路径方法getPath()要求分隔符为 “” ;

  5. 因为得到的绝对路径下可能还存在文件夹,我们要获取其中所有的Class,只能通过递归遍历所有文件夹。

    1. 创建一个用于递归的方法

      extractClassFile(classSet,packageDirectory,packageName),参数为存放Class结果的classSet、文件packageDirectory、要找的包名packageName

    2. 进入方法,先写出递归结束的条件即packageDirectory不为文件。

    3. 列出packageDirectory所有的文件,使用过滤器进行判断:

在这里插入图片描述

  1. 如果为文件夹则先存入数组,用于待会儿的递归;

  2. 如果是.Class文件的话则进入获取Class对象的方法中。方法通过截取字符串得到Class的路径,再使用Class.forName()方法得到Class对象,存入classSet中。

    到此,获取所有Class对象过程结束。

3.创建容器

​ 创建容器思路:

  • 首先容器只有一个,需要使用单例模式创建;

通过饿汉单例模式得到对象。但是普通的单例模式是不安全的,可以通过反射机制创建新的对象,因此可以通过枚举类型类进行保护:

public class EnumStarvingSigleton {
    private EnumStarvingSigleton(){}
    public static EnumStarvingSigleton getInstance(){
        return ContainerHolder.HOLDER.instance;
    }
    private enum ContainerHolder{
        HOLDER;
        private EnumStarvingSigleton instance;
        ContainerHolder(){
            instance = new EnumStarvingSigleton();
        }
    }
}
  • 容器的目的是管理bean,因此需要一个Map<Class,Object>存储管理的bean和他的类;
private final Map<Class,Object> beanMap = new ConcurrentHashMap<Class, Object>();
  • 容器要对bean进行管理,需要通过遍历的方式获得bean,而遍历方式使用上述方法;
//扫描加载所有bean
    public synchronized void loadBeans(String packageName){
        if (isLoaded()){
            return;
        }
        Set<Class> classSet = ClassUtil.extractPackageClass(packageName);
        if (classSet == null || classSet.isEmpty()){
            return;
        }
        for (Class clazz : classSet) {
            for (Class<? extends Annotation> annotation : BEAN_ANNOTATION) {
                //如果类上标记了注解
                if (clazz.isAnnotationPresent(annotation)){
                    beanMap.put(clazz,ClassUtil.newInstance(clazz,true));
                }
            }
        }
        loaded = true;
    }
  • 容器要对bean进行筛选,查找到使用注解标注的bean,因此需要引入存储有所有注解的集合
    private static final List<Class<? extends Annotation>> BEAN_ANNOTATION
            = Arrays.asList(Component.class, Controller.class, Service.class, Repository.class);
  • 判断容器是否被加载过
    private boolean loaded = false;

    public boolean isLoaded(){
        return loaded;
    }
4.提供容器对外操作的方法

对bean的增删改查,没啥好说的

5.实现容器的依赖注入

到这里为止,容器虽然可以接管bean实例,但其仍是不完备的,因为此时并没有进行依赖注入,其成员变量的值为null

实现依赖注入的思路

  • 定义相关注解标签

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AutoWired {
        String value() default "";
    }
    
  • 实现创建被注解标记的成员变量实例,并将其注入到成员变量中

    • 扫描容器中所有的Class
    • 扫描每个Class下的成员变量
    • 找出被注解AutoWired标记的成员变量
    • 获取成员变量的类型
    • 通过成员变量的类型获取其在容器中的实例
      • 因为成员变量的类型有可能是接口类型,因此还需要进行判断:
        • 若不是接口,说明存在实现类,直接返回
        • 若为接口,则要获取其实现类
          • 因为接口可能有多个实现类,因此我们需要选出正确的那一个。可以通过在注解中设置用户修改的Value变量,通过读取来判断;
    • 将实例赋给成员变量
  • 依赖注入的使用

自研框架实现AOP

1.知识储备

在这里插入图片描述

在这里插入图片描述

Advice种类

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 正式开发

1. 解决标记问题

使用AOP需要先准备好两个注解:

  • @Aspect:定义切面,切面里包含了各种横切逻辑;
  • @Order:用于多个Aspect切入时定义执行的顺序,Spring中按照Order的升序执行Aspect;
2.定义横切逻辑模板

在Spring中,横切逻辑Advice使用注解开发,但是本框架中为了简单起见,定义了横切模板类。类中给定了三种类型的Advice,对应不同场景:

  • before
  • afterReturning
  • afterThrowing
public abstract class DefaultAspect {
    /**
    * 定义BeforeAdvice,设置为钩子函数。当需要设置beforeAdvice时实现即可
    * @param targetClass
	 * @param method
	 * @param args
    * @return void
    * @creed: Talk is cheap,show me the code
    * @date 2020/8/24 14:56
    */
    public void before(Class targetClass, Method method,Object[] args)throws Throwable{}

    /**
     * 定义AfterAdvice,也设置为钩子函数。当需要设置时实现即可
     * @param targetClass
     * @param method
     * @param args
     * @return void
     * @creed: Talk is cheap,show me the code
     * @date 2020/8/24 14:56
     */
    public Object afterReturning(Class targetClass, Method method,Object[] args,Object returnValue)throws Throwable{
        return returnValue;
    }

    /**
     * 定义afterThrowing,设置为钩子函数。当需要设置时实现即可
     * @param targetClass
     * @param method
     * @param args
     * @return void
     * @creed: Talk is cheap,show me the code
     * @date 2020/8/24 14:56
     */
    public void afterThrowing(Class targetClass, Method method,Object[] args,Throwable e)throws Throwable{

    }

}

​ 当需要使用哪种横切逻辑时,继承该类的子类重写即可。没有进行重写的钩子函数不会影响程序的执行。

3.对执行多个Aspect进行管理

通过模拟CGLIB动态代理来对逻辑进行切入。在切入之前需要解决的问题是:

如何解决多个Aspect执行顺序的问题?

在知识储备里,我们知道对于多个Aspect,AOP通过Order使其顺序执行。而我们如果普通的使用CGLIB实现类来完成,那么Method会被执行多次,且多个Aspect之间的横切逻辑执行顺序将不复存在。此时我们就需要通过排序队列来确定执行的顺序。

即:按照队列中Aspect的Order顺序排好序,再依次读取Aspect,顺序执行before,再只执行依次Method,之后逆序执行after

而List需要依赖各个Aspect的信息,因此创建类AspectInfo,立面存储Order以及Aspect,对其进行排序。

@AllArgsConstructor
@Getter
public class AspectInfo {
    private int orderIndex;
    private DefaultAspect aspectObject;

    public int getOrderIndex() {
        return orderIndex;
    }

    public DefaultAspect getAspectObject() {
        return aspectObject;
    }
}

Aspect横切逻辑管理类的代码如下:

/**
* 实现Aspect横切逻辑
* AspectListExecutor用来管理各个Aspect。其中需要解决的问题:
 * 1.实现Spring中多个Aspect中的Advice之间的执行顺序
 *  如:执行Aspect1.beforeAdvice --> Aspect2.beforeAdvice --> Aspect3.beforeAdvice
 *  Aspect1.afterAdvice <-- Aspect2.afterAdvice <-- Aspect3.afterAdvice
 *  我们可以通过有序队列完成
*
* @creed: Talk is cheap,show me the code
* @date 2020/8/24 15:07
*/
public class AspectListExecutor implements MethodInterceptor {
    //通过有序队列来实现按顺序读取Aspect
    //排序的依据是每个Aspect的信息,因此创建AspectInfo类进行储存
    private List<AspectInfo> sortedAspectInfoList;
    //被代理的类
    private Class<?> targetClass;

    public List<AspectInfo> getAspectInfoList() {
        return sortedAspectInfoList;
    }

    //在构造函数中完成List的排序
    public AspectListExecutor(List<AspectInfo> aspectInfoList, Class<?> targetClass) {
        this.sortedAspectInfoList = sortAspectInfoList(aspectInfoList);
        this.targetClass = targetClass;
    }

    private List<AspectInfo> sortAspectInfoList(List<AspectInfo> aspectInfoList){
        Collections.sort(aspectInfoList, new Comparator<AspectInfo>() {
            public int compare(AspectInfo o1, AspectInfo o2) {
                return o1.getOrderIndex() - o2.getOrderIndex();
            }
        });
        return aspectInfoList;
    }

    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object result = null;
        //进行判空
        if (sortedAspectInfoList == null || sortedAspectInfoList.size() == 0){
            return null;
        }
        //1.按照升序执行beforeAdvice
        invokeBeforeAdvices(method,args);
        try{
            methodProxy.invokeSuper(proxy,args);
            //2.按照降序执行afterReturningAdvice
            result = invokeAferReturningAdvice(method,args,result);
        }catch (Exception e){
            //3.如果代理方法抛出异常,执行afterThrowingAdvice
            invokeAfterThrowingAdvice(method,args,e);
        }
        return result;
    }

    private void invokeAfterThrowingAdvice(Method method, Object[] args,Throwable e) throws Throwable {
        for (int i = sortedAspectInfoList.size()-1;i >= 0;i--){
           sortedAspectInfoList.get(i).getAspectObject().afterThrowing(targetClass,method,args,e);
        }
    }

    private Object invokeAferReturningAdvice(Method method, Object[] args, Object returnValue) throws Throwable {
        Object result = null;
        for (int i = sortedAspectInfoList.size()-1;i >= 0;i--){
            result = sortedAspectInfoList.get(i).getAspectObject().afterReturning(targetClass,method,args,returnValue);
        }
        return result;
    }

    private void invokeBeforeAdvices(Method method, Object[] args) throws Throwable {
        for (AspectInfo aspectInfo : sortedAspectInfoList) {
            aspectInfo.getAspectObject().before(targetClass,method,args);
        }
    }
}

创建动态代理对象

public class ProxyCreator {
    /**
    * 创建动态代理对象并返回
    * @param  targetClass  被代理的Class对象
     * @param methodInterceptor   方法拦截器
    * @return void
    * @creed: Talk is cheap,show me the code
    * @date 2020/8/24 16:31
    */
    public static Object creatProxy(Class targetClass, MethodInterceptor methodInterceptor){
        return Enhancer.create(targetClass,methodInterceptor);
    }
}

4.将带有织入逻辑的代理放入容器中

本步骤是AOP的关键步骤。在之前的步骤里,我们完成了基础设施的建设:创建注解和Aspect,织入Aspect的方法(解决多个Aspect织入的顺序问题),代理类的创建。

到这一步,我们就需要将之前的步骤整合起来,完成对于整个容器的AOP操作:即织入有织入逻辑的代理类替换掉容器中所有注解标记的AOP对象。具体步骤如下:

  1. 获取容器中所有被@Aspect标记的对象;
//1.获取容器中所有的Aspect
        Set<Class<?>> aspectSet = beanContainer.getClassByAnnotation(Aspect.class);
  1. 根据@Aspect修饰的类对Aspect进行分类
 HashMap<Class<? extends Annotation>, List<AspectInfo>> categorizedMap = new HashMap<Class<? extends Annotation>, List<AspectInfo>>();
        if (aspectSet == null || aspectSet.size() == 0){return;}
        for (Class<?> aspectClass : aspectSet) {
            //对Aspect合法性进行验证
            if (verifyAspect(aspectClass)){
                //对Aspect按照Class进行分类
                categorizedAspect(categorizedMap,aspectClass);
            }else {
                throw new RuntimeException("...");
            }
        }

​ 其中涉及到两个具体点:

  • Aspect合法性验证:要求验证Aspect是否带有@Order、@Aspect标签以及是否继承自Aspect父类DefaultAspect、@Aspect修饰的是否是@Aspect本身:
private boolean verifyAspect(Class<?> aspectClass) {
        return aspectClass.isAnnotationPresent(Aspect.class)&&
                aspectClass.isAnnotationPresent(Order.class)&&
                DefaultAspect.class.isAssignableFrom(aspectClass)&&
                aspectClass.getAnnotation(Aspect.class).value()!= Aspect.class;
    }
  • Aspect分类: 将Aspect按照其修饰的类进行分组,便于后续创建代理类使用
private void categorizedAspect(HashMap<Class<? extends Annotation>, List<AspectInfo>> categorizedMap, Class<?> aspectClass) {
        Order orderTag = aspectClass.getAnnotation(Order.class);
        Aspect aspectTag = aspectClass.getAnnotation(Aspect.class);
        DefaultAspect defaultAspect = (DefaultAspect) beanContainer.getBean(aspectClass);
        AspectInfo aspectInfo = new AspectInfo(orderTag.value(), defaultAspect);
        //如果植入的joinpoint第一次出现,则创建对应的list
        if (!categorizedMap.containsKey(aspectTag.value())){
            ArrayList<AspectInfo> aspectInfoList= new ArrayList<AspectInfo>();
            aspectInfoList.add(aspectInfo);
            categorizedMap.put(aspectTag.value(),aspectInfoList);
        }else {
            //如果不是第一次出现,那么直接添加即可
            List<AspectInfo> aspectInfoList = categorizedMap.get(aspectTag.value());
            aspectInfoList.add(aspectInfo);
        }
  1. 创建含有横切逻辑的代理类将容器中原本的类全部代理
if (categorizedMap == null || categorizedMap.size() == 0){return;}
        for (Class<? extends Annotation> category : categorizedMap.keySet()) {
            weaveByCategory(category,categorizedMap.get(category));
        }

private void weaveByCategory(Class<? extends Annotation> category, List<AspectInfo> aspectInfos) {
        Set<Class<?>> classSet = beanContainer.getClassByAnnotation(category);
        if (classSet.size() == 0 || classSet == null){return;}
        for (Class<?> targetClass : classSet) {
            AspectListExecutor aspectListExecutor = new AspectListExecutor(aspectInfos, targetClass);
            Object proxyBean = ProxyCreator.creatProxy(targetClass, aspectListExecutor);
            beanContainer.addBean(targetClass,proxyBean);
        }
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值