学习Spring----基于注解配置bean+自己实现Spring注解配置bean机制

学习Spring----基于注解配置bean+自己实现Spring注解配置bean机制

一. 基于注解配置bean

  1. @Repository 表示当前注解标识的是一个持久化层的类,通常用于Dao类。
  2. @Service 表示当前注解标识的是一个处理业务逻辑的类,通常用于Service类。
  3. @Controller 表示当前注解标识的是一个控制器,通常用于Controller类 / Servlet类。
  4. @Component 表示当前注解标识的是一个组件。
@Repository
public class UserDao {
}
@Service
public class UserService {
}
@Controller
public class UserController {
}
@Component
public class MyComponent {
}
<?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名称空间-->
    <context:component-scan base-package="com.zzti.spring.component" />



</beans>
 @Test
    public void testBeanByAnnotation() {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("beans05.xml");
        UserDao userDao = ioc.getBean(UserDao.class);
        System.out.println("userDao= " + userDao);
        UserService userService = ioc.getBean(UserService.class);
        System.out.println("userService= " + userService);
        UserController userController = ioc.getBean(UserController.class);
        System.out.println("userController= " + userController);
        MyComponent myComponent = ioc.getBean(MyComponent.class);
        System.out.println("myComponent= " + myComponent);
        /**
         * userDao= com.zzti.spring.component.UserDao@37911f88
         * userService= com.zzti.spring.component.UserService@6f1c29b7
         * userController= com.zzti.spring.component.UserController@4d6025c5
         * myComponent= com.zzti.spring.component.MyComponent@7f284218
         */
    }

注意事项:

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

  • <!--配置自动扫描的包,注意需要加入context名称空间--> <context:component-scan base-package="com.zzti.spring.component" /> 也可以使用通配符 * 来指定,比如 com.zzti.spring.* 提问: com.zzti.spring,component会不会扫描它的子包? 会的

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

  • <context:component-scan base-package="com.zzti.spring.component" resource-pattern="User*.class"/> 其中resource-pattern="User*.class":
    表示值扫描满足要求的类。(使用的少,不想扫描,就可以不写注解,了解一下就行)

  • 排除那些类被扫描

  <!--希望排除某个包下的某种类型的注解,可以通过context:exclude-filter
        type: 指定按照某种类型就行过滤,这里指的是annotation注解类型
        expression: 就是注解的全类名 比如: org.springframework.stereotype.Service 就是@Service,以此类推
    -->
<context:component-scan base-package="com.zzti.spring.component">
    <context:exclude-filter type="annotation"  expression="org.springframework.stereotype.Service"/>
</context:component-scan>

在beanFactory下面的singletonObjects中可以看到, userService不会注入到容器中。
在这里插入图片描述

  • 指定自动扫描哪些注解类
<!--指定自动扫描哪些注解类,使用context:include-filter-->
    <context:component-scan base-package="com.zzti.spring.component" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository" />
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    </context:component-scan>

在这里插入图片描述

  • 默认情况下,标记注解后,类名首字母小写作为id的值,也可以使用注解的value属指定id值(value指定的是什么值,id就是什么值, 会替换原本默认的值),并且value可以省略
@Controller(value = "userController01")
//@Controller("userController01")
public class UserController {
}
@Test
public void testBeanByAnnotation_01() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans05.xml");
    UserController userController01 = ioc.getBean("userController01", UserController.class);
    System.out.println("userController01= " + userController01);
}

在这里插入图片描述
扩展@Controller、@Service、@Component之间的区别
https://zhuanlan.zhihu.com/p/454638478

今天主要聊聊@Controller 、@Service和@Component这三个注解的关系和区别。网上很多人对这三个注解进行了详细的解释,但是仅仅局限于理论,个人对于没有经过自己验证的结果总是持怀疑态度,所有花时间研究了一下,也对这三个注解理解的更加透彻。(ps:网上好多回答不一定正确,所以只能自己花时间验证)

附上三个注解的源代码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
	String value() default "";
}


@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // 关键注解
public @interface Controller {
	@AliasFor(annotation = Component.class)
	String value() default "";
}


@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // 关键注解
public @interface Service {
	@AliasFor(annotation = Component.class)
	String value() default "";
}
注解扫描
首先说说这三个注解的关系,从源码中可以看出,@Controller和@Service都派生于@Component,所以三者的使用方式基本没什么差别。(ps:既然这么设计,那一定是有区别的)。

在平时的开发中,我们通常在控制层采用注解@Controller,在业务层采用注解@Service。spring在启动时,有一个非常核心的类ConfigurationClassPostProcessor会对类路径下的所以类进行扫描,将符合条件的bean扫描出来添加到beanDefinitionMap集合中,方便接下来的实例化。具体的扫描过程比较复杂,仅仅贴出核心判断逻辑代码。

