我随意写,你随意看。

林老头儿

但愿绝望和无奈远走高飞

Fork me on Gitee

Spring注解驱动开发1:组件注册

Spring注解驱动开发系列:

  1. Spring注解驱动开发1:组件注册
  2. Spring注解驱动开发2:生命周期和属性赋值
  3. Spring注解驱动开发3:自动装配
  4. Spring注解驱动开发4:AOP使用和原理
  5. Spring注解驱动开发5:Spring声明式事务
  6. Spring注解驱动开发6:Spring扩展原理

Spring注解驱动开发1:组件注册

在Spring中可以通过XML文件的方式配置Bean,但是在后来的SpringBoot中,使用更多的是通过注解来配置Spring,因此本系列文章主要介绍Spring注解的使用。

@Configuration和@Bean

在项目中编写如下配置类,使用@Configuration表示这是Spring的配置类,使用@Bean表示方法返回的类是一个bean,注册到spring容器中。

//配置类==配置文件
@Configuration
public class MainConfigure {

    //给容器中注册一个Bean,类型为返回值的类型,id默认是用方法名作为id
    @Bean
    public User user() {
        return new User("jinchengll", 18);
    }

}

在MainTest中编写如下代码来启动Spring和获取Bean:

public class MainTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new
                AnnotationConfigApplicationContext(MainConfigure.class);
        //User user = (User) applicationContext.getBean("user");
        User user = (User) applicationContext.getBean(User.class);
        System.out.println(user);
    }
}

可以通过bean名称和类型进行获取。

输入如下:

User{name='jinchengll', age=18}

如果需要指定Bean的名称,可以有如下两种方法:

  1. 修改配置类中对应Bean的方法名,例如修改user()为user1(),则Bean的名称就会变成user1。
  2. 修改配置类中对应Bean的方法的注解,@Bean注解可以指定name,例如@Bean(name = "user2")就可以将Bean名称指定为user2。

@ComponentScan-自动包扫描

在上面的例子中,对于Bean需要手动在Configure文件中创建,所以有了@ComponentScan注解,让Spring自动扫描Bean并装载到容器中。

在配置类中增加@ComponentScan注解,修改MainConfigure类为如下代码:

//配置类==配置文件
@Configuration
//开启自动包扫描,传入要扫描的包路径
@ComponentScan(basePackages = "com.lin.springL")
public class MainConfigure {

}

创建dao、service、controller文件夹,并创建如下代码:

//让Spring扫描到
@Controller
public class UserController {
}
//让Spring扫描到
@Service
public class UserService {
}
//让Spring扫描到
@Component
public class UserDao {

}

在MainTest中进行验证是否被Spring加载:

public class MainTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new
                AnnotationConfigApplicationContext(MainConfigure.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
    }
}

得到输入如下:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfigure
userController
userDao
userService

其中除了spring自己的一些Bean,我们自定义的Bean都被自动扫描到并加入容器中。其中MainConfigure也被扫描到,通过进入Configuration注解类可以看到他也是被@Component所注解。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {

@ComponentScan的属性参数

进入@ComponentScan注解类可以看到其有如下参数:

  • Filter[] includeFilters() default {};表示扫描只包含哪些注解
  • Filter[] excludeFilters() default {};表示扫描不要包含哪些注解

例子:

要排除Controller和Service注解,则将MainConfigure类更改为如下代码:

//配置类==配置文件
@Configuration
//开启自动包扫描,传入要扫描的包路径
//在这里使用FilterType.ANNOTATION规则来过滤,可以有多种方案,具体进入Filter类查看
@ComponentScan(basePackages = "com.lin.springL", excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, 
                value = {Controller.class, Service.class})})
public class MainConfigure {

}

再次运行MainTest类,得到输入如下:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfigure
userDao

很明显,UserController和UserService已经被排除在外了。

自定义Filter过滤规则

Spring允许我们自定义包扫描时候的Filter规则,这时候需要将type指定为CUSTOM,例子如下:

实现org.springframework.core.type.filter.TypeFilter:

public class MyTypeFilter implements TypeFilter {
    /**
     * @param metadataReader        读取到当前正在扫描的类信息
     * @param metadataReaderFactory 可以获取到其他任何类信息
     * @return 表示是否匹配上
     * @throws IOException 异常
     */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        //获取当前类的注解信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //获取当前类的类信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        //获取当前类的资源(类的路径)
        Resource resource = metadataReader.getResource();
        String className = classMetadata.getClassName();
        System.out.println("===>" + className);
        return "com.lin.springL.dao.UserDao".equals(className);
    }
}

更改MainConfigure类为:

//配置类==配置文件
@Configuration
//开启自动包扫描,传入要扫描的包路径
@ComponentScan(basePackages = "com.lin.springL", excludeFilters = {
        @ComponentScan.Filter(type = FilterType.CUSTOM, value = {MyTypeFilter.class})})
public class MainConfigure {

}

执行MainTest得到结果如下:

===>com.lin.springL.MainTest
===>com.lin.springL.config.MyTypeFilter
===>com.lin.springL.controller.UserController
===>com.lin.springL.dao.UserDao
===>com.lin.springL.pojo.User
===>com.lin.springL.service.UserService
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfigure
userController
userService

说明自定义的Filter起作用,并且将匹配到的Bean过滤掉。

@Scope-设置组件作用域

