Spring系列二:基于注解配置bean【建议收藏】

news2024/12/24 10:25:18

文章目录

  • 💗通过注解配置bean
    • 🍝基本介绍
    • 🍝快速入门
    • 🍝注意事项和细节
  • 💗自己实现Spring注解配置Bean机制
    • 🍝需求说明
    • 🍝思路分析
    • 🍝注意事项和细节
  • 💗自动装配 @Autowired
    • 🍝`案例1:` @Autowired引出
    • 🍝`案例2:` @Autowired解读
    • 🍚`案例3:` @Resource解读
    • 🍝小结
  • 💗泛型依赖注入

上文中, 我们学习到了 Spring系列一:spring的安装与使用

接下来我们学习, 通过注解配置bean
在这里插入图片描述

💗通过注解配置bean

🍝基本介绍

基于注解的方式配置bean, 主要是项目开发中的组件, 比如Controller, ServiceDao.

  • 组件注解的形式有
    1.@Component 表示当前注解标识的是一个组件
    2.@Controller 表示当前注解标识的是一个控制器, 通常用于Servlet
    3.@Service 表示当前注解表示的是一个处理业务逻辑的类, 通常用于Service
    4.@Repository表示当前注解标识的是一个持久化的类, 通常用于Dao

🍝快速入门

  • 应用实例
    使用注解的方式来配置 Controller / Service / Repository / Component

  • 代码实现
    1引入spring-aop-5.3.8.jar, 在 spring/lib 目录下拷贝即可
    2.在spring/component包下 创建 UserAction .java, UserService.java, UserDao.java, MyComponent.java

//使用 @Repository 标识该类是一个Repository, 即是一个持久化层的类/对象
@Repository
public class UserDao {}
//@Service 标识该类是一个Service类/对象
@Service
public class UserService {}
//@Controller 标识该类是一个控制器Controller, 通常该类是一个Servlet
@Controller
public class UserAction {}
//@Component 标识该类是一个组件, 是一个通用的注解
@Component
public class MyComponent {}

配置文件
在这里插入图片描述
在这里插入图片描述

beans05.xml

<!--配置容器要扫描的包
解读:
1.component-scan 要对指定包下的类进行扫描, 并创建对象到我们的容器中
2.base-package 指定要扫描的包
3.含义是当spring容器创建/初始化时, 就会扫描com.zzw.spring.component包
 下的所有的 有注解 @Controller / @Service / @Repository / @Component 的类
 将其实例化, 生成对象, 放入到ioc容器
-->
<context:component-scan base-package="com.zzw.spring.component"/>

通过注解来配置bean

public class SpringBeanTest {
    //通过注解来配置bean
    @Test
    public void setBeanByAnnotation() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans05.xml");

        UserAction userAction = ioc.getBean(UserAction.class);
        UserService userService = ioc.getBean(UserService.class);
        UserDao userDao = ioc.getBean(UserDao.class);
        MyComponent myComponent = ioc.getBean(MyComponent.class);

        System.out.println("userDao=" + userDao);
        System.out.println("userService=" + userService);
        System.out.println("userAction=" + userAction);
        System.out.println("myComponent=" + myComponent);

        System.out.println("ok");
    }
}

🍝注意事项和细节

1.需要导入spring-aop-5.3.8.jar, 别忘了
2.必须在Spring配置文件中指定 “自动扫描的包”, IOC容器才能够检测到当前项目中哪些类被标识了注解, 注意必须导入context名称空间

<!–配置自动扫描的包–>
<context:component-scan base-package="com.zzw.spring.component"/>
可以使用通配符 * 来指定, 比如 com.zzw.spring.* 表示

问题: com.zzw.spring.component 会不会去扫描它的子包?

3.Spring的IOC容器不能检测一个使用了@Controller注解的类到底是不是一个真正的控制器. 注解的名称是用于程序员自己识别当前标识的是什么组件. 其它的@Service, @Repository也是一样的道理. [也就是说spring的IOC容器只要检查到注解就会生成对象, 但是这个注解的含义spring不会识别, 注解是给程序员编程方便看的.]

4.<context:component-scan base-package=“com.zzw.spring.component” resource-pattern=“User*.class”/>
注意1: resource-pattern=“User*.class”: 表示只扫描 com.zzw.spring.component包 和 它的子包下的User打头的类.[使用的少, 不想扫描, 不写注解就可以]
注意2: 真正运行的是out目录, 所以扫描的是.class文件
在这里插入图片描述
5.排除一些类, 以annotation注解为例
<context: exclude-filter type=“annotation” expression=“org.springframework.stereotype.Service”/>

<!--
    需求: 如果我们希望排除某个包/子包下的某种类型的注解, 可以通过exclude-filter来指定
    1.context:exclude-filter 指定要排除哪些类
    2.type 指定排除方式 annotation表示按照注解来排除
    3.expression="org.springframework.stereotype.Service" 指定要排除的注解的全路径
-->
<context:component-scan base-package="com.zzw.spring.component">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>