org.springframework.core.type.filter.AnnotationTypeFilter

protected boolean matchSelf(MetadataReader metadataReader) {
	AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
	return metadata.hasAnnotation(this.annotationType.getName()) ||
			(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
}
代码解释:

(1)this.annotationType.getName():获取的是注解@Component的全路径名org.springframework.stereotype.Component。

(2)metadata.hasAnnotation(this.annotationType.getName()):判断当前的类是否直接采用注解@Component。

(3)metadata.hasMetaAnnotation(this.annotationType.getName()):如果当前的类没有直接采用@Component,而是采用了类组合注解@Controller,判断组合注解@Controller中是否包含@Component。

至此,所有添加了注解@Controller、@Service和@Component都被spring扫描出来了。(ps:这就说明了其实在扫描的时候spring其实将这三个注解都按照@Component进行扫描的)

@Controller分析
如果不使用springMVC时,三者使用其实是没有什么差别的,但如果使用了springMVC,@Controller就被赋予了特殊的含义。

spring会遍历上面扫描出来的所有bean,过滤出那些添加了注解@Controller的bean,将Controller中所有添加了注解@RequestMapping的方法解析出来封装成RequestMappingInfo存储到RequestMappingHandlerMapping中的mappingRegistry。后续请求到达时,会从mappingRegistry中查找能够处理该请求的方法。

部分核心代码如下:

org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping

protected boolean isHandler(Class<?> beanType) {
	// 判断扫描出来的bean是否包含注解@Controller,
	// 如果包含,springMVC会将其封装为RequestMappingInfo
	return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
			AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
	// 判断传递进来的方法是否包含@RequestMapping,
	// 如果包含,就将其封装成RequestMappingInfo
	RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
	RequestCondition<?> condition = (element instanceof Class ?
			getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
	return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
@Service分析
目前@Service本人没有找到其特殊之处,可能spring官方后续会添加特殊的操作吧。

@Component分析
该注解是万能的注解,通常加在配置类上。

小结
实际上有一个注解本文没有具体讲解,它就是@Repository,由于本人没有亲自验证,所以就没有进行分析,怕误导大家。有具体分析并验证过的网友,大家可以一起探讨。

如果有小伙伴也想验证,可以将断点打在我在文中贴出来的三段核心代码处,在spring启动的时候可以进行调试。具体的代码调用逻辑,可能会在以后的文章中进行分析。

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

在不使用Spring原生框架,我们自己使用 IO+ Annatation + 反射 + 集合 技术实现。

  1. 思路分析
    在这里插入图片描述
  2. 应用实例

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 "";
}

SpringConfig

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

SpringApplicationContext

package com.zzti.spring.annotation;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import java.io.File;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 这个类的作用类似, spring原生的ioc容器
 */
public class SpringApplicationContext {

    private Class configClass;
    private final ConcurrentHashMap<String,Object> ioc = new ConcurrentHashMap<>();

    public ConcurrentHashMap<String, Object> getIoc() {
        return ioc;
    }

    public SpringApplicationContext(Class configClass){
        this.configClass = configClass;

        //1. 解析配置类
        //2. 获取到配置类的 @ComponentScan(value = "com.zzti.spring.component")
        ComponentScan componentScan = (ComponentScan)this.configClass.getDeclaredAnnotation(ComponentScan.class);
        String path = componentScan.value();
        //扫描路径path= com.zzti.spring.component
        //System.out.println("扫描路径path= " + path);

        //3. 获取扫描路径下所有的类文件
        //1) 先得到类加载器
        ClassLoader classLoader = SpringApplicationContext.class.getClassLoader();
        //在获取某个包对应的url时, 要求是com/zzti/spring/component
        //URL resource = classLoader.getResource("com/zzti/spring/component");
        // file:/D:/Exercise%20items/hsp_spring/target/classes/com/zzti/spring/component
        //System.out.println(resource);

        //2) 将path转换成com/zzti/spring/component形式
        path = path.replace(".", "/");

        //3) 获取到我们要加载的类路径(工作路径: file:/D:/Exercise_items/hsp_spring/target/classes/com/zzti/spring/component )

        //解决文件路径空格转变成了转义字符%20的问题
        URL resource = classLoader.getResource(path);
        System.out.println("resource= " + resource);

        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                String fileAbsolutePath = f.getAbsolutePath();
                System.out.println("===============================");
                System.out.println("文件绝对路径= " + fileAbsolutePath);
                if (fileAbsolutePath.endsWith(".class")) {
                    //先得到类的完整类路径, 形式为: com.zzti,spring.component.UserService
                    String className = fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
                    String classFullPath = path.replace("/",".") + "." + className;
                    System.out.println("类名= " + className);
                    System.out.println("类的全路径= " + classFullPath);

                    //这里只处理.class文件
                    try {
                        //得到该类的Class对象
                        //Class clazz= Class.forName(classFullPath);
                        //方式1 Class clazz= Class.forName(classFullPath); 这种方式是调用该类的静态方法
                        //方式2 classLoader.loadClass(classFullPath); 这种方法不会
                        Class<?> aClass = classLoader.loadClass(classFullPath);
                        if (aClass.isAnnotationPresent(Component.class) ||
                                aClass.isAnnotationPresent(Controller.class) ||
                                aClass.isAnnotationPresent(Service.class) ||
                                aClass.isAnnotationPresent(Repository.class)) {
                            //演示一个Controller注解指定value, 分配id
                            if (aClass.isAnnotationPresent(Controller.class)) {
                                //获取到该注解
                                Controller controller = aClass.getDeclaredAnnotation(Controller.class);
                                String id = controller.value();
                                if (!"".endsWith(id)) {
                                    className = id;//替换
                                }
                            }


                            System.out.println("是一个bean= " + aClass);
                            //将其反射生成到ioc中
                            Class<?> aClass1 = Class.forName(classFullPath);
                            try{
                                Object instance = aClass1.newInstance();
                                ioc.put(className,instance);
                            }catch (InstantiationException e){
                                e.printStackTrace();
                            }catch (IllegalAccessException e){
                                e.printStackTrace();
                            }
                        }else {
                            System.out.println("不是一个bean= " + aClass);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("===================================");

                }

            }
        }
    }
}

注意事项与细节:
可以通过@Component(value=“xxx”) / @Controller(value=“yyy”)等等 中指定value,给bean分配id
在这里插入图片描述

三. 自动装配

1. 基本说明

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

a) @AutoWired的规则说明:

  • 在ioc容器中查找待装配的组件的类型,如果有唯一的bean匹配,则使用该bean装配。
<context:component-scan base-package="com.zzti.spring.component" />
@Service
public class UserService {
    public void hi(){
        System.out.println("UserService hi()~~");
    }
}
@Controller(value = "userController01")
public class UserController {

    @Autowired
    private UserService userService;

    public void sayOk(){
        System.out.println("UserController.userService= " + userService);
        userService.hi();
    }
}

在这里插入图片描述

  • 如果待装配的类型对应的bean在ioc容器中有多个,则使用待装配的属性的属性名作为id在进行查找,找到就装配,找不到就抛出异常。若加上@Qualifier(value = "userService02")注解,则根据该注解配置的bean名称,就行自动装配
<context:component-scan base-package="com.zzti.spring.component" />
<!--增加一个UserService对象-->
<bean id="userService02" class="com.zzti.spring.component.UserService"></bean>
@Controller(value = "userController01")
public class UserController {

    @Autowired
    @Qualifier(value = "userService02")//指定id=userService02的UserService对象进行组装
    private UserService userService;

    public void sayOk(){
        System.out.println("UserController.userService= " + userService);
        userService.hi();
    }
}

在这里插入图片描述

b) @Resource的规则说明:

  • @Resource有两个属性是比较重要的,分别是name和type。Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。
