Spring系列三:基于注解配置bean

news2024/12/26 0:09:16

文章目录

  • 💗通过注解配置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/845832.html

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

相关文章

RISC-V基础之内存分布与映射

内存映射是指将地址空间划分为不同的部分或段&#xff0c;每个段有不同的用途和属性。这段话介绍了五个段&#xff1a;文本段、全局数据段、动态数据段、异常处理器段和操作系统&#xff08;OS&#xff09;段。下面是对每个段的简要说明&#xff1a; 文本段&#xff1a;存放程序…

cookie、sessionStorage、localStorage的使用介绍,通过localStorage保存当前菜单、已打开菜单

这篇文章主要介绍前端常用的几种存储技术cookie、storage&#xff08;sessionStorage、localStorage&#xff09;的简单介绍和使用方法。 一、cookie 1、在JavaScript中使用cookie 使用原生的cookie // 设置cookie document.cookie "nameTom;age18;";// 获取coo…

使用Python和wxPython构建中文OCR截图工具

引言&#xff1a; 随着数字化时代的到来&#xff0c;我们经常需要从图像中提取文本信息。而在处理中文文本时&#xff0c;OCR&#xff08;Optical Character Recognition&#xff0c;光学字符识别&#xff09;技术发挥着重要作用。本文将介绍如何使用Python编程语言和wxPython图…

powershell几句话设置环境变量

设置环境变量比较繁琐&#xff0c;现在用这段话&#xff0c;在powershell中就可以轻松完成。 $existingPath [Environment]::GetEnvironmentVariable("Path", "Machine") $newPath "C:\Your\Path\Here"if ($existingPath -split ";"…

ORACLE常用基础

. 1.oracle开机启动流程 su - oracle lsnrctl start lsnrctl status sqlplus / as sysdba startup 2、如何查看数据库版本 select * from v$version; 3.如何查看用户从那个设备连接的数据库 SELECT DISTINCT machine , terminal FROM V$SESSION; 4.如何查看表结构 selec…

【严重】Apache Airflow Run Task 权限绕过漏洞

漏洞描述 Airflow 是一个开源的工作流自动化平台&#xff0c;它允许用户定义、调度和监视工作流任务的执行。Run Task 是通过 Airflow 的 Web 界面或命令行工具。 在 Airflow 受影响版本中&#xff0c;Run Task 功能允许用户手动触发执行特定的任务&#xff0c;而不受正常的任…

CASAIM与迈普医学合作,基于CT数据三维重建和设计,在医疗辅具研究开发中实现高精度三维建模和偏差比对

近期&#xff0c;CASAIM与广州迈普再生医学科技股份有限公司&#xff08;简称&#xff1a;迈普医学&#xff09;合作&#xff0c;基于CT数据三维重建和设计&#xff0c;在医疗辅具研究开发中实现高精度三维建模和偏差比对&#xff0c;实现与缺损区域的精确匹配。 迈普医学&…

全新二开美化版UI好看的社区源码下载/反编译版

2023全新二开美化版UI精美的社区源码下载/反编译版 之前我分享过Rule原版&#xff0c;相信大家已经有很多人搭建好了。这次我要分享的是RuleAPP的二开美化版&#xff08;请尊重每个作者的版权&#xff09;&#xff0c;这个版本没有加密&#xff0c;可以进行反编译&#xff0c;…

C#实现SqlServer数据库同步

实现效果&#xff1a; 设计思路&#xff1a; 1. 开启数据库及表的cdc&#xff0c;定时查询cdc表数据&#xff0c;封装sql语句(通过执行类型&#xff0c;主键;修改类型的cdc数据只取最后更新的记录)&#xff0c;添加到离线数据表&#xff1b; 2. 线程定时查询离线数据表&#xf…

访问器模式(C++)

定义 表示一个作用于某对象结构中的各元素的操作。使得可以在不改变(稳定)各元素的类的前提下定义(扩展)作用于这些元素的新操作(变化)。 应用场景 在软件构建过程中&#xff0c;由于需求的改变&#xff0c;某些类层次结构中常常需要增加新的行为(方法)&#xff0c;如果直接…