– 通过Debug IOC容器结构, 可以一目了然.
在这里插入图片描述
解读
1)<context:exclude-filter> 放在 <context:component-scan>内, 表示扫描需要过滤掉当前包及其子包的某些类
2)type=“annotation”* 按照注解类型进行过滤
3)expression: 就是注解的全类名, 比如org.springframework.stereotype.Service就是@Service注解的全类名, 其他比如@Controller @Repository等, 依此类推
4)上面表示过滤掉com.zzw.spring.component包及其子包下, 加入了@Service 注解的类
5)测试, 修改beans05.xml, 增加exclude-filter, 发现UserService, 不会注入到容器.

6.指定自动扫描哪些注解类

<!--
    需求: 如果我们希望按照自己的规则, 来扫描包/子包下的某些注解, 可以通过include-filter来指定
    1.use-default-filters="false" 表示不使用默认的 扫描机制/过滤机制
    2.context:include-filter 表示要去扫描哪些类
    3.type="annotation" 按照注解的方式去 扫描/过滤
    4.expression="org.springframework.stereotype.Service" 指定要扫描的注解的全路径
-->
<context:component-scan base-package="com.zzw.spring.component" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

7.默认情况: 标记注解后, 类名首字母小写作为id的值, 也可以使用注解的value属性指定id值, 并且value可以省略.

//在默认情况下, 注解标识的类创建对象后, 在容器中, id 为类名的首字母小写
UserDao userDao1 = ioc.getBean("userDao", UserDao.class);
System.out.println("userDao1=" + userDao1);
 /** 
 * 解读
 * 1.标记注解后, 类名首字母小写作为id的值(默认)
 * 2.value="zzwUserDao" 使用指定的 zzwUserDao作为UserDao对象的id
 */
@Repository(value = "zzwUserDao")
public class UserDao {}

在这里插入图片描述
8.关于@Controller, @Service, @Component区别:
https://zhuanlan.zhihu.com/p/454638478

💗自己实现Spring注解配置Bean机制

🍝需求说明

1.自己写一个简单的Spring容器, 通过读取类的注解(@Component, @Controller, @Service, @Repository), 将对象注入到IOC容器
2.也就是说, 不使用Spring原生框架, 我们自己使用 IO + Annotation + 反射 + 集合 技术实现, 打通Spring注解方式开发的技术痛点.

IO知识,传送门 - 注解知识,传送门 - 反射知识,传送门 - 集合知识,传送门

🍝思路分析

1)思路分析+程序结构
2)我们使用注解方式完成, 这里我们不使用xml来配置
3)程序框架图
在这里插入图片描述

1.搭建基本结构并获取扫描的包

自定义注解 ComponentScan

