(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();
}

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

@Component(value = "userBean")// 语法格式:@注解类型名(属性名=属性值, 属性名=属性值, 属性名=属性值......)
public class User {
}
// 没有注解的Bean	
public class Vip {
}
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);
    }
}

注册组件的四个注解

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

  • 使用注解一般加入的是自己写的组件 , 使用bean标签配置加入的是别人的写的组件 , 开发中常用注解和bean配置相结合的方式
<?xml version="1.0" encoding="UTF-8"?>
<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>

标识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();
}

@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 "";
}

使用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扫描基类包下特定的类,如仅希望扫描基类包下特定的类而非所有类(*表示匹配任意个字符)
<?xml version="1.0" encoding="UTF-8"?>
<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">
    <!--告诉Spring框架要扫描哪些包中的类-->
    <context:component-scan base-package="com.powernode.spring6.bean"> </context:component-scan>
    <!--过滤特定的类-->
    <context:component-scan base-package="com.powernode.spring6" resource-pattern="bean/*"/>
</beans>

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

//@Component(value = "userBean")
@Component("userBean")
public class User {
}

public class AnnotationTest {
    @Test
    public void testBean(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        User userBean = applicationContext.getBean("userBean", User.class);
        System.out.println(userBean);
    }
}

使用细节

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

@Repository// 等价于<bean id="bankDao" class="com.powernode.spring6.bean.BankDao"></bean>
public class BankDao {
}

public class AnnotationTest {
    @Test
    public void testBean(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        BankDao bankDao = applicationContext.getBean("bankDao", BankDao.class);
        System.out.println(bankDao);
    }
}

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

  • 第一种: 在配置文件中指定多个基类包使用逗号隔开
  • 第二种: 指定多个基类包的共同父包,但是这样扫描范围就大了肯定要牺牲一部分效率
<?xml version="1.0" encoding="UTF-8"?>
<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>

选择性实例化包下的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的无参数构造方法执行");
    }
}

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的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<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">
    <!--第一种方案:false表示该包下的所有的带有声明Bean的注解全部失效,组件都不再实例化-->
    <context:component-scan base-package="com.powernode.spring6.bean" use-default-filters="false">
        <!--只有带有Controller注解的组件被包含进来,其他注解全部失效-->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!--第二种方案:true表示该包下的所有的带有声明Bean注解的组件全部都会实例化-->
    <context:component-scan base-package="com.powernode.spring6.bean" use-default-filters="true">
        <!--将带有Repository,Service,Component注解的组件全部排除在外-->
        <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>

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>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值