Spring Framework 学习笔记2:注解开发

Spring Framework 学习笔记2:注解开发

本文使用的示例项目是 spring-demo

1.@Component

@Component注解的用途与<bean/>标签的用途是相同的,都用于向 IoC 容器添加一个 Bean 定义。

比如:

@Component
public class UserDaoImpl implements UserDao {
    @Override
    public void save(){
        System.out.println("UserDaoImpl.save() is called.");
    }
}

只使用@Component注解是不够的,我们还需要告诉 IoC 容器在哪个包路径下检索 Bean 定义:

<context:component-scan base-package="cn.icexmoon.springdemo.dao"/>

需要引入命名空间 context,方式在上一篇文章中有介绍。

现在 Spring 会自动扫描cn.icexmoon.springdemo.dao包下的 Java 类,如果有使用@Component注解,就会将其添加到 Bean 定义。

实际上通常会使用项目的顶级包名,这样就可以扫描项目下的所有类:

<context:component-scan base-package="cn.icexmoon.springdemo"/>

现在通过 IoC 容器就可以获取 Bean 实例:

ApplicationContext ctx = new ClassPathXmlApplicationContext("application.xml");
UserDao userDao = ctx.getBean(UserDao.class);
userDao.save();

默认情况下@Component注解创建的 Bean 的名称使用的是类名(首字母小写):

UserDao userDao = ctx.getBean("userDaoImpl", UserDao.class);

可以通过value属性指定 Bean 名称:

@Component("userDao")
public class UserDaoImpl implements UserDao {
    // ...
}

public class Application {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("application.xml");
        UserDao userDao = ctx.getBean("userDao", UserDao.class);
        userDao.save();
    }
}

@Component有一些衍生注解,比如:

  • @Service,表示 Service 层的 Bean。
  • @Controller,表示 Controller 层的 Bean。
  • @Repository,表示持久层的 Bean。

用途是相同的,都是用于定义 Bean,只不过对 Bean 的用途进行了进一步的区分。

所以示例可以改写为:

@Repository("userDao")
public class UserDaoImpl implements UserDao {
	// ...
}

@Service
public class UserServiceImpl implements UserService {
    // ...
}

public class Application {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("application.xml");
        UserDao userDao = ctx.getBean("userDao", UserDao.class);
        userDao.save();
        UserService userService = ctx.getBean(UserService.class);
        userService.save();
    }
}

2.纯注解开发

虽然现在 Bean 定义已经通过@Component注解实现了,但示例项目依然要加载 XML 配置文件。可以进一步改为不使用配置文件的“纯注解开发”。

需要先准备一个配置类:

@Configuration
@ComponentScan(basePackages = "cn.icexmoon.springdemo")
public class WebConfig {
}

@Configuration表示这个类是一个配置类,它充当了 XML 配置文件的功能,可以用于定义 Bean。

在配置类上添加一个@ComponentScan注解可以用于指定扫描 Bean 定义的包范围,相当于之前使用的<context:component-scan/>标签。

IoC 容器的创建也需要修改,实现类使用AnnotationConfigApplicationContext,将不再加载 XML 配置文件,而是加载配置类作为 Bean 定义:

public class Application {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(WebConfig.class);
        // ...
    }
}

现在已经可以删除 XML 配置文件了。

3.@Scope

注解开发模式下,Bean 默认的作用域同样为单例,可以使用@Scope注解修改作用域:

@Repository("userDao")
@Scope("prototype")
public class UserDaoImpl implements UserDao {
    @Override
    public void save(){
        System.out.println("UserDaoImpl.save() is called.");
    }
}

public class Application {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(WebConfig.class);
        UserDao userDao = ctx.getBean("userDao", UserDao.class);
        UserDao userDao2 = ctx.getBean("userDao", UserDao.class);
        System.out.println(userDao2 == userDao ? "is same object" : "is not same object");
    }
}

虽然可以使用字符串指定作用域,但更好的方式是是用预定义常量:

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

除此之外还有其他常量可选,具体可以查看@Scope的源码注释。

4.生命周期

注解模式下同样可以使用上篇文章提到的接口定义 Spring Bean 的生命周期回调:

@Repository("userDao")
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class UserDaoImpl implements UserDao, InitializingBean, DisposableBean {
	// ...
    @Override
    public void destroy() throws Exception {
        System.out.println("before UserDaoImpl instance destroy.");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("after UserDaoImpl instance init.");
    }
}

但更推荐的方式是使用注解:

@Repository("userDao")
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class UserDaoImpl implements UserDao {
	// ...
    @PreDestroy
    public void destroy() {
        System.out.println("before UserDaoImpl instance destroy.");
    }

    @PostConstruct
    public void afterPropertiesSet() {
        System.out.println("after UserDaoImpl instance init.");
    }
}

