标记与扫描:
在以后的开发中,我们有service,control,dao等非常多的类型,需要交给IOC容器去管理,我们产品的一个功能模块对应的就是一个service,control,dao,如果我们现在都将其配置到spring文件中,当功能非常非常多的情况下,这样的配置方式可能会产生几十个甚至上百个bean标签,但如果我们使用了注解的方式,只需要在类上加上相对应的注解,经过spring的扫描之后,把有注解的类自动去匹配对应的bean,这一过程也就自动完成了将类交给IOC容器管理的过程,但若要通过基于注解的方式管理bean,那么这个注解是要添加至我们bean对象所对应的类上面的,如果我们想通过IOC容器去管理第三方jar包所提供的类,我们就不能通过注解来实现,因为该类不是我们自己编写的,我们不能在第三方jar包所提供的类的源码中添加注解,由此可说明,基于注解的方式虽然简单,但并不是万能的
注解:
和XML配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,它并不会在代码运行的过程中实现一些额外的功能,当我们加载或执行某个方法时,我们会去判断当前的类/成员变量/方法/参数上是否加了注解,如果有注解,那么则实现相对应的功能,具体的功能是:框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作
本质上:所有一切的操作都是java代码来完成的,XML和注解只是告诉框架中的java代码如何执行
举例:
元旦联欢会要布置教室,蓝色的地方贴上元旦快乐四个字,红色的地方贴上花,黄色的地方贴上气球,如下所示
班长做了所有标记,同学们来完成具体工作,墙上的标记相当于我们在代码中使用的注解,后面的同学相当于框架中的具体操作
扫描:
spring为了知道程序员在那些地方标记了什么样的注解,就需要通过扫描的方式来进行检测,然后根据标记的注解进行后续的操作
实例演示如下:
新建一个工程或在当前工程中新建一个模块,完成三层架构的创建,具体的演示过程,可参考上篇文章
标识组件的常用注解:
所谓组件就是在IOC容器中,一个bean标签就是一个组件
//将该类作为一个bean被IOC管理 @Component:将类标识为普通组件 @Controller:将类标识为控制层组件 @Service:将类标识为业务层组件 @Repository:将类标识为持久层组件
虽然上述的几个注解在名称上截然不同,但它的功能都是完全相同的,都是将类标识为一个组件,差别就在于标识的组件含义不同,在程序执行的过程中他们是没有任何区别的,但对于程序员来说,当看到某类上添加的注解为@Controller,那么便知该组件为控制层组件
它们之间的关系和区别,如下所示为其源码:
通过查看源码我们得知,@Controller,@Service,@Repository这三个注解只是在@Component注解的基础上新起的名字,对于spring使用IOC容器管理这些组件来说没有任何区别,所以@Controller,@Service,@Repository是给开发人员看的,让我们能够便于分辨组件的作用
注意:虽然他们本质一样,但为了代码的可读性和为了程序结构严谨性,我们不能随便标记,有什么样的功能就标识为什么样的组件,没有任何功能的组件,我们直接添加@Component注解即可
创建组件:
组件的注解只能加在类上,而不能加在接口上,原因是一个bean标签就是一个组件,而bean对象只能是某个类或者某个接口的实现类,如下所示,对于业务层,我们想添加@Service的注解,那么其注解不能添加在UserService上,而要添加至实现类上
其他的类或实现类我们都需要添加对应的注解:
//业务层实现类添加@Service注解将其标识为业务层组件 @Service public class UserServiceImpl implements UserService {
//控制层类添加@Controller注解将其标识为控制层组件 @Controller public class UserController {
//持久层实现类添加@Repository注解将其标识为持久层组件 @Repository public class UserDaoImpl implements UserDao {
组件创建完成之后, spring必须通过扫描才能发挥其作用
扫描组件:
<!-- 通过context:约束进行扫描,base-package后面的值为我们要扫描的包名,当前包下的所有类都会被扫描到--> <context:component-scan base-package="包名"></context:component-scan>
为了简化代码量,我们可以将所有的包都放在一个公共的包下,如下所示:
那么在其约束中,我们就可以直接写总包名即可,如果要扫描的类在不同的包下,那么需要写多个context约束对每个都进行扫描
<context:component-scan base-package="springIocAnnotation_packages"></context:component-scan>
编写测试类:
import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import springIocAnnotation_packages.spring_Service.UserService; import springIocAnnotation_packages.spring_Dao.UserDao; import springIocAnnotation_packages.spring_controller.UserController; public class spring_ByAnnotationTest { @Test public void test(){ ApplicationContext ioc=new ClassPathXmlApplicationContext("spring-ioc-annotation.xml"); UserController userController= ioc.getBean(UserController.class); System.out.println(userController); //当组件实现了接口,只要bean对象是唯一的,即使实现该接口的类不唯一,仍然可以使用该接口进行获得bean对象 UserService userService= ioc.getBean(UserService.class); System.out.println(userService); UserDao userDao= ioc.getBean(UserDao.class); System.out.println(userDao); } }
输出如下:
springIocAnnotation_packages.spring_controller.UserController@272ed83b springIocAnnotation_packages.spring_Service.impl.UserServiceImpl@41fecb8b springIocAnnotation_packages.spring_Dao.impl.UserDaoImpl@120f102b
我们成功的获取到了这三个对象,那么说明扫描和注解的工作已成功为完成,并且还能说明注解加扫描的这种方式的对象确实是被IOC容器在管理,否则我们根本无法通过IOC容器获取到它们
关于扫描组件其实还是存在一些问题的, 此后在进行SSM整合的时候,spring和spring MVC是要放在一起使用的,spring MVC需要扫描的是控制层,而spring需要扫描的是除控制层以外所有的组件,也就是说,当spring MVC扫描了控制层,而spring也扫描了控制层,这样就会导致控制层的组件被扫描了多次,那么有的小伙伴就会说,那我们在spring.xml文件中使用约束的话,我们通过代码设置不让他重复扫描控制层的包不就行了吗?
但我们说过,一个功能模块对应一个三层架构,如果真的使用上述的这种方法,岂不是很麻烦吗?
针对上述这种问题,spring为我们提供了方法去解决该问题!
排除扫描与只扫描:
spring在context标签中为我们提供了两个子标签,如下所示:
expression属性用来指定相关的类或者注解的全类名等,全类名如何查看?
根据注解进行排除扫描:
修改XML文件中的context标签:
<context:component-scan base-package="springIocAnnotation_packages"> <!-- 假设我们规定不扫描控制层 type属性的值为:annotation,expression属性的值为要排除或者要包含的注解的全类名--> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
其他不做任何修改的情况下,进行测试:
抛出异常:没找到UserController的bean对象,由此可说明我们经过上面的在context的子标签中, 设置不扫描控制层,而我们的设置也实现了相对应的功能,因为其他地方我们并没有进行修改呀
那么这样进行设置时候是否会影响其他组件的扫描呢?
一测便知,我们将获取UserController类的bean对象的代码注释,尝试获取其他的bean对象,测试结果如下:
根据类类型进行排除扫描:
修改XML文件中的type属性的值:
<context:component-scan base-package="springIocAnnotation_packages"> <!-- 假设我们规定不扫描控制层 type属性的值为:assignable,expression属性的值为要排除或者要包含的类的全类名--> <context:exclude-filter type="assignable" expression="springIocAnnotation_packages.spring_controller.UserController"/> </context:component-scan>
我们通过类类型进行排除扫描控制层也是可以的,测试结果依然是抛出找不到控制层的bean对象
根据注解只对其进行扫描:
修改XML文件中的context标签:
<context:component-scan base-package="springIocAnnotation_packages"> <!-- 假设我们规定只扫描控制层--> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
测试结果如下,怎么三个都扫描到了呀,我们命名在context标签中设置只扫描控制层啊
原因如下:
那么为什么上面排除扫描并没有受这个规则的影响啊,原因是:该规则是默认扫描当前包下的所有类,而我们通过exclude去排除了不需要扫描的包,但此时我们想通过include去定义只扫描某个包,如果也仅仅使用这个include标签,那么矛盾点在于,该规则本身就是扫描当前包下的所有类啊,你通过include去指定再扫描某个类是没有任何意义的,它不仅会扫描你指定的,顺带将其他的也会被扫描
修改方法:
将use-default-filters 属性的值从默认的true修改为false
即可
<context:component-scan base-package="springIocAnnotation_packages" use-default-filters="fal
测试结果如下所示:
因此在使用context:include-filter时,一定不要忘记将use-default-filters的值设置为false,在扫描组件的过程中,我们可以设置多个排除或者多个包含,而不能又设置多个排除又设置多个包含,这样是没有意义的,排除的反面就是包含呀,而且从代码角度来讲,如果一个项目的所有的包都位于一个总包下,那么我们设置包含就必须设置use-default-filters为false,如果我们设置排除,那么它的值必须为true,因此,二者是无法同时存在的
注解+扫描的方式是将注解标识的类经过扫描之后在IOC容器中自动配置一个相对应的bean,这个bean不需要我们配置,通过注解+扫描的方式配置的bean-->它是一个bean标签,class就是当前加上注解的类的全类名,但bean都是有id的呀,那通过注解+扫描的方式配置的bean的id是什么呀?
答案是:默认值为类的小驼峰[类名的首字母小写的结果]
举例:
UserController---->userController UserService---->userService
自定义其id值:
举例:
//将控制层中的注解值设置为controller @Controller("controller")
//测试类中尝试通过id+类的方式获取bean对象,注意这里我们的id是默认值 UserController userController= ioc.getBean("userController",UserController.class);
测试结果如下所示:
产生异常的原因就是因为我们已经通过修改注解值的方式将其bean对象的id修改了呀,继续使用默认的id值当然获取不到
尝试使用修改后的值获取bean对象:
UserController userController= ioc.getBean("controller",UserController.class);
测试结果如下:获取成功