Spring一些注解配置和类的解读

1,746 阅读7分钟

利用Maven导入坐标

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context</artifactId>
	<version>5.1.9.RELEASE</version>
</dependency>

因为spring-context依赖于Spring其他几个核心模块的Jar包,所以使用Spring,导入此坐标即可

实例化容器的方法

  • 通过ClassPathXmlApplicationContext获取容器,这个方法需要配置applicationConfig.xml来配合初始化容器中的Bean
    • ApplicationContext context = newClassPathXmlApplicationContext("applicationContext.xml");
      context.refresh();
    • <!-- applicationConfig.xml -->
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
      
          <!-- 创建项目根目录下的src/main/resources目录,这是maven项目约定的资源文件目录。 -->
          <!-- 创建resources目录后,记得reimport Maven Project,否则idea识别不了。 -->
          <!-- 在resources目录下,使用idea创建spring context的xml配置文件。 -->
          <bean id="customerBean" class="cn.edu.dgut.sai.Customer">
              <property name="name" value="hello"/>
          </bean>
      
      </beans>

  • 通过AnnotationConfigApplicationContext获取容器,这个方法需要配置一个 配置类 来配合初始化容器中的Bean
    • AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
      context.refresh();
    • /**
      * Configuration:此类为配置类
      * ComponentScan:告知容器扫描此类所在包所有的类以及子包下所有的类的注解
      */
      @Configuration
      @ComponentScan
      public class Config {
          
      }

注册Bean

使用Component在类上面注解,容器进行扫描时,容器自动将此类注册为Bean

@Component
public class Player {
    private String name;
    private String age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }


    public void setAge(String age) {
        this.age = age;
    }
}

此外,还可以用@Service@Controller等注解配置Bean,区别在于Component是通用注解,而Service则是适用与Service业务层,Controller适用于Web视图层

动态注册Bean

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
context.registerBean("UserBean",User.class);
context.refresh();

使用Junit进行测试

使用Junit进行测试,不再需要机械重复地创建一个类,然后加入main方法,然后在main方法获取容器实例ApplicationContext

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = Config.class)
public class SpringTest {
    @Test
    public void testUser() {
        
    }
}

classes需要填写配置类的字节码文件

Bean的相关生命周期回调接口:InitializingBean、DisposableBean

当一个类成为Bean,继承InitializingBean接口并且实现接口的afterPropertiesSet方法的时候,当该Bean被容器注册并且实例化后的时候,afterPropertiesSet方法就会被调用(回调)。

同样的,当一个类成为Bean,继承了DisposableBean接口并且实现接口的destroy方法的时候,当该Bean被销毁之前,destroy方法就会自动被调用(回调)

public class User implements InitializingBean, DisposableBean {
    private String name;
    private String sex;

    public User() {
    }
	
	@Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet回调");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("User: destroy");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
    
}

ApplicationContextAware

这个接口的作用是令Bean能够获取容器的实例,进而通过编程的方法进行操作,例如下面,User Bean希望获取其他Customer Bean,就创建一个成员变量,通过这个接口获取容器实例,用成员变量保存容器实例,进而获取Customer Bean。

public class User implements InitializingBean, DisposableBean, ApplicationContextAware {
    private String name;
    private String sex;

    private ApplicationContext context;

    public User() {
        System.out.println("初始化");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("User: destroy");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet回调");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("applicationContext注入");
        this.context = applicationContext;
        Customer customerBean = (Customer)this.context.getBean("customerBean");
        System.out.println(customerBean);
    }
}

@Lazy

此注解主要作用是懒加载,一个Bean被ApplicationContext注册的时候,不会立即实例化,需要等到调用的时候,才会被实例化

@Scope

定义Bean的范围,一般是选值为singletonprototypesingleton表示为单例,prototype表示为多例

BeanPostProcessor

回调函数,当一个类注册为Bean,并且继承BeanPostProcessor接口,实现其接口的postProcessBeforeInitializationpostProcessAfterInitialization方法的时候,在这个容器中,只要有Bean注册实例化,就会自动调用此接口的两个方法。有多少个Bean注册实例化,就调用多少次postProcessBeforeInitialization会在Bean注册实例化之前自动被调用,postProcessAfterInitialization会在Bean注册实例化被自动调用。可以从这两个方法之中的参数,获取Bean的实例和注册为Bean的命名

注意:当使用context.register方法动态注册Bean的时候,Bean并不会实例化,不会触发InitializingBean、ApplicationContextAware等回调函数。当使用getBean的方法的时候,即初始化、实例化Bean,就会触发Spring容器调用该Bean相关接口的调用

@Component和@Bean的区别

相同@Component@Bean都是注册Bean

区别@Component被动的方式,等待Spring容器扫描注解,将类注册为Bean,而@Bean则是主动的方式,主动地将类注册为Spring容器的Bean。

用法:@Component是通用写法,而@Bean通常用于引入外部类(从外部导入Jar包),又想将此类注册为Bean,但是又没有办法修改外部类的源代码,就可以利用@Bean将外部类注册成为Bean

@Configuration
@ComponentScan
public class Config {

    @Bean("customerBean")
    public Customer createCustomer() {
        Customer customer = new Customer();
        return customer;
    }
}

依赖注入Bean

注意:依赖的注入,需要依赖已经成为Bean被Spring容器管理

@Autowired

@Ccomponent
public class User{

    @Autowired
    private Player player;
}

构造函数

@Component
public class User {