/**
 * 1. @Target(ElementType.TYPE)指定我们的ComponentScan注解可以修饰 Type程序元素
 * 2. @Retention(RetentionPolicy.RUNTIME)指定ComponentScan注解 保留范围
 * 3. String value() default "": 表示ComponentScan注解可以传入 value
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
    String value() default "";
}

容器配置文件 ZzwSpringConfig

//这是一个配置类, 它的作用类似于我们原生spring的 beans.xml 容器配置文件
@ComponentScan(value = "com.zzw.spring.component")
public class ZzwSpringConfig {

}

模拟Spring-ioc容器 ZzwSpringApplicationContext

//ZzwSpringApplicationContext 类的作用类似Spring原生ioc容器
@SuppressWarnings({"all"})
public class ZzwSpringApplicationContext {

    private Class configClass;

    //ioc存放的就是通过反射创建的对象(基于注解方式)
    private final ConcurrentHashMap<String, Object> ioc =
            new ConcurrentHashMap<>();

    //构造器
    public ZzwSpringApplicationContext(Class configClass) {
        this.configClass = configClass;
        //System.out.println("this.configClass=" + this.configClass);
        //获取要扫描的包
        //1.先得到ZzwSpringConfig配置类的 @ComponentScan(value = "com.zzw.spring.component")
        ComponentScan componentScan =
                (ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
        //2.获取componentScan的value => 即要扫描的包
        String path = componentScan.value();
        System.out.println("要扫描的包=" + path);
    }
}

测试

public class ZzwSpringApplicationContextTest {
    public static void main(String[] args) {
        ZzwSpringApplicationContext ioc =
                new ZzwSpringApplicationContext(ZzwSpringConfig.class);
    }
}

2.获取扫描包下所有的.class文件 [扫描的out目录下的component目录, 里面都是.class后缀的文件]

在这里插入图片描述

public class ZzwSpringApplicationContext {

    private Class configClass;

    //ioc存放的就是通过反射创建的对象(基于注解方式)
    private final ConcurrentHashMap<String, Object> ioc =
            new ConcurrentHashMap<>();

    //构造器
    public ZzwSpringApplicationContext(Class configClass) {
        this.configClass = configClass;
        //System.out.println("this.configClass=" + this.configClass);
        //获取要扫描的包
        //1.先得到ZzwSpringConfig配置类的 @ComponentScan(value = "com.zzw.spring.component")
        ComponentScan componentScan =
                (ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
        //2.通过componentScan的value => 即要扫描的包
        String path = componentScan.value();
        System.out.println("要扫描的包=" + path);//com.zzw.spring.component

        //得到要扫描的包下的所有资源(.class 类)
        //1.得到类的加载器
        ClassLoader classLoader = ZzwApplicationContext.class.getClassLoader();
        //2.通过类的加载器获取到要扫描包的资源url =>类似一个路径
        path = path.replace(".", "/");//一定要把 .替换成 /
        URL resource = classLoader.getResource(path);//file:/D:/idea_project/zzw_spring/spring/out/production/spring/com/zzw/spring/component
        System.out.println("resource=" + resource);
        //3.将要加载的资源(.class) 路径下的文件进行遍历
        File file = new File(resource.getFile());//在io中, 目录也是文件
        if (file.isDirectory()) {//检查是否是目录
            File[] files = file.listFiles();
            for (File f : files) {
                System.out.println("=============================");
                System.out.println(f.getAbsolutePath());
            }
        }
    }
}

测试

public class ZzwSpringApplicationContextTest {
    public static void main(String[] args) {
        ZzwSpringApplicationContext ioc =
                new ZzwSpringApplicationContext(ZzwSpringConfig.class);
    }
}

3.获取全类名 反射对象 放入容器

public class ZzwSpringApplicationContext {

    private Class configClass;

    //ioc存放的就是通过反射创建的对象(基于注解方式)
    private final ConcurrentHashMap<String, Object> ioc =
            new ConcurrentHashMap<>();

    //构造器
    public ZzwSpringApplicationContext(Class configClass) {
        this.configClass = configClass;
        System.out.println("this.configClass=" + this.configClass);
        //获取要扫描的包
        //1.先得到ZzwSpringConfig配置类的 @ComponentScan(value = "com.zzw.spring.component")
        ComponentScan componentScan =
                (ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
        //2.通过componentScan的value => 即要扫描的包
        String path = componentScan.value();
        System.out.println("要扫描的包=" + path);//com.zzw.spring.component

        //得到要扫描的包下的所有资源(.class 类)
        //1.得到类的加载器
        ClassLoader classLoader = ZzwApplicationContext.class.getClassLoader();
        //2.通过类的加载器获取到要扫描包的资源url =>类似一个路径
        path = path.replace(".", "/");//一定要把 .替换成 / com/zzw/spring/component
        URL resource = classLoader.getResource(path);//file:/D:/idea_project/zzw_spring/spring/out/production/spring/com/zzw/spring/component
        System.out.println("resource=" + resource);
        //3.将要加载的资源(.class) 路径下的文件进行遍历
        File file = new File(resource.getFile());//在io中, 目录也是文件
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                System.out.println("=============================");
                System.out.println(f.getAbsolutePath());
                //fileAbsolutePath: D:\idea_project\zzw_spring\spring\out\production\spring\com\zzw\spring\component\UserDao.class
                //获取到 com.zzw.spring.component.UserDao
                String fileAbsolutePath = f.getAbsolutePath();

                //这里我们只处理.class文件
                if (fileAbsolutePath.endsWith(".class")) {

                    //1.获取类名
                    String className =
                            fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.lastIndexOf(".class"));
                    System.out.println("className="+className);
                    //2.获取类的完整的路径(全类名)
                    // path.replace("/", ".") => com.zzw.spring.component
                    String classFullName = path.replace("/", ".") + "." + className;//比如 com.zzw.spring.component.UserDao
                    //System.out.println("classFullName=" + classFullName);

                    //3.判断该类是不是需要注入到容器, 就看该类是不是有注解 @Component @Controller...
                    try {
                        //这时, 我们就得到了该类的Class对象
                        //Class clazz = Class.forName(classFullName)
                        //说明
                        //1. Class clazz = Class.forName(classFullName) 可以反射加载类
                        //2. classLoader.loadClass(classFullName); 可以反射类的Class
                        //3. 区别是: 上面方式会调用该类的静态方法, 下面方式不会
                        //4. aClass.isAnnotationPresent(Component.class) 判断该类是否有 Component注解
                        Class<?> aClass = classLoader.loadClass(classFullName);

                        if (aClass.isAnnotationPresent(Component.class) ||
                                aClass.isAnnotationPresent(Repository.class) ||
                                aClass.isAnnotationPresent(Service.class) ||
                                aClass.isAnnotationPresent(Controller.class)) {

                            //这时就可以反射对象, 并放入到容器中
                            Class<?> clazz = Class.forName(classFullName);
                            Object instance = clazz.newInstance();
                            //放入到容器中, 将类名的首字母小写作为id
                            //StringUtils 工具类 import org.springframework.util.StringUtils;

                            ioc.put(StringUtils.uncapitalize(className), instance);
                        }
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }

    //编写方法返回容器对象
    public Object getBean(String name) {
        return ioc.get(name);
    }
}

测试

public class ZzwSpringApplicationContextTest {
    public static void main(String[] args) {
        ZzwSpringApplicationContext ioc =
                new ZzwSpringApplicationContext(ZzwSpringConfig.class);
        UserAction userAction = (UserAction) ioc.getBean("userAction");
        UserDao userDao = (UserDao) ioc.getBean("userDao");
        UserService userService = (UserService) ioc.getBean("userService");
        MyComponent myComponent = (MyComponent) ioc.getBean("myComponent");
        System.out.println("userAction=" + userAction);
        System.out.println("userDao=" + userDao);
        System.out.println("userService=" + userService);
        System.out.println("myComponent=" + myComponent);
        System.out.println("ok");
    }
}

🍝注意事项和细节

案例:还可以通过 @Component(value = “xx”) @Controller(value = “yy”) @Service(value = “zz”) 中指定的 value, 给bean分配id

@Component(value = "zzw1")
public class MyComponent {

}
if (aClass.isAnnotationPresent(Component.class) ||
        aClass.isAnnotationPresent(Repository.class) ||
        aClass.isAnnotationPresent(Service.class) ||
        aClass.isAnnotationPresent(Controller.class)) {

    //这里我们演示一个Component注解指定value, 分配id
    //这里就是演示了一下机制
    if (aClass.isAnnotationPresent(Component.class)) {
        //获取到该注解
        Component component = aClass.getDeclaredAnnotation(Component.class);
        //获取component的value =>即要分配的id
        String id = component.value();
        if (!"".equals(id)) {
            className = id;
        }
    }

    //这时就可以反射对象, 并放入到容器中
    Class<?> clazz = Class.forName(classFullName);
    Object instance = clazz.newInstance();
    //放入到容器中, 将类名的首字母小写作为id
    //StringUtils 工具类
    ioc.put(StringUtils.uncapitalize(className), instance);
}

测试结果
在这里插入图片描述

💗自动装配 @Autowired

基本说明
1.基于注解配置bean, 也可实现自动装配. 使用的注解是: @Autowired 或者 @Resource

  • @Autowired的规则说明
    1. 在ioc容器中查找待装配的组件的类型, 如果有唯一的bean则匹配.

    2. 如果装配的类型对应的bean在ioc容器中有多个, 则使用待装配的属性的属性名作为id值再进行查找, 找到就装配, 找不到就抛异常.

  • @Resource的规则说明
    1. @Resource有两个属性是有两个属性是比较重要的, 分别是name和type. Spring将@Resource注解的name属性解析为bean的名字, 将type属性解析为bean的类型.

    2. 如果使用name属性, 则使用byName的自动注入策略; 而使用type属性时, 则使用byType自动注入策略.

    3. 如果@Resource 没有指定 name 和 type, 则优先使用byName注入策略 (即使用待装配的属性的属性名作为id值进行查找), 如果匹配不上, 再使用byType策略(即查找待装配的组件的类型). 如果都不成功, 就会报错.

  1. 不管是@Autowired 还是 @Resource 都保证属性名是规范写法就可以 注入.

🍝案例1: @Autowired引出

@Service
public class UserService {
    public void hi() {
        System.out.println("UserService hi()...");
    }
}
@Controller
public class UserAction {
    
    private UserService userService;
    
    public void sayOK() {
        System.out.println("UserAction sayOK()....");
        userService.hi();
    }
}
<?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 https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.zzw.spring.component"/>
</beans>
public class SpringBeanTest {
    @Test
    public void setPropertyByAutowired() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans06.xml");
        UserAction userAction = ioc.getBean("userAction", UserAction.class);
        System.out.println("userAction=" + userAction);//这里会输出
        userAction.sayOK();//这里会报空指针异常
    }
}

加入@Autowired, 就不会报错了.

@Controller
public class UserAction {
    @Autowired
    private UserService userService;
    
    public void sayOK() {
        System.out.println("UserAction sayOK()....");
        userService.hi();
    }
}

🍝案例2: @Autowired解读

下面的代码中, UserAction中的userService200 和 SpringBeanTest中的userService是同一个对象. 因为ioc容器中只有一个UserService类型的对象, 此时按照类型来匹配.

@Service
public class UserService {
    public void hi() {
        System.out.println("UserService hi()...");
    }
}
@Controller
public class UserAction {
    //原先的xml配置, 我们会配置ref. 但是注解配置的情况下, 我们会用@Autowired
    //说明
    //1.在ioc容器中查找待装配的组件的类型, 如果有唯一的bean匹配(按照类型), 则使用该bean匹配
    //2.如果待装配的类型对应的bean在ioc容器中有多个, 则使用待装配的属性的属性名作为id值再进行查找
    //  找到就装配, 找不到就抛异常
    @Autowired
    private UserService userService200;

    public void sayOK() {
        System.out.println("UserAction sayOK()....");
        System.out.println("UserAction 装配的 userService属性=" + userService200);
        userService200.hi();
    }
}
<?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 https://www.springframework.org/schema/context/spring-context.xsd">
       
	<!--通过注解获取的对象id是类名首字母小写-->
    <context:component-scan base-package="com.zzw.spring.component"/>
</beans>
public class SpringBeanTest {
    @Test
    public void setPropertyByAutowired() {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("beans06.xml");
        UserService userService = ioc.getBean("userService", UserService.class);
        System.out.println("ioc容器中的userService=" + userService);
        userAction.sayOK();
    }
}

如果在beans06.xml中加入了同一类型(UserService)的对象, 如下. 那么UserAction中的userService200 和 SpringBeanTest中的userService将不再是同一个对象. 因为ioc容器中UserService类型的对象有多个, 此时将按照待匹配属性的属性名作为id值来匹配, 匹配到的是id为userService200的对象.

<?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 https://www.springframework.org/schema/context/spring-context.xsd">

    <!--通过注解获取的对象id是类名首字母小写-->
    <context:component-scan base-package="com.zzw.spring.component"/><!--id=userService-->

    <!--配置两个UserService对象-->
    <bean class="com.zzw.spring.component.UserService" id="userService200"/>
    <bean class="com.zzw.spring.component.UserService" id="userService300"/>
</beans>

如果UserAction改为如下情况, 那么将会报错. 因为此时是按照待匹配属性的属性名作为id值来匹配的, 但beans06.xml中并没有id=userService400的bean对象, 所以报错.

@Controller
public class UserAction {
    //原先的xml配置, 我们会配置ref. 但是注解配置的情况下, 我们会用@Autowired
    //说明
    //1.在ioc容器中查找待装配的组件的类型, 如果有唯一的bean匹配(按照类型), 则使用该bean匹配
    //2.如果待装配的类型对应的bean在ioc容器中有多个, 则使用待装配的属性的属性名作为id值再进行查找
    //  找到就装配, 找不到就抛异常
    @Autowired
    private UserService userService400;

    public void sayOK() {
        System.out.println("UserAction sayOK()....");
        System.out.println("UserAction 装配的 userService属性=" + userService400);
        userService400.hi();
    }
}

指定id进行组装. 可以使用@Autowired和@Qualifier(value=“userService200”). 这时, 是装配的 id=userService200, 类似于@Resource(name="userService200"), 两个注解要配合使用.
此时, UserAction中的userService 和 SpringBeanTest中的userService200是同一个对象.

@Controller
public class UserAction {
    //指定id进行组装. 可以使用@Autowired和@Qualifier(value="userService200"). 
    //这时, 是装配的 id=userService200, 类似于@Resource(name="userService200").
    //前提是需要两个注解都写上. 
    @Autowired
    @Qualifier(value = "userService200")
    private UserService userService;

    public void sayOK() {
        System.out.println("UserAction sayOK()....");
        System.out.println("UserAction 装配的 userService属性=" + userService);
        userService.hi();
    }
}

🍚案例3: @Resource解读

@Resource源码解读: 通过解析注解来支撑, 底层是注解来支撑的.
String name() default "";
Class<?> type() default java.lang.Object.class;

@Resource(name = "userService") 代表把beans06.xml对应的容器里的id=userService的对象注入到属性上去, 所以UserAction中的userService400 和 SpringBeanTest中的userService是同一个对象

@Controller
public class UserAction {

    //1.@Resource 有两个属性是比较重要的, 分别是name和type, Spring将@Resource注解的name属性解析为bean的名字,
    //  将type属性解析为bean的类型. 所以如果使用name属性, 则使用byName的自动注入策略, 而使用type属性时则使用
    //  byType自动注入策略
    //  比如 @Resource(name="userService") 表示装配id=userService的对象
    //  比如 @Resource(type="UserService.class") 表示按照UserService.class类型进行装配. 这时要求容器中,只能有一个这样类型的对象
    //2.如果@Resource 没有指定 name 和 type, 则先使用byName注入策略 (即使用待装配的属性的属性名作为id值进行查找),
    //  如果匹配不上, 再使用byType策略. 如果都不成功, 就会报错.
    @Resource(name = "userService")
    private UserService userService400;

    public void sayOK() {
        System.out.println("UserAction sayOK()....");
        System.out.println("UserAction 装配的 userService属性=" + userService400);
        userService400.hi();
    }
}
<?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 https://www.springframework.org/schema/context/spring-context.xsd">

    <!--通过注解获取的对象id是类名首字母小写, 这里是userService-->
    <context:component-scan base-package="com.zzw.spring.component"/><!--id=userService-->

    <!--配置两个UserService对象-->
    <bean class="com.zzw.spring.component.UserService" id="userService200"/>
    <bean class="com.zzw.spring.component.UserService" id="userService300"/>
</beans>
public class SpringBeanTest {

    @Test
    public void setPropertyByAutowired() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans06.xml");
                
        UserService userService = ioc.getBean("userService", UserService.class);
        System.out.println("ioc容器中的userService=" + userService);

        UserAction userAction = ioc.getBean("userAction", UserAction.class);
        userAction.sayOK();
    }
}

如果将代码改为@Resource(name = "userService200") 代表把beans06.xml对应的容器里的id=userService200的对象注入到属性上去, 那么UserAction中的userService400 和 SpringBeanTest中的userService200将是同一个对象,

@Controller
public class UserAction {

    //1.@Resource 有两个属性是比较重要的, 分别是name和type, Spring将@Resource注解的name属性解析为bean的名字,
    //  将type属性解析为bean的类型. 所以如果使用name属性, 则使用byName的自动注入策略, 而使用type属性时则使用
    //  byType自动注入策略
    //  比如 @Resource(name="userService") 表示装配id=userService的对象
    @Resource(name = "userService200")
    private UserService userService400;

    public void sayOK() {
        System.out.println("UserAction sayOK()....");
        System.out.println("UserAction 装配的 userService属性=" + userService400);
        userService400.hi();
    }
}
<?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 https://www.springframework.org/schema/context/spring-context.xsd">

    <!--通过注解获取的对象id是类名首字母小写, 这里是userService-->
    <context:component-scan base-package="com.zzw.spring.component"/><!--id=userService-->

    <!--配置两个UserService对象-->
    <bean class="com.zzw.spring.component.UserService" id="userService200"/>
    <bean class="com.zzw.spring.component.UserService" id="userService300"/>
</beans>
public class SpringBeanTest {

    @Test
    public void setPropertyByAutowired() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans06.xml");
                
        UserService userService = ioc.getBean("userService", UserService.class);
        System.out.println("ioc容器中的userService=" + userService);

        UserService userService200 = ioc.getBean("userService200", UserService.class);
        System.out.println("ioc容器中的userService200=" + userService200);

        UserAction userAction = ioc.getBean("userAction", UserAction.class);
        userAction.sayOK();
    }
}

如果将代码改为@Resource(name = "userService600") 会直接报错. 因为beans06.xml对应的容器中没有id为userService600的对象, 所以报错.

@Controller
public class UserAction {

    //1.@Resource 有两个属性是比较重要的, 分别是name和type, Spring将@Resource注解的name属性解析为bean的名字,
    //  将type属性解析为bean的类型. 所以如果使用name属性, 则使用byName的自动注入策略, 而使用type属性时则使用
    //  byType自动注入策略
    //  比如 @Resource(name="userService") 表示装配id=userService的对象
    @Resource(name = "userService600")
    private UserService userService400;

    public void sayOK() {
        System.out.println("UserAction sayOK()....");
        System.out.println("UserAction 装配的 userService属性=" + userService400);
        userService400.hi();
    }
}
<?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 https://www.springframework.org/schema/context/spring-context.xsd">

    <!--通过注解获取的对象id是类名首字母小写, 这里是userService-->
    <context:component-scan base-package="com.zzw.spring.component"/><!--id=userService-->

    <!--配置两个UserService对象-->
    <bean class="com.zzw.spring.component.UserService" id="userService200"/>
    <bean class="com.zzw.spring.component.UserService" id="userService300"/>
</beans>

如果按照类型来装配, @Resource(type = UserService.class), 那么必须保证容器中该类型的对象只有一个.
像下面的情况就会报错.

public class UserAction {

    @Resource(type = UserService.class)
    private UserService userService400;

    public void sayOK() {
        System.out.println("UserAction sayOK()....");
        System.out.println("UserAction 装配的 userService属性=" + userService400);
        userService400.hi();
    }
}
<?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 https://www.springframework.org/schema/context/spring-context.xsd">

    <!--通过注解获取的对象id是类名首字母小写-->
    <context:component-scan base-package="com.zzw.spring.component"/><!--id=userService-->

    <!--配置两个UserService对象-->
    <bean class="com.zzw.spring.component.UserService" id="userService200"/>
    <bean class="com.zzw.spring.component.UserService" id="userService300"/>
</beans>

如果@Resource 没有指定 name 和 type, 则先使用byName注入策略 (即使用待装配的属性的属性名作为id值进行查找),如果匹配不上, 再使用byType策略. 如果都不成功, 就会报错. 像下面的代码就会报错.

public class UserAction {

    //2.如果@Resource 没有指定 name 和 type, 则先使用byName注入策略 (即使用待装配的属性的属性名作为id值进行查找),
    //  如果匹配不上, 再使用byType策略. 如果都不成功, 就会报错.
    @Resource
    private UserService userService400;

    public void sayOK() {
        System.out.println("UserAction sayOK()....");
        System.out.println("UserAction 装配的 userService属性=" + userService400);
        userService400.hi();
    }
}
<?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 https://www.springframework.org/schema/context/spring-context.xsd">

    <!--通过注解获取的对象id是类名首字母小写-->
    <context:component-scan base-package="com.zzw.spring.component"/><!--id=userService-->

    <!--配置两个UserService对象-->
    <bean class="com.zzw.spring.component.UserService" id="userService200"/>
    <bean class="com.zzw.spring.component.UserService" id="userService300"/>
</beans>

下面代码会成功

public class UserAction {

    //2.如果@Resource 没有指定 name 和 type, 则先使用byName注入策略 (即使用待装配的属性的属性名作为id值进行查找),
    //  如果匹配不上, 再使用byType策略. 如果都不成功, 就会报错.
    @Resource
    private UserService userService;

    public void sayOK() {
        System.out.println("UserAction sayOK()....");
        System.out.println("UserAction 装配的 userService属性=" + userService);
        userService.hi();
    }
}

下面代码依然会成功

public class UserAction {

    //2.如果@Resource 没有指定 name 和 type, 则先使用byName注入策略 (即使用待装配的属性的属性名作为id值进行查找),
    //  如果匹配不上, 再使用byType策略. 如果都不成功, 就会报错.
    @Resource
    private UserService userService200;

    public void sayOK() {
        System.out.println("UserAction sayOK()....");
        System.out.println("UserAction 装配的 userService属性=" + userService200);
        userService200.hi();
    }
}

下面代码会失败. 因为beans.xml中没有id=userServise600的bean对象, byName注入策略不成功, 并且byType注入策略也不成功, 所以会失败.

public class UserAction {

    //2.如果@Resource 没有指定 name 和 type, 则先使用byName注入策略 (即使用待装配的属性的属性名作为id值进行查找),
    //  如果匹配不上, 再使用byType策略. 如果都不成功, 就会报错.
    @Resource
    private UserService userService600;

    public void sayOK() {
        System.out.println("UserAction sayOK()....");
        System.out.println("UserAction 装配的 userService属性=" + userService600);
        userService600.hi();
    }
}

但是下面的代码会成功. 虽然beans.xml中没有id=userServise600的bean对象, 即byName注入策略不成功, 但是由于beans06.xml对应的容器中只有一个UserService类型的对象, 所以byType策略成功, 所以下面代码不会报错.

public class UserAction {

    //2.如果@Resource 没有指定 name 和 type, 则先使用byName注入策略 (即使用待装配的属性的属性名作为id值进行查找),
    //  如果匹配不上, 再使用byType策略. 如果都不成功, 就会报错.
    @Resource
    private UserService userService600;

    public void sayOK() {
        System.out.println("UserAction sayOK()....");
        System.out.println("UserAction 装配的 userService属性=" + userService600);
        userService600.hi();
    }
}
<?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 https://www.springframework.org/schema/context/spring-context.xsd">

    <!--通过注解获取的对象id是类名首字母小写-->
    <context:component-scan base-package="com.zzw.spring.component"/><!--id=userService-->

    <!--配置两个UserService对象-->
    <!--<bean class="com.zzw.spring.component.UserService" id="userService200"/>-->
    <!--<bean class="com.zzw.spring.component.UserService" id="userService300"/>-->
</beans>

🍝小结

1.如果装配的类型对应的bean在IOC容器中有多个, 则使用待装配的属性的属性名作为id值再进行查找, 找到就装配, 找不到就抛异常.

💗泛型依赖注入

基本说明
1.为了更好地管理有继承和相互依赖的bean的自动装配, spring还提供基于泛型依赖的注入机制.
2.在继承关系复杂情况下, 泛型依赖注入就会有很大的优越性.

各个类关系图
在这里插入图片描述


传统方法是将 PhoneDao / BookDao 自动装配到 BookService / PhoneService中, 当这种继承关系多时, 就比较麻烦. 可以使用spring提供的泛型依赖注入.

public class Book {}
public class Phone {}
//自定义泛型类
public abstract class BaseDao<T> {
    public abstract void save();
}
@Repository
public class BookDao extends BaseDao<Book> {
    @Override
    public void save() {
        System.out.println("BookDao的 save方法....");
    }
}
@Repository
public class PhoneDao extends BaseDao<Phone> {
    @Override
    public void save() {
        System.out.println("PhoneDao的 save方法...");
    }
}
//自定义泛型类
public class BaseService<T> {
    @Autowired
    private BaseDao<T> baseDao;

    public void save() {
        baseDao.save();
    }
}
@Service
public class BookService extends BaseService<Book> {
    //并没有写属性
}
@Service
public class PhoneService extends BaseService<Phone> {
    //没有写属性
}

beans07.xml

<?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 https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.zzw.spring.depinjection"/>
</beans>
public class SpringBeanTest {

    @Test
    public void setProByDependencyInjection() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans07.xml");

        PhoneService phoneService = ioc.getBean("phoneService", PhoneService.class);
        phoneService.save();//PhoneDao的 save方法...
        System.out.println("OK");
    }
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/825129.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

sql 参数自动替换

需求&#xff1a;看日志时&#xff0c;有的sql 非常的长&#xff0c;参数比较多&#xff0c;无法直接在sql 客户端工具执行&#xff0c;如果一个一个的把问号占位符替换为参数太麻烦&#xff0c;因此写个html 小工具&#xff0c;批量替换&#xff1a; 代码&#xff1a; <!…

【Autoresizing案例2 Objective-C语言】

一、Autoresizing案例2 1.那么,接下来,咱们看第二个案例, 刚才我们设置了Autoresizing,的外面四根线,用来设置这个子控件距离父控件的距离,是固定吧 然后呢,我们给大家演示一下什么,演示一下里面这两根线, 里面有一根横的一根线,和一根竖的一根线, 这两根线是什么作…

windows系统的IP、路由、网关、内外网同时访问路由以及修改系统文件hosts的配置

当我们刚刚入职一家公司的时候、一般公司会给我下发一个ip地址和mac地址、还有访问一些公司的平台需要修改hosts之后的路由配置、以及第一次配置内网、如何内外网同时上网。 目录 一、ip的配置 1.1、IP的配置 1.2、mac地址的配置 1.3、内外网路由的配置&#xff08;w11系统需…

c语言指针一

#include "stdio.h" #include <stdlib.h>int main() {//char buf[] "ab";char a[] { a,b,0,c };//内存存储的为数字0 等价于 \0char b[] { a,b,0,c };char c[] { a,b,\0,c };//printf("%s\n",buf);//puts(buf);//数组元素的首地址//fp…

[Linux]基础IO详解(系统文件I/O接口、文件描述符、理解重定向)

hello&#xff0c;大家好&#xff0c;这里是bang___bang_ &#xff0c;今天和大家谈谈Linux中的基础IO&#xff0c;包含内容有对应的系统文件I/O接口&#xff0c;文件描述符&#xff0c;理解重定向。 目录 1️⃣初识文件 2️⃣ 系统文件I/O接口 &#x1f359;open &#x1…

51单片机学习--DS1302可调时钟

之前学习过用定时器做的时钟&#xff0c;但是那样不仅误差大还费CPU&#xff0c;接下来利用DS1302时钟模块做一个可调实时时钟 这一次直接编写DS1302模块&#xff0c;首先要在DS1392.c 中根据下面的模块原理图进行位声明&#xff1a; sbit DS1302_SCLK P3^6; sbit DS1302_IO …

05 Ubuntu下安装.deb安装包方式安装vscode,snap安装Jetbrains产品等常用软件

使用deb包安装类型 deb包指的其实就是debian系统&#xff0c;ubuntu系统是基于debian系统的发行版。 一般我们会到需要的软件官网下载deb安装包&#xff0c;然后你既可以采用使用“软件安装”打开的方法来进行安装&#xff0c;也可以使用命令行进行安装。我推荐后者&#xff…

电脑更新win10黑屏解决方法

电脑更新win10黑屏解决方法 电脑黑屏出现原因解决步骤 彻底解决 电脑黑屏 出现原因 系统未更新成功就关机&#xff0c;导致系统出故障无法关机 解决步骤 首先长安电源键10s关机 按电源键开机&#xff0c;出现logo时按F8进入安全模式。 进入自动修复环境后&#xff0c;单击…

快速排序——“数据结构与算法”

各位CSDN的uu们好呀&#xff0c;今天又是小雅兰的数据结构与算法专栏啦&#xff0c;下面&#xff0c;就让我们进入快速排序的世界吧&#xff01;&#xff01;&#xff01; 快速排序 快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法&#xff0c;其基本思想为&…

工序委外PR审批后,不允许按工单自动更新PR交期

工序委外PR审批后&#xff0c;不允许按工单自动更新PR交期 工序委外PR审批后&#xff0c;更改工单结束日期&#xff0c;PR的交期还是会按工单重新更新交货日期&#xff1f; 检查配置&#xff1a; 1.生产模块集成的配置没有找到这么细的配置点 2.PR审批标识调整也没有效果 没找…

webrtc的回声消除延迟时间估算

叫回声消除的延迟时间估算不太合理&#xff0c;这里核心就是估算调用webrtc的条件边界&#xff0c;都知道webrtc回声消除的生效的前提就是一定要拿到远端声音的信息&#xff0c;然后拿近端声音和远端声音对齐&#xff0c;从近端声音中&#xff0c;结合远端声音模拟出远端声音在…

MySQL安装 找不到 MSVCP100.dll

安装mysql5.6.51时&#xff0c;出现由于找不到 MSVCP100.dll&#xff0c;无法继续执行代码。重新安装程序可能会解决此问题。 这应该是缺少VS运行库文件导致的&#xff0c;运行库就是支持大部分程序运行的基础&#xff0c;由于很多常用软件都是采用 Microsoft Visual Studio 编…

Toolformer :让AI学会使用工具

paper: 《Toolformer: Language Models Can Teach Themselves to Use Tools 》 核心思想&#xff0c; 1. Sampling API Calls &#xff1a;设计设计prompt,让模型生成含API调用的文本&#xff08;如图3&#xff09;&#xff0c;只保留K个概率最高的API调用 2. Executing API …

nvm安装和切换node版本

1、nvm list查看已安装的node版本 2、查看当前使用的npm和node版本 3、安装某版本的node 4、 切换node版本

一行命令删除tag为<none>的镜像

sudo docker images | grep none | awk {print $3;} | xargs sudo docker rmi

理解APP开发的法规和合规性问题

随着移动应用程序的快速普及&#xff0c;越来越多的用户选择使用这些应用程序。但它们也会带来一些问题&#xff0c;其中许多是由于应用程序开发过程中存在的法规和合规性问题。用户需要了解这些问题&#xff0c;以及如何解决它们。 APP开发是一个不断变化的领域&#xff0c;其…

我的创作纪念日——256天

机缘 最开始我写博客没有什么特别的原因&#xff0c;主要是因为以下几点&#xff1a; 练习自己的语言组织能力 记录自己学习生活中学到的知识 为和我同一个学习阶段的朋友提供帮助 事实上最开始我根本不指望我的博客有多少人看&#xff0c;主要是想找一个好的保存 Markdown 笔…

穷举深搜暴搜回溯剪枝(2)

一)电话号码的字母组合 17. 电话号码的字母组合 - 力扣&#xff08;LeetCode&#xff09; 1)画出决策树:只是需要对最终的决策树做一个深度优先遍历 把图画出来&#xff0c;知道每一层在干什么&#xff0c;代码就能写出来了 2)定义全局变量: 1)定义一个哈希表来处理数字和字符串…

【freespace】YOLOP: You Only Look Once for Panoptic Driving Perception论文解读

目录 Abstract 摘要 1、简介 2、 相关工作 2.1. 交通目标检测 2.2. 可驾驶区域分割 2.3. 车道检测 2.4. 多任务的方法 3. 方法 3.1. 编码器 3.1.1 骨干 3.1.2 脖子 3.2. 解码器 3.2.1 探测头 3.2.2 可驾驶区域分段头&车道线分段头 3.3. 损失函数 3.4. 训练模…