注解扫描原理
通过反射机制获取注解
@Target(value = {ElementType.TYPE})// 设置Component注解可以出现的位置,以上代表表示Component注解只能用在类和接口上
@Retention(value = RetentionPolicy.RUNTIME)// 设置Component注解的保持性策略,以上代表Component注解可以被反射机制读取
public @interface Component {
// 注解中的一个属性, 该属性类型String,属性名是value
String value();
}
假设我们现在只知道一个包名, 这个包下有多少个Bean我们不知道, Bean上有没有注解也不知道,如何通过程序自动将类上有注解的Bean实例化
@Component(value = "userBean")// 语法格式:@注解类型名(属性名=属性值, 属性名=属性值, 属性名=属性值......)
public class User {
}
// 没有注解的Bean
public class Vip {
}
public class ComponentScan {
public static void main(String[] args){
// 存放Bean的Map集合,key存储beanId,value存储Bean
Map<String,Object> beanMap = new HashMap<>();
// 通过包的名字扫描这个包下所有的类,当这个类上有@Component注解的时候实例化该对象,然后放到Map集合中
String packageName = "com.powernode.bean";
// 开始写扫描程序,将包名换成路径获取目录下的所有文件
// 在正则表达式中"."属于通配符代表任意字符,使用"\."代表一个普通的"."字符(java中"\"表示转义字符所以需要使用"\\.")
String packagePath = packageName.replaceAll("\\.", "/");
String packagePath = packageName.replaceAll("\\.", "/");
// 从类的根路径下加载资源,自动返回一个URL类型的对象(路径)
URL url = ClassLoader.getSystemClassLoader().getResource(packagePath);
// 获去扫描的类所在的绝对路径
String path = url.getPath();
// 获取一个绝对路径下的所有文件
File file = new File(path);
// 遍历这个路径下的所有文件,每个文件都是File对象,最后存到一个File类型的数组中
File[] files = file.listFiles();
Arrays.stream(files).forEach(f -> {
try {
// f.getName()获取com.powernode.bean包下的文件名User.class和Vip.class
// f.getName().split("\\.")[0],先通过"."对文件名进行拆分,然后取数组的第一个元素即文件的简类名User和Vip
// 拼接字符串得到文件的全类名com.powernode.bean.User和com.powernode.bean.Vip
String className = packageName+ "." + f.getName().split("\\.")[0];
// 通过全类名获取类的字节码对象
Class<?> aClass = Class.forName(className);
// 判断类上是否有Component注解
if (aClass.isAnnotationPresent(Component.class)) {
// 获取Component注解
Component annotation = aClass.getAnnotation(Component.class);
// 获取Component注解的属性值及即bean的id
String id = annotation.value();
// 有Component注解的都要创建对象
Object obj = aClass.newInstance();
// 将创建的对象放入Map集合当中
beanMap.put(id, obj);
}
} catch (Exception e) {
e.printStackTrace();
}
});
// Map集合中只有User对象,因为Vip类上没有Component注解
System.out.println(beanMap);
}
}
注册组件的四个注解
注解的存在主要是为了简化XML的配置, Spring6倡导全注解开发,所以只要使用了Spring的注解就要使用包扫描机制
- 使用注解一般加入的是自己写的组件 , 使用bean标签配置加入的是别人的写的组件 , 开发中常用注解和bean配置相结合的方式
<?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:component-scan base-package="com.powernode.spring6.bean4"/>
</beans>
标识Bean的四个注解
使用标识Bean的四个注解和使用XML配置的方式将组件加入到容器中后组件的默认行为都是一样的 , 组件都有id和默认作用域
- 四个注解都只有一个value属性用来指定bean的id,bean的名字默认是组件的简单类名首字母小写后得到的字符串 , 作用域默认就是单例的
- 优点: 可读性比较好简化了bean的声明, 更符合MVC的设计理念, 这种声明bean的方式是目前企业中较为常见的bean的声明方式
- 缺点: 没有任何一个地方可以查阅整体信息,只有当程序运行起来才能感知到加载了多少个bean
实际这四个注解用哪个都可以 , 它们的功能都是只能起到标识的作用,Spring并没有能力识别一个组件到底是不是它所标记的MVC架构类型
- 即使将@Respository注解用在一个表述层控制器组件上面也不会产生任何错误
- 所以@Respository、@Service、@Controller这几个注解仅仅是为了让开发人员自己明确当前的组件扮演的角色,增强程序的可读性
注解名 | 功能 |
---|---|
@Repository | 标识一个受Spring IOC容器管理的持久化层组件,给数据库层 (持久化层 , dao层)的组件添加这个注解 |
@Service | 标识一个受Spring IOC容器管理的业务逻辑层组件,推荐给业务逻辑层的组件添加这个注解 |
@Controller | 标识一个受Spring IOC容器管理的表述层控制器组件,推荐给控制层也就是Servlet包下的这些组件加这个注解 |
@Component | 标识一个受Spring IOC容器管理的普通组件,给不属于以上几层的组件添加这个注解 |
@Scope | 指定加入的组件是多实例的还是单实例的,默认是单实例的 , prototype属性 表示指定的bean是多实例的 |
四个注解的源码
@Component源码: @Controller、@Service、@Repository这三个注解都是@Component注解的别名,使用别名的方式可读性更好
@Target(value = {ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Component {
String value();
}
@Repository , @Controller ,@Service注解源码
@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 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 "";
}
使用Spring的IoC注解
使用步骤
第一步: 引入aop的依赖才支持注解模式(如果加入spring-context依赖之后会关联加入aop的依赖)
第二步:使用context:component-scan标签让Spring扫描去指定包中加了注解的组件,并将这些组件实例化后加入到IoC容器中,Spring负责管理这些bean对象
- 在xmlns头部信息中添加context命名空间的配置信息:
xmlns:context="http://www.springframework.org/schema/context"
- 在xsi约束文件中添加约束信息:http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
context:component-scan标签
的属性
属性名 | 功能 |
---|---|
base-package | 指定一个需要扫描的基类包,默认Spring容器会扫描这个指定的基类包及其子包中的所有类 |
resource-pattern | 扫描基类包下特定的类,如仅希望扫描基类包下特定的类而非所有类(* 表示匹配任意个字符) |
<?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">
<!--告诉Spring框架要扫描哪些包中的类-->
<context:component-scan base-package="com.powernode.spring6.bean"> </context:component-scan>
<!--过滤特定的类-->
<context:component-scan base-package="com.powernode.spring6" resource-pattern="bean/*"/>
</beans>
第三步:在Bean类上使用注解, 虽然Component注解换成其它三个注解照样创建bean对象,但为了可读性应该根据属于MVC架构模式的哪一层加对应类型注解
//@Component(value = "userBean")
@Component("userBean")
public class User {
}
public class AnnotationTest {
@Test
public void testBean(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User userBean = applicationContext.getBean("userBean", User.class);
System.out.println(userBean);
}
}
使用细节
如果IoC注解没有设置value属性,Spring会为bean自动取名默认是bean首字母小写后的类名
@Repository// 等价于<bean id="bankDao" class="com.powernode.spring6.bean.BankDao"></bean>
public class BankDao {
}
public class AnnotationTest {
@Test
public void testBean(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
BankDao bankDao = applicationContext.getBean("bankDao", BankDao.class);
System.out.println(bankDao);
}
}
扫描多个基类包下加了注解的类
- 第一种: 在配置文件中指定多个基类包使用逗号隔开
- 第二种: 指定多个基类包的共同父包,但是这样扫描范围就大了肯定要牺牲一部分效率
<?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:component-scan base-package="com.powernode.spring6.bean,com.powernode.spring6.bean2"/>
<!--扫描多个基类包共同的父包,那么这些包都会扫描进来-->
<context:component-scan base-package="com.powernode.spring6"/>
</beans>
选择性实例化包下的Bean
假设在某个包下很多类上都标注了不同类型的注解,现在由于特殊业务的需要只允许其中所有的加了Controller
注解的类参与Bean管理,其他的都不实例化
@Component
public class A {
public A() {
System.out.println("A的无参数构造方法执行");
}
}
@Controller
class B {
public B() {
System.out.println("B的无参数构造方法执行");
}
}
@Service
class C {
public C() {
System.out.println("C的无参数构造方法执行");
}
}
@Repository
class D {
public D() {
System.out.println("D的无参数构造方法执行");
}
}
context:component-scan标签
的属性
属性值 | 功能 |
---|---|
use-default-filters=“true”(默认) | 只要扫描的bean上有Component、Controller、Service、Repository中的任意一个注解都会进行实例化 |
use-default-filters=“false” | 不再使用spring默认实例化规则, 让所有bean上的Component、Controller、Service、Repository注解全部失效 |
context:component-scan标签
的子标签(指定包含与排除基类包下的哪些类)
标签名 | 功能 |
---|---|
context:exclude-filter | 指定扫描基类包时要排除在外的目标类,type属性指定排除的规则 , 默认全部扫描进来并实例化 |
context:include-filter | 指定扫描基类包时要包含在内的目标类,扫描时一定要禁用默认的过滤规则(默认全都扫描) , type属性指定排除规则 |
context:exclude-filter标签和context:include-filter标签
的type
属性和expression
属性的值
Type属性的值 | expression属性的值 | 说明 |
---|---|---|
annotation | 要过滤的注解的全类名 | 按照bean上的注解类型进行过滤 (常用) |
assignable | 要过滤的类的全类名 | 按照类的全类名过滤指定类和它的子类 |
aspectj | com.atguigu.*Service | 根据AspectJ表达式进行过滤 (常用) , 过滤所有类名是以Service结束的类或其子类 |
regex | com.atguigu.anno.* | 根据正则表达式匹配到的类名进行过滤 , 过滤com.atguigu.anno包下的所有类 |
custom | com.atguigu.XxxTypeFilter | 使用XxxTypeFilter类通过编码的方式自定义过滤规则 该类必须实现 org.springframework.core.type.filter.TypeFilter 接口 |
spring的配置文件
<?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">
<!--第一种方案:false表示该包下的所有的带有声明Bean的注解全部失效,组件都不再实例化-->
<context:component-scan base-package="com.powernode.spring6.bean" use-default-filters="false">
<!--只有带有Controller注解的组件被包含进来,其他注解全部失效-->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--第二种方案:true表示该包下的所有的带有声明Bean注解的组件全部都会实例化-->
<context:component-scan base-package="com.powernode.spring6.bean" use-default-filters="true">
<!--将带有Repository,Service,Component注解的组件全部排除在外-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Component"/>
</context:component-scan>
</beans>
Type其他属性的测试
<!--默认是该基类包下所有的类全部扫描进来,根据规则单独指定要排除基类包下的哪些类不扫描进来-->
<context:component-scan base-package="com.powernode.spring6.bean">
<!--根据全类名不扫描基类包下的指定类和它的子类-->
<context:exclude-filter type="assignable" expression="com.powernode.spring6.bean.A">
</context:component-scan>
<!--禁用默认的过滤规则,该包下所有的类都不会扫描进来,单独指定要将该包下的哪些类要扫描进来-->
<context:component-scan base-package="com.atguigu" use-default-filters="false">
<!--根据全类名扫描基类包下的指定类和它的子类-->
<context:include-filter type="assignable" expression="com.powernode.spring6.bean.A">
</context:component-scan>