在上面我们使用的注解默认是单例模式,也就是多次获取同一个名称的Bean,得到的都是同一个Bean,更改MainTest代码如下:

public class MainTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new
                AnnotationConfigApplicationContext(MainConfigure.class);
        UserDao userDao = (UserDao) applicationContext.getBean("userDao");
        UserDao userDao1 = (UserDao) applicationContext.getBean("userDao");
        System.out.println(userDao == userDao1);
    }
}
// 输出结果:true

如果需要多实例,可以使用@Scope来设置,修改UserDao类为如下代码:

@Component
//设置作用域
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class UserDao {

}

再次运行MainTest,将会得到结果:false。

scopeName属性参数

总共有四种作用域:

  • ConfigurableBeanFactory.SCOPE_PROTOTYPE:多实例模式
  • ConfigurableBeanFactory.SCOPE_SINGLETON:单实例模式(默认模式)
  • org.springframework.web.context.WebApplicationContext.SCOPE_REQUEST:基于Web,一次请求新建一个Bean
  • org.springframework.web.context.WebApplicationContext.SCOPE_SESSION:基于Web,一次Session中新建一个Bean

@Lazy-bean懒加载

对于上面提到的Bean,都是在容器启动的时候就会创建对象。懒加载的意思就是在容器启动的时候不创建对象,而是等到第一次使用(getBean)的时候创建对象,避免空间的浪费。

非懒加载例子

更改UserDao的代码如下:

@Component
public class UserDao {
    public UserDao() {
        System.out.println("UserDao init======");
    }
}

MainTest的代码如下:

public class MainTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new
                AnnotationConfigApplicationContext(MainConfigure.class);
    }
}

运行MainTest,得到结果如下:

UserDao init======

说明在容器启动时候,UserDao就已经被创建对象。

懒加载例子

在UserDao类上加入@Lazy注解:

@Component
@Lazy
public class UserDao {
    public UserDao() {
        System.out.println("UserDao init======");
    }
}

再次运行MainTest发现没有的输出,代表着时候UserDao并没有被创建对象。

更改MainTest代码如下:

public class MainTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new
                AnnotationConfigApplicationContext(MainConfigure.class);
        UserDao userDao = (UserDao) applicationContext.getBean("userDao");
    }
}

得到输入如下:

UserDao init======

说明在获取Bean的时候,UserDao才被创建对象。

@Conditional-按照条件注册

上面提到的@Bean注解是都会往容器中注册bean,但是@Conditional可以先判断一个条件,条件成立才进行注册。

例子

编写两个Condition:

public class LinCondition implements Condition {

    /**
     * @param context 判断条件能使用的上下文环境
     * @param metadata 当前类的注释信息
     * @return 是否匹配上
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //在这里返回false,让其不通过
        return false;
    }
}
public class KeCondition implements Condition {
    /**
     * @param context  判断条件能使用的上下文环境
     * @param metadata 当前类的注释信息
     * @return 是否匹配上
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //在这里返回true,让其通过
        return true;
    }
}

更改MainConfigure代码如下:

//配置类==配置文件
@Configuration
public class MainConfigure {

    @Conditional(value = {LinCondition.class})
    @Bean
    public User user1() {
        return new User("lin", 33);
    }

    @Conditional(value = {KeCondition.class})
    @Bean
    public User user2() {
        return new User("ke", 23);
    }
}

更改MainTest代码如下:

public class MainTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new
                AnnotationConfigApplicationContext(MainConfigure.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
    }
}

运行MainTest得到结果:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfigure
user2

可以看到Condition返回true的Bean会被加载。

@Import-给容器中快速导入一个组件

通常如果是我们自己编写的类,可以使用上面提到的@Controller等注解来注册到容器中,但是如果需要导入第三方jar包里面的类的时候,这种方法就不适用了,对于这种情况,Spring提供了两种方式,一种是在配置文件中new出对应的类并使用@Bean注释方法,还有一种是使用@Import注解来帮助导入。

例子

在配置文件中使用Import,更改MainConfigure代码如下:

//配置类==配置文件
@Configuration
@Import(value = {User.class})
public class MainConfigure {
}

运行MainTest得到如下结果:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfigure
com.lin.springL.pojo.User

包含了User类,id默认为全类名。

FactoryBean创建Bean

可以用FactoryBean来包装一个对象给容器

例子

编写UserFactoryBean代码如下:

public class UserFactoryBean implements FactoryBean<User> {

    @Override
    public User getObject() throws Exception {
        return new User("factorybean", 33);
    }

    @Override
    public Class<?> getObjectType() {
        return User.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

更改MainConfigure文件代码如下:

//配置类==配置文件
@Configuration
public class MainConfigure {

    @Bean
    public UserFactoryBean userFactoryBean() {
        return new UserFactoryBean();
    }

}

更改MainTest代码如下:

public class MainTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new
                AnnotationConfigApplicationContext(MainConfigure.class);
        Object userFactoryBean = applicationContext.getBean("userFactoryBean");
        System.out.println(userFactoryBean.getClass());
    }
}

运行MainTest得到结果如下:

class com.lin.springL.pojo.User

虽然我们注册的是一个FactoryBean,但是Spring对通过内部的getObject方法获得真实的Bean。

posted @ 2020-05-18 10:21  林老头儿  阅读(260)  评论(0编辑  收藏  举报