Spring6 - (12) Spring IoC注解

image-20230206074352715

Spring6 -(12)Spring IoC注解

1. 注解回顾

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

我们来回顾一下:

  • 第一:注解怎么定义,注解中的属性怎么定义?
  • 第二:注解怎么使用?
  • 第三:通过反射机制怎么读取注解?

1.1 注解的定义,注解中属性的定义

自定义注解:

package com.julissa.annotaqtion;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(value = {ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Component {
    String value();
}

以上是自定义了一个注解:Component

该注解上面修饰的注解包括:Target注解和Retention注解,这两个注解被称为元注解。

Target注解用来设置Component注解可以出现的位置,以上代表表示Component注解只能用在类和接口上。

Retention注解用来设置Component注解的保持性策略,以上代表Component注解可以被反射机制读取。

String value(); 是Component注解中的一个属性。该属性类型String,属性名是value。

1.2 注解的使用

定义一个类:User

package com.julissa.bean;

import com.julissa.annotaqtion.Component;

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

用法简单,语法格式:@注解类型名(属性名=属性值, 属性名=属性值, 属性名=属性值…)

userBean为什么使用双引号括起来,因为value属性是String类型,字符串。

另外如果属性名是value,则在使用的时候可以省略属性名,例如:

package com.julissa.bean;

import com.julissa.annotaqtion.Component;

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

1.3 反射机制读取注解

定义一个类读取注解:

package com.julissa.test;

import com.julissa.annotaqtion.Component;

public class ReflectAnnotation {
    public static void main(String[] args) throws ClassNotFoundException {
        //获取类
        Class<?> aClass = Class.forName("com.julissa.bean.User");
        //判断类上有没有注解
        if (aClass.isAnnotationPresent(Component.class)) {
            //获取注解
            Component annotation = aClass.getAnnotation(Component.class);
            //获取注解的值
            String value = annotation.value();
            System.out.println(value);
        }
    }
}

要读取某个类上的注解,首先需要获取到这个类,通过Class.forName(类的全限定名)获取到这个类

再通过这个类的getAnnotation()方法获取到注解。

1.4 案例

需求:目前只已知一个包的名字,扫描这个包下所有的类,当类上有@Component注解时,则实例化Bean对象,如果没有,则不实例化对象。

User类:

package com.julissa.bean;

import com.julissa.annotaqtion.Component;

@Component("userBean")
public class User {
}

Student类:

package com.julissa.bean;

public class Student {
}

Teacher类:

package com.julissa.bean;

import com.julissa.annotaqtion.Component;

@Component("teacherBean")
public class Teacher {
}

编写程序

package com.julissa.test;

import com.julissa.annotaqtion.Component;

import java.io.File;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class Test {
    public static void main(String[] args) {
        // 存放Bean的Map集合。key存储beanId。value存储Bean。
        Map<String,Object> beanMap = new HashMap<>();
		// 已知包名
        String packageName = "com.julissa.bean";
        // com/julissa/bean
        String path = packageName.replaceAll("\\.", "/");
        URL url = ClassLoader.getSystemClassLoader().getResource(path);
        // 获取一个绝对路径下的所有文件
        File file = new File(url.getPath());
        File[] files = file.listFiles();
        Arrays.stream(files).forEach(f -> {
            String className = packageName + "." + f.getName().split("\\.")[0];
            try {
                // 通过反射机制解析注解
                Class<?> clazz = Class.forName(className);
                // 判断类名上是否有这个注解
                if (clazz.isAnnotationPresent(Component.class)) {
                    // 获取注解
                    Component component = clazz.getAnnotation(Component.class);
                    String beanId = component.value();
                    Object bean = clazz.newInstance();
                    beanMap.put(beanId, bean);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        System.out.println(beanMap);
    }
}

运行结果:

image-20230321161357196

2. 声明Bean的注解

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

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

源码如下:

Component

package org.springframework.stereotype;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {

	String value() default "";


Controller

package org.springframework.stereotype;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {

   @AliasFor(annotation = Component.class)
   String value() default "";

}

service

package org.springframework.stereotype;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
    
	@AliasFor(annotation = Component.class)
	String value() default "";

}

Repository

package org.springframework.stereotype;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {

	@AliasFor(annotation = Component.class)
	String value() default "";

}

通过源码可以看到,@Controller、@Service、@Repository这三个注解都是@Component注解的别名。

也就是说:这四个注解的功能都一样。用哪个都可以。

只是为了增强程序的可读性,建议:

  • 控制器类上使用:Controller
  • service类上使用:Service
  • dao类上使用:Repository

他们都是只有一个value属性。value属性用来指定bean的id,也就是bean的名字。

image-20230321163113159

3. Spring注解的使用

如何使用以上的注解呢?

  • 第一步:加入aop的依赖
  • 第二步:在配置文件中添加context命名空间
  • 第三步:在配置文件中指定扫描的包
  • 第四步:在Bean类上使用注解

第一步:加入aop的依赖

我们可以看到当加入spring-context依赖之后,会关联加入aop的依赖。所以这一步不用做。

image-20230321163733807

第二步:在配置文件中添加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">

</beans>

第三步:在配置文件中指定要扫描的包

<?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.julissa.spring6.bean"/>
</beans>

第四步:在Bean类上使用注解

package com.julissa.spring6.bean;

import org.springframework.stereotype.Component;

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

编写测试程序:

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

运行测试程序

image-20230321164645454

如果注解的属性名是value,那么value是可以省略的。

package com.julissa.spring6.bean;

import org.springframework.stereotype.Component;

@Component("userBean")
public class User {
}

运行测试程序

image-20230321164805757

如果把value属性彻底去掉,spring会被Bean自动取名吗?会的。并且默认名字的规律是:Bean类名首字母小写即可。

package com.julissa.spring6.bean;

import org.springframework.stereotype.Component;

@Component
public class User {
}

也就是说,这个User的bean的名字为:user

编写测试程序

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

运行测试程序

image-20230321164943704

我们将Component注解换成其它三个注解,看看是否可以用:

package com.julissa.spring6.bean;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;

@Controller
public class User {
}

运行测试程序

image-20230321165138797

如果是多个包怎么办?有两种解决方案:

  • 第一种:在配置文件中指定多个包,用逗号隔开。
  • 第二种:指定多个包的共同父包。

创建一个新的包:bean2,定义一个Bean类。

package com.julissa.spring6.bean2;

import org.springframework.stereotype.Component;

@Component
public class Student {
}

配置文件修改:

<?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.julissa.spring6.bean,com.julissa.spring6.bean2"/>
</beans>

编写测试程序

@Test
public void test(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
    User user = applicationContext.getBean("user", User.class);
    System.out.println(user);
    Student student = applicationContext.getBean("student", Student.class);
    System.out.println(student);
}

运行测试程序

image-20230321165606191

我们再来看看,指定共同的父包行不行:

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

运行测试程序

image-20230321165708863

4. 选择性实例化Bean

假设在某个包下有很多Bean,有的Bean上标注了Component,有的标注了Controller,有的标注了Service,有的标注了Repository,现在由于某种特殊业务的需要,只允许其中所有的Controller参与Bean管理,其他的都不实例化。这应该怎么办呢?

这里为了方便,将这几个类都定义到同一个java源文件中了

package com.julissa.spring6.bean;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

@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的无参构造方法执行了");
    }
}

我只想实例化bean下的Controller。配置文件这样写:·

<?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.julissa.spring6.bean" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
</beans>

use-default-filters=“true” 表示:使用spring默认的规则,只要有Component、Controller、Service、Repository中的任意一个注解标注,则进行实例化。

use-default-filters=“false” 表示:不再spring默认实例化规则,即使有Component、Controller、Service、Repository这些注解标注,也不再实例化。

<context:include-filter type=“annotation” expression=“org.springframework.stereotype.Controller”/> 表示只有Controller进行实例化。

编写测试程序:

@Test
public void test(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
}

运行测试程序:

image-20230321171615078

也可以将use-default-filters设置为true(不写就是true),并且采用exclude-filter方式排出哪些注解标注的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.julissa.spring6.bean">
        <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.Controller"/>
    </context:component-scan>
</beans>

运行测试程序

image-20230321172033113

5. 负责注入的注解

@Component @Controller @Service @Repository 这四个注解是用来声明Bean的,声明后这些Bean将被实例化。接下来我们看一下,如何给Bean的属性赋值。给Bean属性赋值需要用到这些注解:

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

5.1 @Value

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

第一步:定义一个类:

package com.julissa.spring6.bean;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class User {
    @Value(value = "julissa")
    private String name;
    
    @Value(value = "20")
    private int age;

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

第二步:在配置文件中声明扫描

<?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.julissa.spring6.bean"/>
</beans>

第三步:编写测试程序

@Test
public void test(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
    User user = applicationContext.getBean("user", User.class);
    System.out.println(user);
}

第四步:运行测试程序

image-20230321175536055

通过以上代码可以发现,我们并没有给属性提供setter方法,但仍然可以完成属性赋值。

如果提供setter方法,并且在setter方法上添加@Value注解,可以完成注入吗?

进行测试:

package com.julissa.spring6.bean;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class User {

    private String name;

    private int age;
    @Value(value = "julissa")
    public void setName(String name) {
        this.name = name;
    }
    @Value(value = "20")
    public void setAge(int age) {
        this.age = age;
    }

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

运行测试程序

image-20230321175714740

通过测试可以得知,@Value注解可以直接使用在属性上,也可以使用在setter方法上。都是可以的。都可以完成属性的赋值。

为了简化代码,以后我们一般不提供setter方法,直接在属性上使用@Value注解完成属性赋值。

测试是否能够通过构造方法完成注入:

package com.julissa.spring6.bean;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class User {

    private String name;

    private int age;

    public User(@Value(value = "julissa") String name,  @Value(value = "20")int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

运行测试程序:

image-20230321175959034

通过测试得知:@Value注解可以出现:

  • 在属性上
  • setter方法上
  • 构造方法的形参上

5.2 @Autowired与@Qualifier

@Autowired注解可以用来注入非简单类型。被翻译为:自动连线的,或者自动装配。

单独使用@Autowired注解,默认根据类型装配。【默认是byType】

Autowired的源码:

package org.springframework.beans.factory.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
	boolean required() default true;
}

源码中有两处需要注意:

  • 第一处:该注解可以标注在哪里?

    • 构造方法上
    • 方法上
    • 形参上
    • 属性上
    • 注解上
  • 第二处:该注解有一个required属性,默认值是true,表示在注入的时候要求被注入的Bean必须是存在的,如果不存在则报错。如果required属性设置为false,表示注入的Bean存在或者不存在都没关系,存在的话就注入,不存在的话,也不报错。

属性上使用@Autowired注解:

第一步:定义一个Dao接口:

package com.julissa.spring6.dao;

/**
 * Dao接口
 */
public interface UserDao {
    public void insert();
}

第二步:定义一个Dao接口的实现类:

package com.julissa.spring6.dao.impl;

import com.julissa.spring6.dao.UserDao;
import org.springframework.stereotype.Repository;

/**
 *
 * UserDao实现类
 */
@Repository
public class MySQLUserDaoImpl implements UserDao {

    @Override
    public void insert() {
        System.out.println("MySQL数据库正在保存用户信息~~");
    }
}

第三步:定义一个Service

package com.julissa.spring6.service;

import com.julissa.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 业务层
 */
@Service
public class UserService {
    @Autowired // 注解在属性上
    private UserDao userDao;

    public void add(){
        userDao.insert();
    }
}

第四步:配置包扫描

<?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.julissa.spring6.dao,com.julissa.spring6.service"/>
</beans>

第五步:编写测试程序

@Test
public void test2(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
    UserService userService = applicationContext.getBean("userService", UserService.class);
    userService.add();
}

第六步:运行测试程序

image-20230321195010099

接下来,再来测试一下@Autowired注解出现在setter方法上:

第一步:修改UserService

package com.julissa.spring6.service;

import com.julissa.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 业务层
 */
@Service
public class UserService {

    private UserDao userDao;

    @Autowired // 注解在setter方法上
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void add(){
        userDao.insert();
    }
}

第二步:运行测试程序

image-20230321195440316

再来测试一下@Autowired注解出现在构造方法上

第一步:修改UserService

package com.julissa.spring6.service;

import com.julissa.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 业务层
 */
@Service
public class UserService {

    private UserDao userDao;

    @Autowired // 注解在构造方法上
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    public void add(){
        userDao.insert();
    }
}

第二步:运行测试代码

image-20230321195645339

测试@Autowired注解注解能不能只标注在构造方法的形参上

第一步:修改UserService

package com.julissa.spring6.service;

import com.julissa.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 业务层
 */
@Service
public class UserService {

    private UserDao userDao;

    //注解在构造方法的形参上
    public UserService(@Autowired UserDao userDao) {
        this.userDao = userDao;
    }

    public void add(){
        userDao.insert();
    }
}

第二步:运行测试代码

image-20230321195841410

当有参数的构造方法只有一个时,@Autowired注解可以省略。

第一步:修改UserService

package com.julissa.spring6.service;

import com.julissa.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 业务层
 */
@Service
public class UserService {

    private UserDao userDao;

    // @Autowired注解省略
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    public void add(){
        userDao.insert();
    }
}

第二步:运行测试代码

image-20230321200013564

如果有多个构造方法,@Autowired肯定是不能省略的。

第一步:修改UserService

package com.julissa.spring6.service;

import com.julissa.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 业务层
 */
@Service
public class UserService {

    private UserDao userDao;

    //注解省略
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    public UserService() {
    }

    public void add(){
        userDao.insert();
    }
}

第二步:运行测试代码

image-20230321200105687

到此为止,我们已经清楚@Autowired注解可以出现在哪些位置了。

@Autowired注解默认是byType进行注入的,也就是说根据类型注入的

如果以上程序中,UserDao接口还有另外一个实现类,会出现问题吗?

第一步:定义另一个UserDao的实现类

package com.julissa.spring6.dao.impl;

import com.julissa.spring6.dao.UserDao;
import org.springframework.stereotype.Repository;

/**
 *
 * UserDao实现类
 */
@Repository //交给Spring管理
public class OracleUserDaoImpl implements UserDao {
    @Override
    public void insert() {
        System.out.println("Oracle数据库正在保存用户信息~~");
    }
}

当你写完这个新的实现类之后,此时IDEA工具已经提示错误信息了:

image-20230321200451808

错误信息中说:不能装配,UserDao这个Bean的数量大于1.

怎么解决这个问题呢?当然要byName,根据名称进行装配了。

@Autowired注解和@Qualifier注解联合起来才可以根据名称进行装配,在@Qualifier注解中指定Bean名称。

package com.julissa.spring6.dao.impl;

import com.julissa.spring6.dao.UserDao;
import org.springframework.stereotype.Repository;

/**
 *
 * UserDao实现类
 */
@Repository //这里没有给bean起名,默认名字是:oracleUserDaoImpl
public class OracleUserDaoImpl implements UserDao {
    @Override
    public void insert() {
        System.out.println("Oracle数据库正在保存用户信息~~");
    }
}

在UserService中用@Qualifier注解中指定Bean名称

package com.julissa.spring6.service;

import com.julissa.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

/**
 * 业务层
 */
@Service
public class UserService {
    @Autowired
    @Qualifier("oracleUserDaoImpl") //注入bean的名称
    private UserDao userDao;


    public void add(){
        userDao.insert();
    }
}

运行测试程序

image-20230321200809625

总结:

  • @Autowired注解可以出现在:属性上、构造方法上、构造方法的参数上、setter方法上。
  • 当带参数的构造方法只有一个,@Autowired注解可以省略。
  • @Autowired注解默认根据类型注入。如果要根据名称注入的话,需要配合@Qualifier注解一起使用。

5.3 @Resource

@Resource注解也可以完成非简单类型注入。那它和@Autowired注解有什么区别?

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

@Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖:【如果是JDK8的话不需要额外引入依赖。高于JDK11或低于JDK8需要引入以下依赖。

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

一定要注意:如果你用Spring6,要知道Spring6不再支持JavaEE,它支持的是JakartaEE9。(Oracle把JavaEE贡献给Apache了,Apache把JavaEE的名字改成JakartaEE了,大家之前所接触的所有的 javax.* 包名统一修改为 jakarta.*包名了。)

如果你是spring5-版本请使用这个依赖

<dependency>
  <groupId>javax.annotation</groupId>
  <artifactId>javax.annotation-api</artifactId>
  <version>1.3.2</version>
</dependency>

@Resource注解的源码如下:

image-20230321203627812

下面来进行测试:

第一步:定义一个Dao接口:

package com.julissa.spring6.dao;

/**
 * Dao接口
 */
public interface UserDao {
    public void insert();
}

第二步:定义一个Dao接口的实现类:

package com.julissa.spring6.dao.impl;

import com.julissa.spring6.dao.UserDao;
import org.springframework.stereotype.Repository;

/**
 *
 * UserDao实现类
 */
@Repository("aaa")
public class MySQLUserDaoImpl implements UserDao {

    @Override
    public void insert() {
        System.out.println("MySQL数据库正在保存用户信息~~");
    }
}

第三步:定义一个Service,@Resource根据名称注入

package com.julissa.spring6.service;

import com.julissa.spring6.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

/**
 * 业务层
 */
@Service
public class UserService {
    @Resource(name = "aaa")
    private UserDao userDao;


    public void add(){
        userDao.insert();
    }
}

第四步:配置包扫描

<?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.julissa.spring6.dao,com.julissa.spring6.service"/>
</beans>

第五步:编写测试程序

@Test
public void test2(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
    UserService userService = applicationContext.getBean("userService", UserService.class);
    userService.add();
}

第六步:运行测试程序

image-20230321204114811

我们把MySQLUserDaoImpl的名字aaa修改为userDao,让这个Bean的名字和UserService类中的UserDao属性名一致:

第一步:修改MySQLUserDaoImpl的名称

package com.julissa.spring6.dao.impl;

import com.julissa.spring6.dao.UserDao;
import org.springframework.stereotype.Repository;

/**
 *
 * UserDao实现类
 */
@Repository("userDao")
public class MySQLUserDaoImpl implements UserDao {

    @Override
    public void insert() {
        System.out.println("MySQL数据库正在保存用户信息~~");
    }
}

第二步:UserService类中Resource注解并没有指定name

package com.julissa.spring6.service;

import com.julissa.spring6.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

/**
 * 业务层
 */
@Service
public class UserService {
    @Resource
    private UserDao userDao;


    public void add(){
        userDao.insert();
    }
}

第三步:运行测试程序

image-20230321204352200

过测试得知,当@Resource注解使用时没有指定name的时候,还是根据name进行查找,这个name是属性名

接下来把UserService类中的属性名修改一下:

package com.julissa.spring6.service;

import com.julissa.spring6.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

/**
 * 业务层
 */
@Service
public class UserService {
    @Resource
    private UserDao userDao2;


    public void add(){
        userDao2.insert();
    }
}

运行测试程序

image-20230321204557006

通过测试得知:当通过name找不到的时候,自然会启动byType进行注入

总结:

@Resource注解:默认byName注入,没有指定name时把属性名当做name,根据name找不到时,才会byType注入。byType注入时,某种类型的Bean只能有一个

6. 全注解式开发

所谓的全注解开发就是不再使用spring配置文件了。写一个配置类来代替配置文件。

第一步:配置类代替spring配置文件

package com.julissa.spring6.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration  // 声明该类为配置类
@ComponentScan({"com.julissa.spring6.dao","com.julissa.spring6.service"}) // 声明扫描范围
public class Spring6Config {
}

第二步:编写测试程序:不再new ClassPathXmlApplicationContext()对象了。

@Test
public void test1(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Config.class);
    UserService userService = applicationContext.getBean("userService", UserService.class);
    userService.add();
}

运行测试程序

image-20230321205841769

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值