需要注意的是,@PreDestroy@PostConstruct注解并不属于 spring-context 依赖,其属于 javax.annotation-api 依赖。所以需要添加:

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.2</version>
</dependency>

5.自动装配

5.1.属性

可以使用@Autowired注解实现对属性依赖的自动装配:

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
	// ...
}

Spring 会寻找类型匹配的 Bean 并使用反射的方式完成依赖注入,所以这里并不需要添加 Set 方法。

5.2.构造器

@Autowired标记构造器同样可以实现自动装配:

@Service
public class UserServiceImpl implements UserService {
    private UserDao userDao;

    @Autowired
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }
	// ...
}

5.3.Setter

也可以用@Autowired标记 Setter 实现自动装配:

@Service
public class UserServiceImpl implements UserService {
    @Setter(onMethod = @__(@Autowired))
    private UserDao userDao;
	// ...
}

这里使用的是 Lombok 生成 Setter 的写法,具体可以参考这篇文章

5.4.@Qualifier

如果有多个类型匹配,我们需要用@Qualifier注解指定其中的一个 Bean 用于注入:

@Repository
public class UserDaoImpl implements UserDao {
	// ...
}

@Repository
public class UserDaoImpl2 implements UserDao {
	// ...
}

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    @Qualifier("userDaoImpl")
    private UserDao userDao;
    // ...
}

5.5.@Value

对于简单类型(基础类型和 String ),可以使用@Value实现自动装配:

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    @Qualifier("userDaoImpl")
    private UserDao userDao;
    @Value("icexmoon")
    private String name;

    @Override
    public void save() {
        System.out.println("UserServiceImpl.save() is called.");
        System.out.println("name:" + name);
        userDao.save();
    }
}

在这个示例中这样做多少显得有点多余,完全可以:

@Service
public class UserServiceImpl implements UserService {
    // ...
    private String name = "icexmoon";
}

但使用@Value的好处是可以从 properties 文件加载属性。

比如 Resource 目录下有一个 jdbc.properties 文件:

name=icexmoon

在配置类上使用@PropertySource注解加载这个 properties 文件:

@Configuration
@ComponentScan(basePackages = "cn.icexmoon.springdemo")
@PropertySource("classpath:jdbc.properties")
public class WebConfig {
}

使用@Value完成注入:

@Service
public class UserServiceImpl implements UserService {
	// ...
    @Value("${name}")
    private String name;
	// ...
}

6.管理第三方 Bean

添加第三方依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
</dependency>

在配置类中添加一个 Bean 方法:

@Configuration
@ComponentScan(basePackages = "cn.icexmoon.springdemo")
@PropertySource("classpath:jdbc.properties")
public class WebConfig {
    @Bean
    public DataSource dataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("mysql");
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        druidDataSource.setUrl("jdbc://localhost:3306/test");
        return druidDataSource;
    }
}

通过 IoC 容器获取 Bean:

public class Application {
    public static void main(String[] args) {
        // ...
        DataSource dataSource = ctx.getBean(DataSource.class);
        System.out.println(dataSource);
    }
}

可以按照 Bean 类型对配置类进行拆分,比如将数据库的相关类放在JDBCConfig中:

@Configuration
public class JDBCConfig {
    @Bean
    public DataSource dataSource() {
        // ...
    }
}

因为@Configuration同样是@Component的一个衍生注解,所以在我们已经制定扫描顶级包的情况下,这个配置类同样是有效的。

也可以不使用扫描,在作为入口的配置类上通过@Import注解导入其它配置类:

public class JDBCConfig {
    @Bean
    public DataSource dataSource() {
        // ...
    }
}

@Configuration
@ComponentScan(basePackages = "cn.icexmoon.springdemo")
@PropertySource("classpath:jdbc.properties")
@Import(JDBCConfig.class)
public class WebConfig {

}

public class Application {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(WebConfig.class);
        // ...
    }
}

同样的,数据库连接信息应当从 properties 文件中读取,比如:

jdbc.user=root
jdbc.password=mysql
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc://localhost:3306/test

利用@Value自动装配数据库连接信息到属性,并在 Bean 方法中使用:

public class JDBCConfig {
    @Value("${jdbc.user}")
    private String user;
    @Value("${jdbc.password}")
    private String password;
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;

    @Bean
    public DataSource dataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUsername(user);
        druidDataSource.setPassword(password);
        druidDataSource.setDriverClassName(driver);
        druidDataSource.setUrl(url);
        return druidDataSource;
    }
}

The End,谢谢阅读。

7.参考资料

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值