Spring 注解开发

目录


注解开发简介

注解开发的好处:使用注解的形式替代 xml 配置,将繁杂的 Spring 配置文件从工程中彻底消除掉,简化书写。

注解驱动的弊端:


常用注解

Spring 原始注解:主要是替代 <Bean> 的配置。

注解 说明 @Component 使用在类上用于实例化 Bean @Controller 使用在 web 层类上用于实例化 Bean @Service 使用在 service 层类上用于实例化 Bean @Repository 使用在 dao 层类上用于实例化 Bean @Autowired 使用在字段上用于根据类型依赖注入 @Qualifier 结合 @Autowired 一起使用,用于根据名称进行依赖注入引用类型 @Resource 相当于 @Autowired + @Qualifier,按照名称进行注入引用类型 @Value 注入普通类型的属性 @Scope 标注 Bean 的作用范围 @PostConstruct 使用在方法上标注该方法是 Bean 的初始化方法 @PreDestroy 使用在方法上标注该方法是 Bean 的销毁方法

Spring 新注解:

使用上面的注解还不能全部替代 xml 配置文件,还需要使用注解替代的配置如下:

注解 说明 @Configuration 用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解。用于指定 Spring 在初始化容器时要扫描的包。 @ComponentScan 作用和在 Spring 的 xml 配置文件中的 <context:component-scan base-package="com.itheima"/> 一样。 @Bean 使用在方法上,标注将该方法的返回值存储到 Spring 容器中。 @PropertySource 用于加载 .properties 文件中的配置。 @Import 用于导入其他配置类。

启用注解功能

<context:component-scan base-package="packageName"/>

bean 定义:@Component、@Controller、@Service、@Repository

@Component
public class ClassName{}

bean 的引用类型属性注入:@Autowired、@Qualifier


bean 的引用类型属性注入:@Inject、@Named、@Resource


bean 优先级注入:@Primary


bean 的非引用类型属性注入:@Value


bean 的作用域:@Scope


bean 的生命周期:@PostConstruct、@PreDestroy


加载第三方资源:@Bean

@Bean("dataSource")
public DruidDataSource createDataSource() {    return ……;    }

加载 properties 文件:@PropertySource

@PropertySource(value="classpath:jdbc.properties")
public class ClassName {
    @Value("${propertiesAttributeName}")
    private String attributeName;
}

纯注解开发:@Configuration、@ComponentScan

@Configuration
@ComponentScan("scanPackageName")
public class SpringConfigClassName{
}
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

导入第三方配置:@Import

@Configuration
@Import(OtherClassName.class)
public class ClassName {
}

综合案例

maven 依赖:

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>
    </dependencies>

spring 配置类

DataSourceConfig.java:

package com.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;

// 数据源配置类
// 相当于 <context:property-placeholder location="classpath:jdbc.properties"/>,且不能用通配符*
@PropertySource("classpath:jdbc.properties")
public class DataSourceConfig {

    @Value("${jdbc.driver}")
    private static String driver;
    @Value("${jdbc.url}")
    private static String url;
    @Value("${jdbc.username}")
    private static String username;
    @Value("${jdbc.password}")
    private static String password;

    @Bean("dataSource")  // 将方法的返回值放置Spring容器中
    public static DruidDataSource getDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}

SpringConfig.java:

package com.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

// Spring核心配置类
@Configuration
@ComponentScan("com")  // 相当于 <context:component-scan base-package="com"/>
@Import({DataSourceConfig.class})  // 相当于 <import resource=""/>
public class SpringConfig {

}

dao 层

UserDaoImpl.java:

package com.dao.impl;

import com.dao.UserDao;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

// 相当于 <bean id="UserDao" ref="com.dao.impl.UserDaoImpl"/>
// @Component("userDao")
@Repository("userDao")
public class UserDaoImpl implements UserDao {
    @Override
    public void save() {
        System.out.println("UserDao save...");
    }
}

service 层

UserServiceImpl.java:

package com.service.impl;

import com.dao.UserDao;
import com.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.sql.DataSource;

