Spring-IOC—基于注解配置Bean

  |   0 评论   |   0 浏览

Spring-IOC—基于注解配置Bean

1.基本使用

1.说明

● 基本介绍

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

● 组件注解的形式有

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

2.快速入门

● 应用实例

使用注解的方式来配置 Controller / Service / Respository / Component

● 代码实现

  1. 引入 spring-aop-5.3.8.jar , 在 spring/libs 下拷贝即可
  2. 创建 UserAction.java UserService.java, UserDao.java MyComponent.java

image-20220515124538230

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

beans.xml

<!--
    配置容器要扫描的包
    1. component-scan 要对指定包下的类进行扫描, 并创建对象到容器
    2. base-package 指定要扫描的包 扫描com.llp.spring.component包下的类及其子包下面的类
    3. 含义是当spring容器创建/初始化时,就会扫描com.llp.spring.component包
    下的所有的 有注解 @Controller / @Service / @Respository / @Component类
    将其实例化,生成对象,放入到ioc容器
    4. resource-pattern="User*.class" 表示只扫描com.llp.spring.component 和它的子包下的User打头的类

-->
<context:component-scan base-package="com.llp.spring.component"/>

测试

/**
 * 通过注解方式配置bean
 */
@Test
public void setBeanByAnnotation(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans05.xml");
    UserDao userDao = ioc.getBean(UserDao.class);
    UserService userService = ioc.getBean(UserService.class);
    UserController userController = ioc.getBean(UserController.class);
    MyComponent myComponent = ioc.getBean(MyComponent.class);
    System.out.println(userDao);
    System.out.println(userService);
    System.out.println(userController);
    System.out.println(myComponent);
}

image-20220515124714275

3.注意事项和细节说明

1.需要导入 spring-aop-5.3.8.jar

2.必须在 Spring 配置文件中指定"自动扫描的包",IOC 容器才能够检测到当前项目中哪些类被标识了注解, 注意到导入 context 名称空间

xmlns:context="http://www.springframework.org/schema/context"

<context:component-scan base-package="com.llp.spring.component"/>

可以使用通配符 * 来指定 ,比如 com.llp.spring.* 表示

==提问: com.hspedu.spring.component 会不会去扫描它的子包?== 会的

3.Spring 的 IOC 容器不能检测一个使用了@Controller 注解的类到底是不是一个真正的控制器。注解的名称是用于程序员自己识别当前标识的是什么组件。

其它的@Service、@Repository 也是一样的道理 [也就是说spring的IOC容器只要检查到注解就会生成对象,但是这个注解的含义 spring 不会识别,注解是给程序员编程方便看的]

4.resource-pattern="User*.class": 表示只扫描满足User打头的类.[使用的少,不想扫描,不写注解就可以, 知道这个知识点即可]

<context:component-scan base-package="com.llp.spring.component" resource-pattern="User*.class"/>

5.排除哪些类 , 以 annotaion 注解为例,如下就是排除@Service注解和@Repository类

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

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.llp.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:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>

debug查看beanFactory中SingletonObjects对象中table属性的值

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

/**
 * 标识该类是一个控制器
 */
@Controller(value = "user")
public class UserController {
}
    /**
     * 通过注解方式配置bean
     */
    @Test
    public void setBeanByAnnotation(){
        ApplicationContext ioc = new ClassPathXmlApplicationContext("beans05.xml");
        //在获取userController时 id变成对应的value值
        UserService userService = ioc.getBean("user",UserService.class);
        System.out.println(userController);
    }

2.手动开发-简单的Spring基于注解配置的程序

1.需求说明

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

2.思路分析

  1. 思路分析+程序结构

    1)我们使用注解方式完成, 这里2不用 xml 来配置

    2) 程序框架图

image-20220515135821737

● 应用实例

  1. 手动实现注解的方式来配置 Controller / Service / Respository / Component
  2. 我们使用自定义注解来完成.

3.代码实现

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

2.获取扫描包下所有.class文件

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

自定义包扫描注解 @ComponentScan

//注解指定运行的类型
@Target(ElementType.TYPE)
//应用范围
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ComponentScan {
    String value() default "";
}

配置类

//配置类, 作用类似我们原生spring的 beans.xml 容器配置文件
@ComponentScan(value = "com.llp.spring.component")
public class LlpSpringConfig {
}