分布式理论之CAP与BASE

分布式理论之CAP与BASE 一、什么是CAP1. 一致性&#xff08;Consistency&#xff09;&#xff1a;2. 可用性&#xff08;Availability&#xff09;&#xff1a;3. 分区容错性&#xff08;Partition tolerance&#xff09;&#xff1a;4. CAP小结&#xff1a; 二、AP&CP如何…

通向架构师的道路之weblogic与apache的整合与调优

一、BEAWeblogic的历史 BEA WebLogic是用于开发、集成、部署和管理大型分布式Web应用、 网络应用和数据库应 用的Java应用服务器。将Java的动态功能和Java Enterprise标准的安全性引入大型网络应用的 开发、集成、部署和管理之中。 BEA WebLogic Server拥有处理关键Web应…

气味传感器

本文将通过图文及视频的形式为各位介绍太阳诱电因应市场需求而开发的气味传感器产品。高浓度端采用QCM型&#xff0c;较低浓度端采用MEMS半导体型。另外还准备有FBAR型转换器。 太阳诱电为实现高灵敏度感应而开发了3款转换器。  高浓度端采用QCM型&#xff0c;较低浓度端采用…

python excel 操作

excel文件内容如下&#xff1a; 一、xlrd 读Excel 操作 1、打开Excel文件读取数据 filexlrd.open_workbook(filename)#文件名以及路径&#xff0c;如果路径或者文件名有中文给前面加一个 r 2、常用函数 &#xff08;1&#xff09;获取一个sheet工作表 table file.sheets(…

LeetCode 热题 100 JavaScript--234. 回文链表

function ListNode(val, next) {this.val val undefined ? 0 : val;this.next next undefined ? null : next; }var isPalindrome function (head) {if (!head || !head.next) {return true; }// 使用快慢指针法找到链表的中间节点let slow head;let fast head;while …

在win10上安装Docker desktop,并在docker中安装mysql

操作步骤: 安装docker 去 官网 下载安装软件;选择windows 2. 依次点击安装; 安装完成后无需登录,直接启动即可; 如果有提示需要更新wsl,执行以下命令更新: wsl --update wsl --status 查看状态 验证一下 适用命令:docker run hello-world 设置国内镜像 docker安装mysql…

Dockerfile部署golang,docker-compose

使用go镜像打包&#xff0c;运行在容器内 redis和mysql用外部的 项目目录结构 w1go项目&#xff1a; Dockerfile # 这种方式是docker项目加上 本地的mysql和redis环境 # go打包的容器 FROM golang:alpine AS builder# 为我们镜像设置一些必要的环境变量 ENV GO111MODULEon …

Apollo配置覆盖引发懒加载配置失效问题

背景 出现问题版本过低&#xff0c;最新版本可能已经做出调整&#xff0c;此文章只是记录 apollo-client.version 1.2.0 因为本地开发&#xff0c;想用测试环境的apollo&#xff0c;所以经常会出现本地覆盖测试环境的配置&#xff1b;一般我会新建一个local.properties来覆盖 …

Debian安装和使用Elasticsearch 8.9

命令行通过 .deb 包安装 Elasticsearch 创建一个新用户 adduser elastic --> rust # 添加sudo权限 # https://phoenixnap.com/kb/how-to-create-sudo-user-on-ubuntu usermod -aG sudo elastic groups elastic下载Elasticsearch v8.9.0 Debian 包 https://www.elastic.co/…

GATK BaseRecalibratorSpark 过程中因Too many open files终止

Error&#xff1a; GATK BaseRecalibratorSpark 过程中因Too many open files终止 执行命令&#xff1a; nohup time ./gatk --java-options "-Xmx16G" BaseRecalibratorSpark -R ../../alignment/hg38/hg38.fa -I ../../alignment/bam/P368T.sorted.markdup.bam …