// 相当于 <bean id="UserService" ref="com.service.impl.UserServiceImpl"/>
// @Component("userService")
@Service("userService")
public class UserServiceImpl implements UserService {

    // <property name="userDao" ref="userDao"></property>
    // @Autowired  // 可单独使用,按照数据类型从spring容器中进行匹配的(有多个相同数据类型的bean时则会有匹配问题)
    // @Qualifier("userDao")  // 指定bean的id从spring容器中匹配,且要结合@Autowired一起用
    @Resource(name="userDao")  // 相当于 @Autowired+@Autowired
    UserDao userDao;

    @Resource(name="dataSource")
    DataSource dataSource;

    @Value("${jdbc.driver}")  // 读取配置文件中的值
    private String driver;

    /* 使用注解开发可以省略set方法,使用配置文件则不能省略
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    */

    @Override
    public void save() {
        System.out.println("driver: "+driver);
        System.out.println("dataSource: "+dataSource);
        userDao.save();
    }

    @PostConstruct
    public void init() {
        System.out.println("service对象的初始化方法");
    }

    @PreDestroy
    public void destroy() {
        System.out.println("service对象的销毁方法");
    }
}

controller 层

App.java:

package com.controller;

import com.config.SpringConfig;
import com.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {

    public static void main(String[] args) {
        // ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = (UserService)context.getBean("userService");
        userService.save();
        context.close();
    }
}

运行结果:

service对象的初始化方法
dataSource: {
    CreateTime:"2021-12-03 01:05:00",
    ActiveCount:0,
    PoolingCount:0,
    CreateCount:0,
    DestroyCount:0,
    CloseCount:0,
    ConnectCount:0,
    Connections:[
    ]
}
UserDao save...
service对象的销毁方法

整合第三方技术

注解整合 Mybatis

注解整合 MyBatis 的开发步骤:

  1. 修改 mybatis 外部配置文件格式为注解格式;
  2. 业务类使用 @Component 声明 bean,使用 @Autowired 注入对象;
  3. 建立配置文件 JDBCConfig 与 MyBatisConfig 类,并将其导入到核心配置类 SpringConfig;
  4. 开启注解扫描;
  5. 使用 AnnotationConfigApplicationContext 对象加载配置项。

项目工程地址

核心内容如下:

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.3</version>
        </dependency>
package com.config;

import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

public class MybatisConfig {

    /*
    <!-- spring整合mybatis后,创建连接用的对象 -->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="typeAliasesPackage" value="com.domain"/>
    </bean>

    <!-- 扫描mybatis映射配置,将其作为spring的bean进行管理 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.dao"/>
    </bean>
     */

    // 以下注解代替以上配置文件内容:返回值会作为Spring容器的bean
    @Bean
    public SqlSessionFactoryBean getSqlSessionFactoryBean(@Autowired DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setTypeAliasesPackage("com.domain");
        return sqlSessionFactoryBean;
    }

    @Bean
    public MapperScannerConfigurer getMapperScannerConfigurer() {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setBasePackage("com.dao");
        return mapperScannerConfigurer;
    }

}
package com.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;

    @Bean("dataSource")
    public DataSource getDataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }
}
package com.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@ComponentScan("com")  // 相当于 <context:component-scan base-package="com"/>
@Import({JdbcConfig.class, MybatisConfig.class})  // 相当于 <import resource=""/>
public class SpringConfig {

}

注解整合 Junit

注解整合 Junit 的开发步骤:

  1. Spring 接管 Junit 的运行权,使用 Spring 专用的 Junit 类加载器;

2.为 Junit 测试用例设定对应的 Spring 容器:

示例:整合 Junit5

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.8.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
package com.service;

import com.config.SpringConfig;
import com.domain.User;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.util.List;

// 设定spring专用的类加载器
@ExtendWith(SpringExtension.class)
// 设定加载的spring上下文对应的配置
@ContextConfiguration(classes=SpringConfig.class)
public class UserServiceTest {

    @Autowired
    UserService userService;

    @Test
    public void testFindById() {
        User user = userService.findById(1);
        // System.out.println(user);
        Assertions.assertEquals("Mick", user.getName());
    }

