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


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

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

    1.@Component

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

    比如:

    @Component
    public class UserDaoImpl implements UserDao {
        @Override
        public void save(){
            System.out.println("UserDaoImpl.save() is called.");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

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

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

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

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

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

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

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

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

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

    可以通过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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    @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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2.纯注解开发

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

    需要先准备一个配置类:

    @Configuration
    @ComponentScan(basePackages = "cn.icexmoon.springdemo")
    public class WebConfig {
    }
    
    • 1
    • 2
    • 3
    • 4

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

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

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

    public class Application {
        public static void main(String[] args) {
            ApplicationContext ctx = new AnnotationConfigApplicationContext(WebConfig.class);
            // ...
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    现在已经可以删除 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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

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

    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    
    • 1

    除此之外还有其他常量可选,具体可以查看@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.");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

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

    @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.");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

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

    <dependency>
        <groupId>javax.annotationgroupId>
        <artifactId>javax.annotation-apiartifactId>
        <version>1.2version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5.自动装配

    5.1.属性

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

    @Service
    public class UserServiceImpl implements UserService {
        @Autowired
        private UserDao userDao;
    	// ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    5.2.构造器

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

    @Service
    public class UserServiceImpl implements UserService {
        private UserDao userDao;
    
        @Autowired
        public UserServiceImpl(UserDao userDao) {
            this.userDao = userDao;
        }
    	// ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    5.3.Setter

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

    @Service
    public class UserServiceImpl implements UserService {
        @Setter(onMethod = @__(@Autowired))
        private UserDao userDao;
    	// ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这里使用的是 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;
        // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

    @Service
    public class UserServiceImpl implements UserService {
        // ...
        private String name = "icexmoon";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

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

    name=icexmoon
    
    • 1

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

    @Configuration
    @ComponentScan(basePackages = "cn.icexmoon.springdemo")
    @PropertySource("classpath:jdbc.properties")
    public class WebConfig {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    使用@Value完成注入:

    @Service
    public class UserServiceImpl implements UserService {
    	// ...
        @Value("${name}")
        private String name;
    	// ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    6.管理第三方 Bean

    添加第三方依赖:

    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>druidartifactId>
        <version>1.1.16version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在配置类中添加一个 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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    通过 IoC 容器获取 Bean:

    public class Application {
        public static void main(String[] args) {
            // ...
            DataSource dataSource = ctx.getBean(DataSource.class);
            System.out.println(dataSource);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

    @Configuration
    public class JDBCConfig {
        @Bean
        public DataSource dataSource() {
            // ...
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    因为@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);
            // ...
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

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

    jdbc.user=root
    jdbc.password=mysql
    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc://localhost:3306/test
    
    • 1
    • 2
    • 3
    • 4

    利用@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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    The End,谢谢阅读。

    7.参考资料

  • 相关阅读:
    隐式微分方程求解Matlab
    【javaEE】网络原理(数据链路层+小结)
    c语言基础:L1-053 电子汪
    图论学习总结
    day3 力扣
    C语言的查找
    压测的问题排查
    Java认识异常(超级详细)
    ChatGPT AIGC 办公自动化拆分Excel工作表
    如何在3dMax中使用Python返回场景内所有对象的列表?
  • 原文地址:https://blog.csdn.net/hy6533/article/details/133361892