Spring IoC 注解式开发

134 阅读5分钟

1. 回顾注解

注解的存在主要是为了简化 XML 的配置,Spring6倡导全注解开发

// 元注解:标注注解的注解。Target:注解可以出现的位置
// 表示 @Component 可以出现在 类上、属性上
@Target(value = {ElementType.TYPE, ElementType.FIELD})
// @Retention 也是一个元注解,用来标注 @Component 注解最终保留在 class 文件当中,并可以被反射机制读取。
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
    String name();
    int[] nums();
}
@Component(name = "value", nums = {1, 2, 3})
public class User {
} 
public class ReflectAnnotationTest1 {
    public static void main(String[] args) throws Exception {
        Class<?> aClass = Class.forName("com.powernode.spring6.bean.User");
        // 判断类上有没有这个注解
        if (aClass.isAnnotationPresent(Component.class)) {
            // 获取类上的注解
            Component annotation = aClass.getAnnotation(Component.class);
            // 访问注解属性
            System.out.println(annotation.name());
        }
    }
}

组件扫描原理

public class ComponentScan {
    public static void main(String[] args) {
        Map<String, Object> beanMap = new HashMap<String, Object>();
        // 只知道包的名字,扫描这个包下所有类,当这个类上有 @Component 注解的时候,实例化该对象,然后放到 Map 集合当中
        String packageName = "com.powernode.spring6.bean";
        // 写扫描程序
        // . 在正则表达式中表示任意字符
        String packagePath = packageName.replaceAll("\\.", "/");
        // com 是在类的根路径下的一个目录
        URL url = ClassLoader.getSystemClassLoader().getResource(packagePath);
        String path = url.getPath();
        // 获取绝对路径下的所有文件
        File file = new File(path);
        File[] files = file.listFiles();
        Arrays.stream(files).forEach(f -> {
//            System.out.println(f.getName().split("\\.")[0]);
            try {
                String className = packageName + "." + f.getName().split("\\.")[0];
                // 通过反射机制解析注解
                Class<?> aClass = null;
                aClass = Class.forName(className);
                // 判断类上是否有这个注解
                if (aClass.isAnnotationPresent(Component.class)) {
                    // 获取注解
                    Component component = aClass.getAnnotation(Component.class);
                    String id = component.value();
                    // 有这个注解的都要创建对象
                    Object o = aClass.newInstance();
                    beanMap.put(id, o);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        });
        System.out.println(beanMap);
    }
}

2. 声明 Bean 的注解

负责声明Bean的注解,常见的包括四个:

  • @Component
  • @Controller
  • @Service
  • @Repository

这几个本质上没区别,都是 @Component 的别名,就是在三层架构中起一个规范的作用

3. Spring 注解的使用

如何使用上述注解?

  1. 加入 aop 的依赖
  2. 在配置文件中添加 context 命名空间
  3. 在配置文件中指定扫描的包
  4. 在 Bean 类上使用注解

第一步:加入 aop 依赖

在加入 spring-context 依赖后,会关联加入 aop 的依赖。

第二步:在配置文件中添加 context 命名空间

<?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"/>
</beans>

4. 多个包扫描

<!-- 多个包使用逗号隔开 -->
<context:component-scan base-package="com.powernode.spring6.bean, com.powernode.spring6.dao"/>
<!-- 也可以指定这些包的共同父包,但这样效率会降低 -->
<context:component-scan base-package="com.powernode.spring6" --></context:component-scan>

5. 选择性实例化 Bean

假设在某个包下有很多 Bean,有的是 Component,有的是 Controller。但因为某种特殊的业务,我们只允许 Controller 参与Bean管理,其他的都不实例化,该怎么做?

<!-- 第一种解决方案
    use-default-filters="false"
    false 表示包下所有的带有声明 Bean 的注解全部失效 @Component、@Service、@Service、@Repository
-->
<context:component-scan base-package="com.powernode.spring6.bean2" use-default-filters="false">
    <!-- 只有 @Repository 被包含进来 -->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
<!-- 第二种解决方案
    use-default-filters="true"
    false 表示包下所有的带有声明 Bean 的注解全部生效
-->
<context:component-scan base-package="com.powernode.spring6.bean2" use-default-filters="true">
    <!-- 只有 @Repository 被包含进来 -->
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>

5. 负责注入的注解

上面那四个注解是用来声明 Bean 的,声明之后 Bean 就会实例化,然后呢,就要给 Bean 的属性赋值啦,用到的注解有:

  • @Value
  • @Autowired
  • @Qualifier
  • @Resource

5.1 @Value

当属性的类型是简单类型时,可以使用 @Value 注解注入

@Component
public class User2 {
    @Value("张二蛋")
    private String name;
    @Value("30")
    private int age;

    public User2(@Value("张四蛋") String name, @Value("38") int age) {
        this.name = name;
        this.age = age;
    }

    @Value("张三蛋")
    public void setName(String name) {
        this.name = name;
    }
    @Value("33")
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User2{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

5.2 @Autowired 与 @Qualifier

@Autowired 注解可以用来注入非简单类型

单独使用 @Autowired 注解,默认根据类型自动装配(byType)

该注解可以标注在哪里?

  • 构造方法上
  • 方法上
  • 形参上
  • 属性上
  • 注解上

该注解有一个 required 属性,默认值是true,表示注入的时候要求被注入的 Bean 必须是存在的,如果不存在则报错。

根据类型

// @Autowired 使用时,不需要指定任何属性,直接使用
// 根据 byType 自动装配
@Autowired
private OrderDao orderDao;

根据名字

@Autowired
@Qualifier("orderDaoImplForOracle")
private OrderDao orderDao;

public void generate() {
    orderDao.insert();
}

如果一个类当中构造方法只有一个,并且构造方法上的参数和属性都能够对应上,@Autowired 注解可以省略

5.3 @Resource

@Resource 注解也可以完成非简单类型注入,那他与 @Autowired 有什么区别呢?

  • @Resource 注解是 JDK 扩展包中的,也就是说属于 JDK 的一部分。所以该注解是标准注解,更具有通用性。
  • @Autowired 注解是Spring 框架自己的。
  • @Resource 注解默认根据名称装配 byName,未指定 name 时,使用属性名作为 name。通过 name 中不到的话会自动启动通过类型 byType 装配。
  • @Autowired 注解默认根据类型装配 byType,如果想根据名称,需要配合 @Qualifier 注解一起用。
  • @Resource 注解用在属性上、setter 方法上。
  • @Autowired 注解用在属性上、setter 方法上、构造方法上、构造方法参数上。

@Resource 注解属于 JDK 扩展包,不在 JDK 中,需要额外引入下面的依赖(JDK 8 不用)

<dependency>
	<groupId>jakarata.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
    <version>2.1.1</version>
</dependency>

一定要注意:Spring6 已不再支持 JavaEE,它支持 JakartaEE9。(Oracle 把 JavaEE 给 Apache 了,Apache 给改名了,之前接触的所有 javax.* 一律改为 jakarta.*

Spring5 则引入下面的依赖

<dependency>
	<groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>
@Service("studentService")
public class StudentService {

    @Resource(name="studentDaoImplForMySQL")
    private StudentDao studentDao;

    public void deleteStudent () {
        studentDao.deleteById();
    }
}

5.4 全注解开发

@Configuration
@ComponentScan({"cn.powernode.dao", "cn.powernode.service"})
public class Spring6Config {
}
@Test
public void testNoXML() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spring6Config.class);
    StudentService studentService = context.getBean("studentService", StudentService.class);
    studentService.deleteStudent();
}