通过反射机制获取注解
@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的注解就要使用包扫描机制
<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和默认作用域
实际这四个注解用哪个都可以 , 它们的功能都是只能起到标识的作用,Spring并没有能力识别一个组件到底是不是它所标记的MVC架构类型
注解名 | 功能 |
---|---|
@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 "";
}
使用步骤
第一步: 引入aop的依赖才支持注解模式(如果加入spring-context依赖之后会关联加入aop的依赖)
第二步:使用context:component-scan标签让Spring扫描去指定包中加了注解的组件,并将这些组件实例化后加入到IoC容器中,Spring负责管理这些bean对象
xmlns:context="http://www.springframework.org/schema/context"
context:component-scan标签
的属性
属性名 | 功能 |
---|---|
base-package | 指定一个需要扫描的基类包,默认Spring容器会扫描这个指定的基类包及其子包中的所有类 |
resource-pattern | 扫描基类包下特定的类,如仅希望扫描基类包下特定的类而非所有类(* 表示匹配任意个字符) |
<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"> 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// 等价于
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);
}
}
扫描多个基类包下加了注解的类
<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的配置文件
<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" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
context:component-scan>
<context:component-scan base-package="com.powernode.spring6.bean" use-default-filters="true">
<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>