    @Test
    public void testFindAll() {
        List<User> users = userService.findAll();
        Assertions.assertEquals(3, users.size());
    }

}

IoC 底层核心原理

IoC 核心接口


组件扫描器:@ComponentScan

组件扫描器:开发过程中,需要根据需求加载必要的 bean 或排除指定 bean。

应用场景:

配置扫描器

@Configuration
@ComponentScan(
    value="com",  // 设置基础扫描路径
    excludeFilters =      // 设置过滤规则,当前为排除过滤
    @ComponentScan.Filter(            // 设置过滤器
        type= FilterType.ANNOTATION,  // 设置过滤方式为按照注解进行过滤
        classes=Repository.class)     // 设置具体的过滤项。如不加载所有@Repository修饰的bean
)
public class SpringConfig {

}

自定义扫描器

示例:

public class MyTypeFilter implements TypeFilter {
    public boolean match(MetadataReader mr, MetadataReaderFactory mrf) throws IOException {
        ClassMetadata cm = metadataReader.getClassMetadata();
        tring className = cm.getClassName();
        if(className.equals("com.itheima.dao.impl.BookDaoImpl")){
            return true;  // 进行过滤(拦截)
        }
        return false;  // 不过滤(放行)
    }
}
@Configuration
@ComponentScan(
        value = "com",
        excludeFilters = @ComponentScan.Filter(
                type= FilterType.CUSTOM,
                classes = MyTypeFilter.class
        )
)
public class SpringConfig {

}

自定义导入器:ImportSelector

bean 只有通过配置才可以进入 spring 容器,被 spring 加载并控制。配置 bean 的方式如下:

企业开发过程中,通常需要配置大量的 bean,因此需要一种快速高效配置大量 bean 的方式。

ImportSelector 注解:

示例:

public class MyImportSelector implements ImportSelector {
    public String[] selectImports(AnnotationMetadata icm) {
        // 返回需要导入的bean数组。该bean即使没加@Component注解也能被扫描识别
        return new String[]{"com.dao.impl.AccountDaoImpl"};
    }
}
@Configuration
@ComponentScan("com")
@Import(MyImportSelector.class)  // 导入自定义导入器
public class SpringConfig {
}

自定义导入器的封装工具类:

import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AspectJTypeFilter;
import org.springframework.core.type.filter.TypeFilter;

import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class CustomerImportSelector implements ImportSelector {

    private String expression;

    public CustomerImportSelector(){
        try {
            //初始化时指定加载的properties文件名
            Properties loadAllProperties = PropertiesLoaderUtils.loadAllProperties("import.properties");
            //设定加载的属性名
            expression = loadAllProperties.getProperty("path");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //1.定义扫描包的名称
        String[] basePackages = null;
        //2.判断有@Import注解的类上是否有@ComponentScan注解
        if (importingClassMetadata.hasAnnotation(ComponentScan.class.getName())) {
            //3.取出@ComponentScan注解的属性
            Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());
            //4.取出属性名称为basePackages属性的值
            basePackages = (String[]) annotationAttributes.get("basePackages");
        }
        //5.判断是否有此属性(如果没有ComponentScan注解则属性值为null,如果有ComponentScan注解,则basePackages默认为空数组)
        if (basePackages == null || basePackages.length == 0) {
            String basePackage = null;
            try {
                //6.取出包含@Import注解类的包名
                basePackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            //7.存入数组中
            basePackages = new String[] {basePackage};
        }
        //8.创建类路径扫描器
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        //9.创建类型过滤器(此处使用切入点表达式类型过滤器)
        TypeFilter typeFilter = new AspectJTypeFilter(expression,this.getClass().getClassLoader());
        //10.给扫描器加入类型过滤器
        scanner.addIncludeFilter(typeFilter);
        //11.创建存放全限定类名的集合
        Set<String> classes = new HashSet<>();
        //12.填充集合数据
        for (String basePackage : basePackages) {
            scanner.findCandidateComponents(basePackage).forEach(beanDefinition -> classes.add(beanDefinition.getBeanClassName()));
        }
        //13.按照规则返回
        return classes.toArray(new String[classes.size()]);
    }
}

自定义注册器:ImportBeanDefinitionRegistrar

示例:

// 表示com目录下的bean全部注册
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    public void registerBeanDefinitions(AnnotationMetadata icm, BeanDefinitionRegistry r) {
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(r, false);
        TypeFilter tf = new TypeFilter() {
            public boolean match(MetadataReader mr, MetadataReaderFactory mrf) throws IOException {
                return true;
            }
        };
        scanner.addIncludeFilter(tf);  // 包含
        // scanner.addExcludeFilter(tf);  // 排除
        scanner.scan("com");
    }
}
@Configuration
@Import(MyImportBeanDefinitionRegistrar.class)  // 作用等同于 @ComponentScan("com")
public class SpringConfig {
}