    private final Player player;

    public User (Player player) {        System.out.println("Config构造方法:" + player);
        this.player = player;
    }

}

setter方法

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

通过@Autowired注入ApplicationContext

public class SimpleMovieLister {
    @Autowired
    private ApplicationContext context;

    private MovieFinder movieFinder;

    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

@Primary

根据类型自动注入Bean,当有两个相同类型的候选者,优先注入有@Primary注解的类

@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

}

@import

在一个配置类里面,引入其他配置类

@Configuration
@Import({TestConfig.class,DataSourceConfig.class})
public class ImportConfig {
}

@DependsOn

表明某个Bean需要依赖其他Bean

@Configuration
public class TestConfig {

    @Bean("depend")
    public AccountRepository createJdbcAccountRepo() {
        return new JdbcAccountRepository();
    }

    @Bean
    @DependsOn("depend")
    public TransferService createTransferSvc() {
        return new TransferServiceImpl(createJdbcAccountRepo());
    }

}

Environment

通过Environment可以获取JVM属性集

ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);

获取Spring容器的环境对象,判断JVM是否存在my-property属性,如果存在则返回true,否则返回flase

System.getProperties()表示JVM系统属性集,System.getenv()表示系统环境变量集。可以用此功能来判断用户环境变量是否存在某个属性值,判断是否存在然后进行相应的操作

可以使用System.getProperties().put(key,value)来自动存入自定义JVM属性集

也可以使用@PropertySource,添加Property到Spring的Enviroment

自定义Spring注解

通过Resource获取本地或者网络资源

public void testResourceLoader() throws IOException {

	// 本地资源可以使用类路径加载
	Resource resource = ctx.getResource("https://www.baidu.com/img/bd_logo1.png");


	Path path = Paths.get("C:\Users\admin\Desktop\java");

	Files.createDirectories(path);

	//@formatter:off
	Files.copy(
			resource.getInputStream(),
			path.resolve(UUID.randomUUID().toString() + resource.getFilename()),
			StandardCopyOption.REPLACE_EXISTING
	);
	//@formatter:on
}

注意:使用此处的Paths和Resource需要使用JDK11以上

自定义事件

Spring的容器ApplicationContext是通过ApplicationEvent类和ApplicationListener接口处理事件。ApplicationEvent用于定义事件,ApplicationListener用于监听事件。

创建自定义事件

创建自定义事件,需要创建一个自定义事件的类,继承ApplicationEvent类

public class MyEvent extends ApplicationEvent {
    private String name;
    private String address;

    public MyEvent(Object source, String name, String address) {
        super(source);
        this.name = name;
        this.address = address;
    }
}

发布自定义事件

发布一个自定义事件,需要创建一个类,实现ApplicationEventPublisherAware接口并将其注册为Spring Bean的类,然后调用ApplicationEventPublisher发布事件。这里,我选择用EventConfig的配置类实现接口,进而将ApplicationEventPublisher注入到Spring Bean,最后会通过测试类来调用该方法发布事件

@Configuration
public class EventConfig implements ApplicationEventPublisherAware{

    private ApplicationEventPublisher publisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    @EventListener({MyEvent.class})
    public void publisCustomEvent(MyEvent event) {
        System.out.println("--------开始监听事件---------");
        System.out.println("--获取自定义事件--"+event);
        System.out.println("--------处理事件-----"+event.getSource());
    }
}

监听自定义事件

需要监听自定义事件的运行,则需要创建一个实现ApplicationListener并注册为SpringBean的类,在这里我选择使用基于注解的事件监听器类在EventConfig配置类实现该功能

@Configuration
public class EventConfig implements ApplicationEventPublisherAware{

    private ApplicationEventPublisher publisher;


    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    @EventListener({MyEvent.class})
    public void publisCustomEvent(MyEvent event) {
        System.out.println("--------开始监听事件---------");
        System.out.println("--获取自定义事件--"+event);
        System.out.println(event.getSource());
        System.out.println(event.getName()+":"+event.getAddress());
        System.out.println("--------处理事件-----");

    }
}

测试类发布事件

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = {ImportConfig.class})
public class EventTest {
    @Autowired
    private ApplicationEventPublisher publisher;

    @Autowired
    private AopAdvice aopAdvice;

    @Test
    public void testEvent() {
        System.out.println("开始发布事件");
        publisher.publishEvent(new MyEvent("这是一个自定义事件噢","Test","测试自定义监听"));
        System.out.println("结束发布事件");
    }
}

AOP

导入坐标

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aspects</artifactId>
	<version>5.1.9.RELEASE</version>
</dependency>

开启AOP支持

在Spring配置类基于注解@EnableAspectJAutoProxy开启AOP支持

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

声明一个切面

使用@Aspect表明这个类为切面类

@Aspect
public class NotVeryUsefulAspect {

}

通知类型

前置通知 @Before

后置通知 @AfterReturing

异常通知 @AfterThrowing

最终通知 @After

环绕通知 @Around

切入点表达式

  • 执行任何公共方法
    • execution(public * *(..))
  • 执行名称为set开头的所有方法
    • execution(* set*(..))
  • 执行AccountService接口定义的任何方法
    • execution(* cn.edu.dddd.AccountService.* (..))
  • 执行service包中定义的任何方法
    • execution(* cn.edu.dddd.service.*.* (..)
  • 执行service包及其子包的任何方法
    • execution(* cn.edu.dddd.service..*.* ( . . ))