<!--配置自动扫描的包,注意需要加入context名称空间-->
<context:component-scan base-package="com.zzti.spring.component" />
<!--增加一个UserService对象-->
<bean id="userService02" class="com.zzti.spring.component.UserService"></bean>
@Controller(value = "userController01")
public class UserController {
    @Resource(name = "userService")
    private UserService userService;

    public void sayOk(){
        System.out.println("userController 装配的 userService属性= " + userService);
        userService.hi();
    }
}
======================================
userController 装配的 userService属性= com.zzti.spring.component.UserService@420a85c4
UserService hi()~~
ioc容器中userService= com.zzti.spring.component.UserService@420a85c4

如果把 @Resource(name = “userService”),修改为@Resource(name = “userService02”)就自动装配userService02这个bean。

如果按照@Resource(type = UserService.class)装配,则beans.xml文件中就只能只有一个userService的bean,否则会报错。

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

  • @Autowired + @Qualifier(value = “userService02”) = @Resource(value = “userService02”)

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

四. 泛型依赖注入

在继承关系复杂情况下,泛型依赖注入就会有很大的优越性。
在这里插入图片描述

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的ave()~~");
    }
}
@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>{
}
 <context:component-scan base-package="com.zzti.spring.depinjection" />
@Test
public void testBeanByDePinJection() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans05.xml");
    BookService bookService = ioc.getBean(BookService.class);
    bookService.save();
    PhoneService phoneService = ioc.getBean(PhoneService.class);
    phoneService.save();
}
========================================================
BookDaoave()~~
PhoneDaosave()
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值