封装工具类:

import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AspectJTypeFilter;
import org.springframework.core.type.filter.TypeFilter;

import java.io.IOException;
import java.util.Map;
import java.util.Properties;

public class CustomeImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    private String expression;

    public CustomeImportBeanDefinitionRegistrar(){
        try {
            //初始化时指定加载的properties文件名
            Properties loadAllProperties = PropertiesLoaderUtils.loadAllProperties("import.properties");
            //设定加载的属性名
            expression = loadAllProperties.getProperty("path");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //1.定义扫描包的名称
        String[] basePackages = null;
        //2.判断有@Import注解的类上是否有@ComponentScan注解
        if (importingClassMetadata.hasAnnotation(ComponentScan.class.getName())) {
            //3.取出@ComponentScan注解的属性
            Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());
            //4.取出属性名称为basePackages属性的值
            basePackages = (String[]) annotationAttributes.get("basePackages");
        }
        //5.判断是否有此属性(如果没有ComponentScan注解则属性值为null,如果有ComponentScan注解,则basePackages默认为空数组)
        if (basePackages == null || basePackages.length == 0) {
            String basePackage = null;
            try {
                //6.取出包含@Import注解类的包名
                basePackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            //7.存入数组中
            basePackages = new String[] {basePackage};
        }
        //8.创建类路径扫描器
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);
        //9.创建类型过滤器(此处使用切入点表达式类型过滤器)
        TypeFilter typeFilter = new AspectJTypeFilter(expression,this.getClass().getClassLoader());
        //10.给扫描器加入类型过滤器
        scanner.addIncludeFilter(typeFilter);
        //11.扫描指定包
        scanner.scan(basePackages);
    }
}

bean 初始化过程解析

bean 统一初始化

示例:

package com.post;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

public class MyBeanFactory implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        System.out.println("Bean工厂制作好了");
    }
}
package com.post;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBean implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("bean之前巴拉巴拉");
        System.out.println(beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("bean之后巴拉巴拉");
        return bean;
    }
}
package com.service.impl;

import com.dao.UserDao;
import com.domain.User;
import com.service.UserService;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

@Service("userService")
public class UserServiceImpl implements InitializingBean {

    // 定义当前bean初始化操作,功效等同于init-method属性配置
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("UserServiceImpl......bean ...init......");
    }
}
Bean工厂制作好了
bean之前巴拉巴拉
springConfig
bean之后巴拉巴拉
bean之前巴拉巴拉
com.config.DataSourceConfig
bean之后巴拉巴拉
bean之前巴拉巴拉
dataSource
bean之后巴拉巴拉
bean之前巴拉巴拉
getSqlSessionFactoryBean
bean之后巴拉巴拉
bean之后巴拉巴拉
bean之前巴拉巴拉
userDao
bean之后巴拉巴拉
bean之后巴拉巴拉
bean之前巴拉巴拉
userService
UserServiceImpl......bean ...init......
bean之后巴拉巴拉
bean之前巴拉巴拉
com.service.UserServiceTest.ORIGINAL
bean之后巴拉巴拉

单个 bean 初始化

FactoryBean 与 BeanFactory 区别:

示例:

import org.springframework.beans.factory.FactoryBean;

public class UserServiceImplFactoryBean implements FactoryBean {

    // 重点:返回数据
    @Override
    public Object getObject() throws Exception {
        return new UserServiceImpl();
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }

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