Spring使用注解配置依赖注入

 2022-09-05
原文地址:https://cloud.tencent.com/developer/article/1703105

大部分情况下,使用Spring配置依赖注入时,都是使用注解来进行配置,因为注解比xml要方便和简单。不过类似于数据源对象这种配置信息容易变更的对象除外,这种对象使用xml文件来进行配置会更适合,方便于在外部进行修改,而不需要打开代码来进行修改。

接下来简单介绍一下注解的配置方式,首先要让Spring支持注解,编辑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:p="http://www.springframework.org/schema/p"
           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:annotation-config/>
        <!-- 指定哪些包下的类受可以让Spring通过注解来管理 -->
        <context:component-scan base-package="org.zero01"/>
    
    </beans>

通过注解配置来让Spring帮我们创建对象,Student类代码如下:

    package org.zero01;
    
    import org.springframework.stereotype.Component;
    
    // 加上这个注解表示该类受到Spring的管理,注解的值为该类的id,该注解的作用相当于xml中的bean标签
    @Component("stu")
    public class Student {
            ...

测试代码:

    package org.zero01;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class Test {
    
        public static void main(String[] args) {
    
            ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            // 同样的通过配置的id获得实例对象
            Student stu1 = (Student) app.getBean("stu");
            Student stu2 = (Student) app.getBean("stu");
    
            // 默认都是单例对象
            if (stu1 == stu2) {
                System.out.println("单例对象");
            }else{
                System.out.println("非单例对象");
            }
        }
    }

运行结果:

    单例对象

使用注解时可以不配置id值,直接写上 @Component 也行:

    package org.zero01;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class Student {
            ...

然后通过该类的class来获取实例对象:

    Student stu1 = app.getBean(Student.class);

但是这种方式的灵活性没有使用id值的方式好,因为字符串是可以通过变量改变的,而这种使用class的方式相当于是写死在代码上了。

如果不希望从容器里取出来的不是单例对象的话,可以使用 @Scope 注解来配置指定使用原型模式,需要配置属性的值可以使用 @Value 注解进行配置,例如:

    package org.zero01;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    import org.springframework.context.annotation.Scope;
    
    @Component("stu")
    @Scope(value = "prototype") // 取值与xml中的scope属性是一样的
    public class Student {
    
        @Value("小明")
        private String name;
        @Value("15")
        private int age;
        @Value("南京")
        private String address;
        ...

测试代码:

    package org.zero01;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class Test {
    
        public static void main(String[] args) {
    
            ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            Student stu1 = app.getBean(Student.class);
            Student stu2 = app.getBean(Student.class);
    
            if (stu1 == stu2) {
                System.out.println("单例对象");
            }else{
                System.out.println("非单例对象");
            }
    
            System.out.println(stu1.getName());
            System.out.println(stu1.getAge());
            System.out.println(stu1.getAddress());
        }
    }

运行结果:

    非单例对象
    小明
    15
    南京

注:我们可以将 @Value 注解写在属性的setter方法上,和写在属性上的作用是一样的。

如果需要注入自建类型,有两个注解可以做到,分别是 @Resource 和 @Autowired,但是要想通过这两个注解来配置依赖注入,被注入的对象需要写上 @Component 注解:

    package org.zero01;
    
    import org.springframework.stereotype.Component;
    
    @Component("phone")
    public class Phone {
    }
    
    package org.zero01;
    
    import org.springframework.stereotype.Component;
    
    @Component("dog")
    public class Dog {
    }

然后才可以使用 @Resource 和 @Autowired 注解配置依赖注入,Student类代码:

    package org.zero01;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    import org.springframework.context.annotation.Scope;
    
    import javax.annotation.Resource;
    
    @Component("stu")
    @Scope(value = "prototype")
    public class Student {
    
        @Value("小明")
        private String name;
        @Value("15")
        private int age;
        @Value("南京")
        private String address;
        @Resource
        private Dog dog;
        @Autowired
        private Phone phone;
        ...

测试代码:

    package org.zero01;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class Test {
    
        public static void main(String[] args) {
    
            ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            Student stu1 = app.getBean(Student.class);
    
            System.out.println(stu1.getName());
            System.out.println(stu1.getAge());
            System.out.println(stu1.getAddress());
            System.out.println(stu1.getDog());
            System.out.println(stu1.getPhone());
        }
    }

运行结果:

    小明
    15
    南京
    org.zero01.Dog@47db50c5
    org.zero01.Phone@5c072e3f

@Autowired 和 @Resource的区别简述:

  • 用途:做bean的注入时使用

  • 历史:@Autowired 属于Spring的注解,@Resource 不属于Spring的注解,是JDK1.6支持的注解

  • 共同点:装配bean. 写在字段上,或写在setter方法

  • 不同点:

    • @Autowired 默认按类型装配,依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,例如:@Autowired(required=false),也可以使用名称装配,配合 @Qualifier 注解。
    • @Resource 是JDK1.6支持的注解,默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名,按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
  • 便利程度:两者的便利程度都差不多,都可以实现自动装配

  • 耦合问题:可能会有人说使用Java自带的 @Resource 可以降低与Spring的耦合,但实际上注解处理器我们使用的是Spring提供的,是一样的,无所谓解耦不解耦的说法


使用以上介绍到的注解做一个简单的增删查改小例题:

pom.xml文件配置如下:

        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>4.3.14.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.39</version>
            </dependency>
            <dependency>
                <groupId>com.mchange</groupId>
                <artifactId>c3p0</artifactId>
                <version>0.9.5.2</version>
            </dependency>
            <dependency>
                <groupId>org.json</groupId>
                <artifactId>json</artifactId>
                <version>20160810</version>
            </dependency>
        </dependencies>

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:p="http://www.springframework.org/schema/p"
           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:annotation-config/>
        <!-- 指定哪些包下的类受可以让Spring通过注解来管理 -->
        <context:component-scan base-package="org.zero01"/>
    
        <!-- 配置数据源对象 -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
              p:driverClass="com.mysql.jdbc.Driver"
              p:jdbcUrl="jdbc:mysql:///school"
              p:user="root"
              p:password="your_password"
              p:maxPoolSize="10"
              p:minPoolSize="1"
              p:loginTimeout="2000"
        />
    
    </beans>

首先是接口代码:

    package org.zero01.dao;
    
    import org.zero01.pojo.Student;
    
    import java.util.List;
    
    public interface DAO {
    
        public int insert(Student student) throws Exception;
        public int delete(int sid) throws Exception;
        public List<Student> selectAll() throws Exception;
        public int update(Student student) throws Exception;
    }
    
    package org.zero01.service;
    
    import org.zero01.pojo.Student;
    
    import java.util.List;
    
    public interface Service {
    
        public int enterSchool(Student student);
        public int dropOut(int sid);
        public List<Student> getStudents();
        public int updateData(Student student);
    
    }
    
    package org.zero01.view;
    
    import org.zero01.pojo.Student;
    
    import java.util.List;
    
    public interface View {
    
        public int enterSchool(Student student);
        public int dropOut(int sid);
        public List<Student> getStudents();
        public int updateData(Student student);
    }

然后是具体的实现类代码,StudentDAO类:

    package org.zero01.dao;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.zero01.pojo.Student;
    
    import javax.sql.DataSource;
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.List;
    
    @Component("stuDAO")
    public class StudentDAO implements DAO {
    
        @Autowired
        private DataSource dataSource;
    
        public int insert(Student student) throws Exception {
    
            Connection connection = null;
    
            try {
    
                connection = dataSource.getConnection();
                String sql = "INSERT INTO student(sname,age,sex,address) VALUES (?,?,?,?)";
                PreparedStatement preparedStatement = connection.prepareStatement(sql);
                preparedStatement.setString(1, student.getSname());
                preparedStatement.setInt(2, student.getAge());
                preparedStatement.setString(3, student.getSex());
                preparedStatement.setString(4, student.getAddress());
    
                int row = preparedStatement.executeUpdate();
    
                if (row > 0) {
                    return row;
                }
    
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                connection.close();
            }
    
            return 0;
        }
    
        public int delete(int sid) throws Exception {
    
            Connection connection = null;
    
            try {
    
                connection = dataSource.getConnection();
                String sql = "DELETE FROM student WHERE sid=?";
                PreparedStatement preparedStatement = connection.prepareStatement(sql);
                preparedStatement.setInt(1, sid);
    
                int row = preparedStatement.executeUpdate();
    
                if (row > 0) {
                    return row;
                }
    
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                connection.close();
            }
    
            return 0;
        }
    
        public List<Student> selectAll() throws Exception {
            Connection connection = null;
    
            try {
    
                connection = dataSource.getConnection();
                String sql = "select * from student";
                PreparedStatement preparedStatement = connection.prepareStatement(sql);
    
                ResultSet resultSet = preparedStatement.executeQuery();
    
                List<Student> students = new ArrayList<Student>();
                while (resultSet.next()) {
                    Student student = new Student();
                    student.setSid(resultSet.getInt("sid"));
                    student.setSname(resultSet.getString("sname"));
                    student.setSex(resultSet.getString("sex"));
                    student.setAddress(resultSet.getString("address"));
                    students.add(student);
                }
                return students;
    
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                connection.close();
            }
    
            return null;
        }
    
        public int update(Student student) throws Exception {
            Connection connection = null;
    
            try {
    
                connection = dataSource.getConnection();
                String sql = "UPDATE student SET sname=?,age=?,sex=?,address=? WHERE sid=?";
                PreparedStatement preparedStatement = connection.prepareStatement(sql);
                preparedStatement.setString(1, student.getSname());
                preparedStatement.setInt(2, student.getAge());
                preparedStatement.setString(3, student.getSex());
                preparedStatement.setString(4, student.getAddress());
                preparedStatement.setInt(5, student.getSid());
    
                int row = preparedStatement.executeUpdate();
    
                if (row > 0) {
                    return row;
                }
    
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                connection.close();
            }
    
            return 0;
        }
    }

SchoolService代码:

    package org.zero01.service;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.zero01.dao.DAO;
    import org.zero01.pojo.Student;
    
    import java.util.List;
    
    @Component("schoolService")
    public class SchoolService implements Service {
    
        @Autowired
        private DAO dao;
    
        public int enterSchool(Student student) {
            try {
                return dao.insert(student);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return 0;
        }
    
        public int dropOut(int sid) {
            try {
                return dao.delete(sid);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return 0;
        }
    
        public List<Student> getStudents() {
            try {
                return dao.selectAll();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        public int updateData(Student student) {
            try {
                return dao.update(student);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return 0;
        }
    }

SchoolAction代码:

    package org.zero01.view;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.zero01.pojo.Student;
    import org.zero01.service.Service;
    
    import java.util.List;
    
    @Component("stuAction")
    public class SchoolAction implements View {
    
        @Autowired
        private Service schoolService;
    
        public int enterSchool(Student student) {
            return schoolService.enterSchool(student);
        }
    
        public int dropOut(int sid) {
            return schoolService.dropOut(sid);
        }
    
        public List<Student> getStudents() {
            return schoolService.getStudents();
        }
    
        public int updateData(Student student) {
            return schoolService.updateData(student);
        }
    }

从以上的代码可以看到,我们没有在哪一个类里写了关于任何实例化对象的代码,而是把实例化这项工作交给Spring容器去帮我们完成,这样每个类都不需要去管理、维护自己的依赖对象,只需要完成自己业务代码即可,这样弱化了类与类之间的依赖,让代码的复杂度降低,每个类都只需要维护自己的业务代码即可,这是Spring的IOC模块给我们带来的好处。而且每个类都依赖的是接口,而不是具体的实现类,符合依赖倒转原则,不会导致代码紧耦合,当具体的实现类被替换时,不会影响到其他类。

测试代码:

    package org.zero01;
    
    import org.json.JSONObject;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.zero01.view.View;
    import org.zero01.pojo.Student;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class Test {
    
        public static void main(String[] args) {
    
            ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            View view = (View) app.getBean("stuAction");
            Student student = (Student) app.getBean("student");
    
            System.out.println("enterSchool() 影响行数:" + view.enterSchool(student));
            System.out.println("dropOut() 影响行数:" + view.dropOut(25));
    
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("studentList", view.getStudents());
            map.put("length", view.getStudents().size());
            System.out.println(new JSONObject(map));
    
            student.setSname("小刚");
            student.setAddress("长沙");
            student.setAge(18);
            student.setSid(29);
            System.out.println("updateData() 影响行数:" + view.updateData(student));
        }
    }

运行结果:

    enterSchool() 影响行数:1
    dropOut() 影响行数:1
    {
      "studentList": [
        {
          "address": "南京",
          "sname": "小明",
          "sex": "男",
          "age": 0,
          "sid": 26
        },
        {
          "address": "南京",
          "sname": "小明",
          "sex": "男",
          "age": 0,
          "sid": 27
        },
        {
          "address": "南京",
          "sname": "小明",
          "sex": "男",
          "age": 0,
          "sid": 28
        },
        {
          "address": "南京",
          "sname": "小明",
          "sex": "男",
          "age": 0,
          "sid": 29
        },
        {
          "address": "南京",
          "sname": "小明",
          "sex": "男",
          "age": 0,
          "sid": 30
        },
        {
          "address": "南京",
          "sname": "小明",
          "sex": "男",
          "age": 0,
          "sid": 31
        }
      ],
      "length": 6
    }
    updateData() 影响行数:1