• (a)Spring注解式开发,注册组件的@Repository,@Service,@Controller,@Component使用及说明


    注解扫描原理

    通过反射机制获取注解

    @Target(value = {ElementType.TYPE})// 设置Component注解可以出现的位置,以上代表表示Component注解只能用在类和接口上
    @Retention(value = RetentionPolicy.RUNTIME)// 设置Component注解的保持性策略,以上代表Component注解可以被反射机制读取
    public @interface Component {
        // 注解中的一个属性, 该属性类型String,属性名是value
        String value();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    假设我们现在只知道一个包名, 这个包下有多少个Bean我们不知道, Bean上有没有注解也不知道,如何通过程序自动将类上有注解的Bean实例化

    @Component(value = "userBean")// 语法格式:@注解类型名(属性名=属性值, 属性名=属性值, 属性名=属性值......)
    public class User {
    }
    // 没有注解的Bean	
    public class Vip {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    public class ComponentScan {
        public static void main(String[] args){
            // 存放Bean的Map集合,key存储beanId,value存储Bean
            Map<String,Object> beanMap = new HashMap<>();
            // 通过包的名字扫描这个包下所有的类,当这个类上有@Component注解的时候实例化该对象,然后放到Map集合中
            String packageName = "com.powernode.bean";
            // 开始写扫描程序,将包名换成路径获取目录下的所有文件
            // 在正则表达式中"."属于通配符代表任意字符,使用"\."代表一个普通的"."字符(java中"\"表示转义字符所以需要使用"\\.")
            String packagePath = packageName.replaceAll("\\.", "/");
            String packagePath = packageName.replaceAll("\\.", "/");
            // 从类的根路径下加载资源,自动返回一个URL类型的对象(路径)
            URL url = ClassLoader.getSystemClassLoader().getResource(packagePath);
            // 获去扫描的类所在的绝对路径
            String path = url.getPath();
    
            // 获取一个绝对路径下的所有文件
            File file = new File(path);
            // 遍历这个路径下的所有文件,每个文件都是File对象,最后存到一个File类型的数组中
            File[] files = file.listFiles();
            Arrays.stream(files).forEach(f -> {
                try {
                    // f.getName()获取com.powernode.bean包下的文件名User.class和Vip.class
                    // f.getName().split("\\.")[0],先通过"."对文件名进行拆分,然后取数组的第一个元素即文件的简类名User和Vip
                    // 拼接字符串得到文件的全类名com.powernode.bean.User和com.powernode.bean.Vip
                    String className = packageName+ "." + f.getName().split("\\.")[0];
    
                    // 通过全类名获取类的字节码对象
                    Class<?> aClass = Class.forName(className);
    
                    // 判断类上是否有Component注解
                    if (aClass.isAnnotationPresent(Component.class)) {
                        // 获取Component注解
                        Component annotation = aClass.getAnnotation(Component.class);
                        // 获取Component注解的属性值及即bean的id
                        String id = annotation.value();
                        // 有Component注解的都要创建对象
                        Object obj = aClass.newInstance();
                        // 将创建的对象放入Map集合当中
                        beanMap.put(id, obj);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            // Map集合中只有User对象,因为Vip类上没有Component注解 
            System.out.println(beanMap);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    注册组件的四个注解

    注解的存在主要是为了简化XML的配置, Spring6倡导全注解开发,所以只要使用了Spring的注解就要使用包扫描机制

    • 使用注解一般加入的是自己写的组件 , 使用bean标签配置加入的是别人的写的组件 , 开发中常用注解和bean配置相结合的方式
    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
        <context:component-scan base-package="com.powernode.spring6.bean4"/>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    标识Bean的四个注解

    使用标识Bean的四个注解和使用XML配置的方式将组件加入到容器中后组件的默认行为都是一样的 , 组件都有id和默认作用域

    • 四个注解都只有一个value属性用来指定bean的id,bean的名字默认是组件的简单类名首字母小写后得到的字符串 , 作用域默认就是单例的
    • 优点: 可读性比较好简化了bean的声明, 更符合MVC的设计理念, 这种声明bean的方式是目前企业中较为常见的bean的声明方式
    • 缺点: 没有任何一个地方可以查阅整体信息,只有当程序运行起来才能感知到加载了多少个bean

    实际这四个注解用哪个都可以 , 它们的功能都是只能起到标识的作用,Spring并没有能力识别一个组件到底是不是它所标记的MVC架构类型

    • 即使将@Respository注解用在一个表述层控制器组件上面也不会产生任何错误
    • 所以@Respository、@Service、@Controller这几个注解仅仅是为了让开发人员自己明确当前的组件扮演的角色,增强程序的可读性
    注解名功能
    @Repository标识一个受Spring IOC容器管理的持久化层组件,给数据库层 (持久化层 , dao层)的组件添加这个注解
    @Service标识一个受Spring IOC容器管理的业务逻辑层组件,推荐给业务逻辑层的组件添加这个注解
    @Controller标识一个受Spring IOC容器管理的表述层控制器组件,推荐给控制层也就是Servlet包下的这些组件加这个注解
    @Component标识一个受Spring IOC容器管理的普通组件,给不属于以上几层的组件添加这个注解
    @Scope指定加入的组件是多实例的还是单实例的,默认是单实例的 , prototype属性表示指定的bean是多实例的

    四个注解的源码

    @Component源码: @Controller、@Service、@Repository这三个注解都是@Component注解的别名,使用别名的方式可读性更好

    @Target(value = {ElementType.TYPE})
    @Retention(value = RetentionPolicy.RUNTIME)
    public @interface Component {
        String value();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    @Repository , @Controller ,@Service注解源码

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Component
    public @interface Controller {
        // 别名
        @AliasFor(
            annotation = Component.class
        )
        String value() default "";
    }
    
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Component
    public @interface Controller {
        // 别名
        @AliasFor(
            annotation = Component.class
        )
        String value() default "";
    }
    
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Component
    public @interface Service {
        // 别名
        @AliasFor(
            annotation = Component.class
        )
        String value() default "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    使用Spring的IoC注解

    使用步骤

    第一步: 引入aop的依赖才支持注解模式(如果加入spring-context依赖之后会关联加入aop的依赖)

    在这里插入图片描述

    第二步:使用context:component-scan标签让Spring去指定包中扫描加了注解的组件,并将这些组件实例化后加入到IoC容器中同时管理这些bean对象

    • 在xmlns头部信息中添加context命名空间的配置信息:xmlns:context="http://www.springframework.org/schema/context"
    • 在xsi约束文件中添加约束信息:http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd

    context:component-scan标签的属性

    属性名功能
    base-package指定一个需要扫描的基类包,默认Spring容器会扫描这个指定的基类包及其子包中的所有类
    resource-pattern扫描基类包下特定的类,如仅希望扫描基类包下特定的类而非所有类(*表示匹配任意个字符)
    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
        
        <context:component-scan base-package="com.powernode.spring6.bean"> context:component-scan>
        
        <context:component-scan base-package="com.powernode.spring6" resource-pattern="bean/*"/>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    第三步:在Bean类上使用注解, 虽然Component注解换成其它三个注解照样创建bean对象,但为了可读性应该根据属于MVC架构模式的哪一层加对应类型注解

    //@Component(value = "userBean")
    @Component("userBean")
    public class User {
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    public class AnnotationTest {
        @Test
        public void testBean(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
            User userBean = applicationContext.getBean("userBean", User.class);
            System.out.println(userBean);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    使用细节

    如果IoC注解没有设置value属性,Spring会为bean自动取名默认是bean首字母小写后的类名

    @Repository// 等价于
    public class BankDao {
    }
    
    
    • 1
    • 2
    • 3
    • 4
    public class AnnotationTest {
        @Test
        public void testBean(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
            BankDao bankDao = applicationContext.getBean("bankDao", BankDao.class);
            System.out.println(bankDao);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    扫描多个基类包下加了注解的类

    • 第一种: 在配置文件中指定多个基类包使用逗号隔开
    • 第二种: 指定多个基类包的共同父包,但是这样扫描范围就大了肯定要牺牲一部分效率
    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
        
        <context:component-scan base-package="com.powernode.spring6.bean,com.powernode.spring6.bean2"/>
        
        
        <context:component-scan base-package="com.powernode.spring6"/>
    beans>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    选择性实例化包下的Bean

    假设在某个包下很多类上都标注了不同类型的注解,现在由于特殊业务的需要只允许其中所有的加了Controller注解的类参与Bean管理,其他的都不实例化

    @Component
    public class A {
        public A() {
            System.out.println("A的无参数构造方法执行");
        }
    }
    
    @Controller
    class B {
        public B() {
            System.out.println("B的无参数构造方法执行");
        }
    }
    
    @Service
    class C {
        public C() {
            System.out.println("C的无参数构造方法执行");
        }
    }
    
    @Repository
    class D {
        public D() {
            System.out.println("D的无参数构造方法执行");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    context:component-scan标签的属性

    属性值功能
    use-default-filters=“true”(默认)只要扫描的bean上有Component、Controller、Service、Repository中的任意一个注解都会进行实例化
    use-default-filters=“false”不再使用spring默认实例化规则, 让所有bean上的Component、Controller、Service、Repository注解全部失效

    context:component-scan标签的子标签(指定包含与排除基类包下的哪些类)

    标签名功能
    context:exclude-filter指定扫描基类包时要排除在外的目标类,type属性指定排除的规则 , 默认全部扫描进来并实例化
    context:include-filter指定扫描基类包时要包含在内的目标类,扫描时一定要禁用默认的过滤规则(默认全都扫描) , type属性指定排除规则

    context:exclude-filter标签和context:include-filter标签type属性和expression属性的值

    Type属性的值expression属性的值说明
    annotation要过滤的注解的全类名按照bean上的注解类型进行过滤 (常用)
    assignable要过滤的类的全类名按照类的全类名过滤指定类和它的子类
    aspectjcom.atguigu.*Service根据AspectJ表达式进行过滤 (常用) , 过滤所有类名是以Service结束的类或其子类
    regexcom.atguigu.anno.*根据正则表达式匹配到的类名进行过滤 , 过滤com.atguigu.anno包下的所有类
    customcom.atguigu.XxxTypeFilter使用XxxTypeFilter类通过编码的方式自定义过滤规则
    该类必须实现org.springframework.core.type.filter.TypeFilter接口

    spring的配置文件

    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
        
        <context:component-scan base-package="com.powernode.spring6.bean" use-default-filters="false">
            
            <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        context:component-scan>
    
        
        <context:component-scan base-package="com.powernode.spring6.bean" use-default-filters="true">
            
            <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
            <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
            <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Component"/>
        context:component-scan>
    beans>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    Type其他属性的测试

    
    <context:component-scan base-package="com.powernode.spring6.bean">
        
        <context:exclude-filter type="assignable" expression="com.powernode.spring6.bean.A">             
    context:component-scan>
            
            
           
    <context:component-scan base-package="com.atguigu" use-default-filters="false">
        
        <context:include-filter type="assignable" expression="com.powernode.spring6.bean.A">             
    context:component-scan>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  • 相关阅读:
    Simulink 自动代码生成电机控制:Keil工程转到CubeIDE相关问题(2/2)
    单点登录原理
    19 OpenCV 霍夫曼变换检测圆
    蓝桥杯算法赛 第 6 场 小白入门赛 解题报告 | 珂学家 | 简单场 + 元宵节日快乐
    山东菏泽家乡网页代码 html静态网页设计制作 dw静态网页成品模板素材网页 web前端网页设计与制作 div静态网页设计
    小记一个用对象简化if...else if的思路
    Spring Boot 集成 MinIO 实现文件上传、下载和删除
    jQuery中遍历元素each
    TCP重头戏来!了!(1) —— 小林图解学习摘记
    设计模式之访问者模式(下)
  • 原文地址:https://blog.csdn.net/qq_57005976/article/details/133267222