自定义容器-LlpSpringApplicationContext

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

    public LlpSpringApplicationContext(Class aClass) {
        //传入LlpSpringConfig
        this.aClass = aClass;
        //根据LlpSpringConfig类获取到@ComponentScan注解
        ComponentScan componentScan = (ComponentScan) aClass.getDeclaredAnnotation(ComponentScan.class);
        //获取@ComponentScan注解配置的包路径
        //basePackage=com.llp.spring.component
        String basePackage = componentScan.value();
        System.out.println("basePackage=" + basePackage);
        ClassLoader classLoader = LlpSpringApplicationContext.class.getClassLoader();
        //path=com/llp/spring/component
        String path = basePackage.replace(".", "/");
        System.out.println("path=" + path);
        //根据包路径获取目录的绝对路径
        // file:/C:/ide/IdeaProjects/llp-spring/out/production/llp-spring/com/llp/spring/component
        URL resource = classLoader.getResource(path);
        System.out.println(resource);
        //从URL对象中获取file 目录
        File file = new File(resource.getFile());
        //判断文件是否时一个目录
        if (file.isDirectory()) {
            //获取目录下所有的文件
            File[] files = file.listFiles();
            //遍历
            for (File f : files) {
                System.out.println("=========");
                //获取目录下每个文件的绝对路径
                String absolutePath = f.getAbsolutePath();
                //C:\ide\IdeaProjects\llp-spring\out\production\llp-spring\com\llp\spring\component\MyComponent.class
                System.out.println(f.getAbsolutePath());
                //1.获取类名
                String className = absolutePath.substring(absolutePath.lastIndexOf("\\") + 1, absolutePath.indexOf(".class"));
                System.out.println(className);
                //2.获取类的全类名
                //com.llp.spring.component
                String classFullName = path.replace("/", ".") + "." + className;
                System.out.println(classFullName);
                //3.进行过滤,只处理文件目录下的.class文件
                if (absolutePath.endsWith(".class")) {
                    //4.判断是不是需要注入到Spirng容器中,看该类是不是有注解@Component @Service
                    try {

                        //1. Class clazz = Class.forName(classFullName) 可以反射加载类
                        //2. classLoader.loadClass(classFullName); 可以反射类的Class
                        //3. 区别是 : 上面方式后调用来类的静态方法, 下面方法不会
                        //4. aClass.isAnnotationPresent(Component.class) 判断该类是否有 @Component

                        //得到com.llp.spring.component包下的类的Class对象
                        Class<?> cls = classLoader.loadClass(classFullName);
                        //判断该类上是否有@Component、@Controller、@Service、@Repository注解
                        if (cls.isAnnotationPresent(Component.class) ||
                                cls.isAnnotationPresent(Controller.class) ||
                                cls.isAnnotationPresent(Service.class) ||
                                cls.isAnnotationPresent(Repository.class)) {
                                   Class<?> clazz = Class.forName(classFullName);
                                    Object o = clazz.newInstance();
                                    String id = null;
                                    if(cls.isAnnotationPresent(Component.class)){
                                        Component component = cls.getAnnotation(Component.class);
                                        id = component.value();
                                        className = id;
                                    }
                            		//StringUtils.uncapitalize(className)将类的首字母小写
                                    ioc.put(id==null? StringUtils.uncapitalize(className):id,o);
                                        System.out.println(ioc);
                                    }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public Object getBean(String name){
         return ioc.get(name);
    }
}

测试类

public class LlpSpringApplicationContextTest {
    public static void main(String[] args) {
        LlpSpringApplicationContext ioc = new LlpSpringApplicationContext(LlpSpringConfig.class);
        UserService userService = (UserService)ioc.getBean("userService");
        UserDao userDao = (UserDao)ioc.getBean("userDao");
        UserController userController = (UserController)ioc.getBean("userController");
        MyComponent myComponent = (MyComponent)ioc.getBean("myComponent");
    }
}

测试结果

image-20220515164946550

3.自动装配

1.基本说明

● 基本说明

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

  2. @AutoWired 的规则说明

    1)在 IOC 容器中查找待装配的组件的类型,如果有唯一的 bean 匹配,则使用该 bean 装配

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

  3. @Resource 的规则说明

    1)@Resource 有两个属性是比较重要的,分别是 name 和 type,Spring 将@Resource 注解的 name 属性解析为 bean 的名字,而 type 属性则解析为 bean 的类型.所以如果使用 name 属 性,则使用 byName 的自动注入策略,而使用 type 属性时则使用 byType 自动注入策略

    2)如果@Resource 没有指定 name 和 type ,则先使用byName注入策略, 如果匹配不上, 再使用 byType 策略, 如果都不成功,就会报错

  4. 建议,不管是@Autowired 还是 @Resource 都保证属性名是规范的写法就可以 注入

2.注意事项和细节说明

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

4.泛型依赖注入

1.泛型依赖解释

● 基本说明

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

2.应用实例

● 应用实例需求

  1. 各个类关系图

image-20220515174554806

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

● 应用实例-代码实现

//自定义的泛型类
public abstract class BaseDao<T> {
    public abstract void save();
}
//自定义泛型类
public class BaseService<T> {
    @Autowired
    private BaseDao<T> baseDao;

    public void save() {
        baseDao.save();
    }
}
public class Book {
}

public class Phone {
}

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

@Service
public class BookService extends BaseService<Book>{
    //并没有写属性
}

@Service
public class PhoneService extends BaseService<Phone>{
  
}
//通过泛型依赖来配置Bean
@Test
public void setProByDependencyInjection() {

    ApplicationContext ioc =
            new ClassPathXmlApplicationContext("beans07.xml");
    PhoneService phoneService = ioc.getBean("phoneService", PhoneService.class);
    phoneService.save();
    System.out.println("ok");

}

标题:Spring-IOC—基于注解配置Bean
作者:llp
地址:https://llinp.cn/articles/2022/05/15/1652608336057.html