最全面Spring注解驱动开发教程

文章目录

1、容器

本文使用的依赖

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>4.3.11.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>3.0-alpha-1</version>
      <scope>provided</scope>
    </dependency>

1.11 AnnotationConfigApplicationContext

1.1.1 配置类

  早期的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:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	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-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">

	<context:property-placeholder location="classpath:person.properties"/>
	<!-- 包扫描、只要标注了@Controller、@Service、@Repository,@Component -->
	 <!-- <context:component-scan base-package="cn.klb" use-default-filters="false"></context:component-scan> -->
	<bean id="person" class="cn.klb.bean.Person"  scope="prototype" >
		<property name="age" value="${}"></property>
		<property name="name" value="zhangsan"></property>
	</bean>
	
	<!-- 开启基于注解版的切面功能 -->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
	
	<!-- <tx:annotation-driven/> -->

</beans>

  使用配置类效果如下:

@Configuration
public class MainConfig {
	
	@Bean("person")
	public Person person01(){
		return new Person("lisi", 20);
	}
}

  配置文件有什么,配置类就可以配置什么,用来代替配置文件的功能。

1.1.2 包扫描

  配置文件使用以下语句来包扫描:

<context:component-scan base-package="cn.klb" use-default-filters="false"></context:component-scan>

  那么配置类就用注解来包扫描:

@Configuration
@ComponentScan("cn.klb",useDefaultFilters = false)
public class MainConfig2 {
	// ...
}

1.2 组件添加

1.2.1 @ComponentScan

1. 源码定义
package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.type.filter.TypeFilter;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {

	@AliasFor("basePackages")
	String[] value() default {};

	@AliasFor("value")
	String[] basePackages() default {};

	Class<?>[] basePackageClasses() default {};

	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

	Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

	ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

	String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;

	boolean useDefaultFilters() default true;

	Filter[] includeFilters() default {};

	Filter[] excludeFilters() default {};

	boolean lazyInit() default false;

	@Retention(RetentionPolicy.RUNTIME)
	@Target({})
	@interface Filter {

		FilterType type() default FilterType.ANNOTATION;

		@AliasFor("classes")
		Class<?>[] value() default {};

		@AliasFor("value")
		Class<?>[] classes() default {};

		String[] pattern() default {};
	}
}
2. 作用

  默认会扫描该被注解的类所在的包下所有的配置类,相当于之前的 <context:component-scan>

3. 常用属性

  value:等同于属性basePackages,指定要扫描的包,如果没有取值,则默认就是被注解的类所在的包;
  basePackageClasses:类型安全的basePackages替代方法,用于指定扫描带注释组件的包。扫描指定的每个类所在的包。
  includeFilters:指定扫描的时候按照什么规则排除哪些组件;
  excludeFilters:指定扫描的时候只需要包含哪些组件;

4. 补充说明

  includeFiltersexcludeFilters的取值是@Filter,其源码如下:

@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {

	FilterType type() default FilterType.ANNOTATION;

	@AliasFor("classes")
	Class<?>[] value() default {};

	@AliasFor("value")
	Class<?>[] classes() default {};

	String[] pattern() default {};
	}

  type的取值有:
    FilterType.ANNOTATION:按照注解;
    FilterType.ASSIGNABLE_TYPE:按照给定的类型;
    FilterType.CUSTOM:按照自定义规则
    FilterType.ASPECTJ:按照ASOECTJ表达式;
    FilterType.REGEX:按照正则指定;
  使用示例:

@ComponentScan(useDefaultFilters = false,
        includeFilters = {
        @Filter(type = FilterType.ANNOTATION,classes = {Controller.class}),
        @Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {BookService.class}),
        @Filter(type = FilterType.CUSTOM,classes = MyTypeFilter.class)
})
public class MainConfig {
	//...
}

  需要注意的是,要关闭默认的过滤规则,否则自定义的失效。

1.2.2 @Bean

1. 源码定义
package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {

	@AliasFor("name")
	String[] value() default {};
	
	@AliasFor("value")
	String[] name() default {};

	Autowire autowire() default Autowire.NO;

	String initMethod() default "";

	String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}
2. 作用

  @Target说明了@Bean注解可以作用在方法和注解上,产生一个Bean对象,然后这个Bean对象交给Spring管理。它用于显式声明单个bean,而不是让Spring像@Component那样自动执行它。它将bean的声明与类定义分离,并允许您精确地创建和配置bean。

3. 常用属性

  value:等同于name,定义Bean的名称,如果有多个名称,则为一个主bean名称加上别名。如果未指定,bean的名称就是被注解的方法的名称;
  autowire:判断依赖关系是否通过基于约定的名字或类型自动装配注入的;
  initMethod:指定创建Bean时的初始化方法;
  destroyMethod:指定销毁Bean时的销毁方法。

4. 初始化和销毁
方法一:指定初始化销毁

  在组件的定义类中写好初始化方法和销毁方法,然后在注册组件时,给@Bean注解的initMethod属性和destroyMethod赋值方法名即可。例如:

@Bean(value = "person",initMethod = "init",destroyMethod = "destroy")
public Person person01(){
	return new Person("lisi", 20);
}
方法二:初始化其他方式

  使用接口InitializingBeanDisposableBean
  首先组件实现这两个接口,然后重写接口方法:

public class Blue implements InitializingBean, DisposableBean {

    public Blue(){
        System.out.println("Blue..Constructor...");
    }

    public void destroy() throws Exception {
        System.out.println("Blue..destroy..");
    }

    public void afterPropertiesSet() throws Exception {
        System.out.println("Blue...afterPropertiesSet...");
    }
}

  注册组件时,spring会自动识别实现了这两个接口的方法,进行初始化和销毁:

@Configuration
@ComponentScan("cn.klb")
public class MainConfig {

    @Bean("blue")
    public Blue getBlue(){
        return new Blue();
    }
}
方法三:JSR250

  JSR250是java规范,里面有两个注解@PostConstruct@PreDestroy,使用起来很简单,就是在组件定义类中的初始化方法加上@PostConstruct注解,构造器执行完后会执行该方法;@PreDestroy作用在销毁方法上,容器销毁之前会调用该方法。例如:

public class Car {
    public Car() {
        System.out.println("Car..Construct..");
    }

    @PreDestroy
    public void destroy(){
        System.out.println("Car..PreDestroy...");
    }

    @PostConstruct
    public void init() throws Exception {
        System.out.println("Car..PostConstruct..");
    }
}

  注册组件时,spring会自动识别实现了这两个注解作用的方法,进行初始化和销毁:

```java
@Configuration
@ComponentScan("cn.klb")
public class MainConfig {

    @Bean("car")
    public Car getCar(){
        return new Car();
    }
}
方法四:BeanPostProcessor
package org.springframework.beans.factory.config;

import org.springframework.beans.BeansException;

public interface BeanPostProcessor {

	Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;

	Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

  BeanPostProcessor是一个接口,也称为Bean后置处理器,该接口有两个方法:
  postProcessBeforeInitialization:在初始化之前进行后置处理操作;
  postProcessAfterInitialization:在初始化之后进行后置处理操作;
  实现了这个接口的实现类作为一个组件注册到IOC容器中,然后spring会识别出这个组件,并执行这两个方法。是所有组件初始化的基础。

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Before.."+bean+"->"+beanName);
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("After.."+bean+"->"+beanName);
        return bean;
    }
}

  注册到组件后,创建容器时,效果如下:
在这里插入图片描述

1.2.3 @Configuration

1. 源码定义
package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {

	String value() default "";
}
2. 作用

  @Target(ElementType.TYPE)表示@Configuration可以作用在类、接口(包括注解类型) 或enum声明,被注解的类称为配置类,内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContextAnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。

3. 常用属性

  value:显式指定与此配置类关联的Spring bean定义的名称。如果不指定(常见情况),将自动生成一个bean名称。只有当配置类通过组件扫描获取或直接提供给AnnotationConfigApplicationContext时,自定义名称才会应用。如果配置类注册为传统的XML bean定义,则bean元素的名称/id将优先。

1.2.4 @Component

1. 源码定义
package org.springframework.stereotype;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Component {

	String value() default "";
}
2. 作用

  用于自动检测和使用类路径扫描自动配置bean。被@Component注解的类和bean之间存在隐式的一对一映射(即每个类一个bean)。这种方法对需要进行逻辑处理的控制非常有限,因为它纯粹是声明性的。

3. 常用属性

  value:该值可以指示逻辑组件名称的建议,以便在自动检测组件的情况下转换为Spring bean。

1.2.5 @Service

  等价于@Component,主要是为了增强代码可读性,作用在业务层(service)。

1.2.6 @Controller

  等价于@Component,主要是为了增强代码可读性,作用在表现层(web)。

1.2.7 @Repository

  等价于@Component,主要是为了增强代码可读性,作用在持久层(dao)。

1.2.8 @Conditional

1. 源码定义
package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

	Class<? extends Condition>[] value();
}
2. 作用

  满足条件才会生成bean注册到IOC容器中。

3. 属性

  value:条件,要求是继承抽象类Condition并且实现matches方法。

4. 使用示例

  要使用@Condition注解,主要工作是定义一个类继承Condition,举例如下:
  定义条件类并实现matches方法:

public class MyCondition implements Condition {
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment environment = context.getEnvironment();
        String property = environment.getProperty("os.name");
        return property.contains("Windows");
        //return property.contains("Linux");
    }
}

  然后使用赋值:

@Bean("klb")
@Conditional(MyCondition.class)
public Person getPerson01(){
	return new Person("klb",12);
}

  当matches方法的返回值为true时,名称为”klb“的bean才会注册到IOC容器中。

1.2.9 @Primary

1. 源码定义
package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Primary {

}
2. 作用

  作用在注册Bean的地方(类、方法等),使得Spring进行自动装配的时候,默认首选的bean。这个注解主要是针对存在2个以上同个类型的bean的情况。

1.2.10 @Lazy

1. 源码定义
package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lazy {

	boolean value() default true;
}
2. 作用

  可以作用在类、方法、构造器和元素上,被注解的组件可以选择延迟加载操作,延迟加载意思是该bean不会随着IOC容器创建和马上创建,直等到要用到这个bean时再创建它。

3. 属性

  value:是否启动延迟加载,默认值为开启。

1.2.11 @Scope

1. 源码定义
package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {

	@AliasFor("scopeName")
	String value() default "";

	@AliasFor("value")
	String scopeName() default "";

	ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}
2. 作用

  springIoc容器中的一个作用域,在 Spring IoC 容器中具有以下几种作用域:基本作用域singleton(单例)、prototype(多例),Web 作用域(reqeust、session、globalsession)
  可以作用在类和方法上。

3. 常用属性

  scopeName:等同于value,指定要为带注释的组件/bean使用的范围的名称;取值有:
    ConfigurableBeanFactory.SCOPE_PROTOTYPE:表示bean是单例的;
    ConfigurableBeanFactory.SCOPE_SINGLETON:表示bean是多实例的;
    WebApplicationContext.SCOPE_REQUEST:web作用域request;
    WebApplicationContext.SCOPE_SESSION:web作用域session。

1.2.12 @Import

1. 源码定义
package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

	Class<?>[] value();
}
2. 作用

  可以作用在类、接口、注解或枚举上,快速给容器导入一个组件。

3. 属性

  value:Class类型数组,可以有三种取值,分别为:
    Configuration:要导入IOC容器中的组件,容器中就会自动注册这个组件,组件的id默认为全类名;
    ImportSelector:一个选择器,其实就是实现了ImportSelector接口的类,重写selectImports方法返回要导入的组件的全类名;
    ImportBeanDefinitionRegistrar:一个Bean注册器,其实就是实现了ImportBeanDefinitionRegistrar接口的类,重写registerBeanDefinitions方法中注册要导入的组件。

4. 使用示例

  定义三个组件:

package cn.klb.bean;

public class Car {
	// ...
}
package cn.klb.bean;

public class Color {
	// ...
}
package cn.klb.bean;

public class Rainbow {
	// ...
}

  定义一个ImportSelector选择器:

package cn.klb.selector;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

/**
 * @Author: Konglibin
 * @Description:
 * @Date: Create in 2020/5/13 18:48
 * @Modified By:
 */
public class MySelector implements ImportSelector {
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        String[] s = {"cn.klb.bean.Color"};
        return s;
    }
}

  定义一个ImportBeanDefinitionRegistrarBean注册器:

package cn.klb.registrar;

import cn.klb.bean.RainAndBow;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

/**
 * @Author: Konglibin
 * @Description:
 * @Date: Create in 2020/5/13 20:20
 * @Modified By:
 */
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    	// 要注册组件的前提条件(自定义)
        boolean hasPerson = registry.containsBeanDefinition("person");
        if(hasPerson){
        	// 定义待注册Bean
            RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Rainbow.class);
            // 注册Bean,并且这个Bean的id(或者叫名字)为 rainbow
            registry.registerBeanDefinition("rainbow",rootBeanDefinition);
        }
    }
}

  快速导入组件:

@Import({Car.class,MySelector.class, MyImportBeanDefinitionRegistrar.class})
public class MainConfig {
	// ...
}

1.2.14 工厂模式

  除了直接在方法上加上@Bean注解来注册组件,Spring还提供FactoryBean来注册。
  首先是实现FactoryBean接口并实现接口方法:

public class MyFactoryBean implements FactoryBean<Person> {
    public Person getObject() throws Exception {
        System.out.println("MyFactoryBean..getObject");
        return new Person();
    }

    public Class<?> getObjectType() {
        return Person.class;
    }

    public boolean isSingleton() {
        return true;
    }
}

  然后在被@Configuration作用的配置类中添加:

@Configuration
public class MainConfig {

    @Bean
    public MyFactoryBean factoryBean(){
        return new MyFactoryBean();
    }
}

  Spring会识别出这个Bean是一个工厂,所以注册的时候不会把它的类型当成MyFactoryBean,而是它所生产出来的Person类型。如果我们就是要Spring把它看成普通的Bean,只需要在获取Bean的时候加一个&符号,使用示例:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);

// factoryBean 类型为 Person
Object factoryBean = context.getBean("factoryBean");

// factoryBean 类型为 MyFactoryBean
Object factoryBean = context.getBean("&factoryBean");

1.3 组件赋值

1.3.1 @Value

1. 源码定义
package org.springframework.beans.factory.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {

	String value();
}
2. 作用

  给组件的属性赋值,有三种方式:基本数值、SpEL表达式、${}取出配置文件的值。

3. 属性

  value,取值为我们要赋值的值。

4. 使用示例

  首先在配置类中使用@PropertySource来指定配置文件的名称。

@Configuration
@PropertySource("classpath:/person.properties")
public class MainConfig2 {

    @Bean("person")
    public Person getPerson(){
        return new Person();
    }
}

  自定义组件:

public class Person {

    @Value("klb")
    private String name;

    @Value("#{20-3}")
    private Integer age;

    @Value("${person.hobby}")
    private String hobby;

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", hobby='" + hobby + '\'' +
                '}';
    }
}

  由于配置类是早于Bean创建,所以在初始化Person组件时,${person.hobby}已经可以读取到值了。

1.3.2 @Autowired

1. 源码定义
package org.springframework.beans.factory.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {

	boolean required() default true;
}
2. 作用

  实现对组件的依赖关系赋值,Spring检测到被@Autowired注解的组件时,优先按照类型去容器找对应的组件,如果找到多个相同类型的组件,再将待赋值的变量的名称作为组件的id去容器中查找。

3. 属性

  required:被作用的组件是否一定要被赋值,默认是true。如果设置为false,当在IOC容器中找不到可以赋值的组件时,赋值为null;true时,如果找不到可赋值的组件会抛出异常。

4. 补充说明
指定bean进行赋值

  Spring要实现自动注入时,优先选择同类型的组件,如果我们要选择特定的组件来赋值,可以使用@Qualifier注解:

public class Color {

    @Qualifier("ylo")
    @Autowired
    private Yello yello;

	// ...    
}

  这样Spring会从容器中找到名字为ylo的bean来执行赋值。

其他可以自动赋值的注解

  @Resource@Inject分别是JSR250和JSR330规范下的注解,属于Java原生注解,不支持指定方式赋值,而且@Inject还要导入javax.inject包才能使用,如果使用spring框架,不建议使用这两个注解。

1.3.5 @Profile

1. 源码定义
package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.ConfigurableEnvironment;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {

	String[] value();
}
2. 作用

  指定组件在哪个环境下才被注册到容器中。不指定则默认任何环境都把组件注册到容器中。如果作用在配置类中,那么在指定环境下配置类才会生效。没有环境标识的Bean在任何环境下都生效。

3. 属性

  value:环境的名称。默认是default环境。

4. 激活环境
方法一:命令行形式

  在虚拟机参数位置加载:

-Dspring.profiles.active=test
方法二:代码方式
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.getEnvironment().setActiveProfiles("test");
context.register(MainConfig3.class);
context.refresh();

1.4 组件注入

  @Autowired除了可以作用在组件的属性上,还可以作用在方法参数、构造器上。

1.4.1 方法参数

  作用在方法上时,直接从IOC容器找组件来赋值,默认不写@Autowired,以下三种写法是一样的:

@Bean("red")
public Red getRed(Yello yello){
	return new Red(yello);
}
@Bean("red")
@Autowired
public Red getRed(Yello yello){
	return new Red(yello);
}
@Bean("red")
public Red getRed(@Autowired Yello yello){
	return new Red(yello);
}

1.4.2 构造器注入

  作用在构造器上时,Spring会从IOC容器中找到组件来给构造器的参数赋值,如果组件只有一个有参构造器,那么@Autowired可以省略不写。

@Component("red")
public class Red {
    private Yello yello;

    public Yello getYello() {
        return yello;
    }

    public void setYello(Yello yello) {
        this.yello = yello;
    }

	// 注解省略也可以生效,前提是只有一个有参构造
    // @Autowired
    public Red(Yello yello) {
        this.yello = yello;
    }
}

1.4.3 使用Spring底层组件

  如果我们自定义的组件要使用Spring底层的组件,比如ApplicationContextBeanFactory等等,可以通过实现响应接口来进行使用。
  比如我要使用ApplicationContext组件,那么我们就实现ApplicationContextAware接口,并实现它的方法;又或者我要使用BeanName这个组件,那么就实现BeanNameAware接口;又或者我要使用EmbeddedValueResolver组件,就实现EmbeddedValueResolverAware接口。举例:

@Component
public class Red implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {

    private ApplicationContext applicationContext;

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("applicationContext:" + applicationContext);
        this.applicationContext = applicationContext;
    }

    public void setBeanName(String name) {
        System.out.println("当前bean的名字:" + name);
    }

    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        String s = resolver.resolveStringValue("你好${os.name},我是#{3*6}");
        System.out.println(s);
    }
}

  Spring底层组件肯定是在我们自定义组件注册之前就存在了,因此可以通过实现xxxAware接口来调用Spring底层组件。
  每一个xxxAware都是由一个xxxAwareProcessor来处理。而xxxAwareProcessor是后置处理器。

1.5 AOP

1.5.1 使用流程

  1、导入AOP依赖:

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

  2、定义业务逻辑类(被增强):

package cn.klb.aop;

public class MathCalculator {
	
	public int div(int i,int j){
		System.out.println("MathCalculator...div...");
		return i/j;	
	}
}

  3、定义日志切面类:

package cn.klb.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;

import java.util.Arrays;

/**
 * @Author: Konglibin
 * @Description:
 * @Date: Create in 2020/5/15 19:43
 * @Modified By:
 */
@Aspect
public class LogAspect {

    @Pointcut("execution(public int cn.klb.aop.MathCalculator.*(..))")
    public void pointCut() {
    }

    ;

    @Before("pointCut()")
    public void logStart(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        String name = joinPoint.getSignature().getName();
        System.out.println("@Before...name=" + name + ",args={" + Arrays.asList(args) + "}");
    }

    @After("cn.klb.aop.LogAspect.pointCut()")
    public void logEnd(JoinPoint joinPoint) {
        System.out.println("@After...");
    }

    @AfterReturning(value = "pointCut()", returning = "result")
    public void logReturn(JoinPoint joinPoint, Object result) {
        System.out.println("@AfterReturning...result=" + result);
    }

    @AfterThrowing(value = "pointCut()", throwing = "exception")
    public void logException(JoinPoint joinPoint, Exception exception) {
        System.out.println("@AfterThrowing...exception=" + exception);
    }
}

  4、配置类中将业务逻辑组件和切面类都加入到容器中:

package cn.klb.config;

import cn.klb.aop.LogAspect;
import cn.klb.aop.MathCalculator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * @Author: Konglibin
 * @Description:
 * @Date: Create in 2020/5/15 19:17
 * @Modified By:
 */
@Configuration
@EnableAspectJAutoProxy
public class MainConfigOfAOP {

    @Bean
    public MathCalculator calculator(){
        return new MathCalculator();
    }

    @Bean
    public LogAspect logAspect(){
        return new LogAspect();
    }
}

1.5.2 用到的注解

@EnableAspectJAutoProxy
1. 源码定义
package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

	boolean proxyTargetClass() default false;

	boolean exposeProxy() default false;
}
2. 作用

  作用在配置类上,告诉Spring开启AOP注解支持。

@Aspect
1. 源码定义
package org.aspectj.lang.annotation;

import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Aspect {

    public String value() default "";
}
2. 作用

  告诉spring,被这个注解作用的类是一个切面类,而不是一个普通bean组件。

3. 属性

  value:每个子句表达式,默认为单例切面。

@Before
1. 源码定义
package org.aspectj.lang.annotation;

import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Before {

    String value();
    
    String argNames() default "";
}
2. 作用

  作用在方法上,被注解的方法是前置通知,在目标方法运行之前运行。

3. 属性

  value:指定切入点表达式,即目标方法;
  argNames:用于获取切入点表达式的参数;

@After
1. 源码定义
package org.aspectj.lang.annotation;

import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface After {

    String value();
    
    String argNames() default "";
}
2. 作用

  作用在方法上,被注解的方法是后置通知,在目标方法运行结束之后运行(无论方法正常结束还是异常结束)。

3. 属性

  value:指定切入点表达式;
  argNames:获取切入点表达式的参数;

@AfterReturning
1. 源码定义
package org.aspectj.lang.annotation;

import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterReturning {

    String value() default "";

    String pointcut() default "";

    String returning() default "";
    
    String argNames() default "";
}
2. 作用

  作用在方法上,被注解的方法是返回通知,在目标方法正常返回(目标方法异常就不会执行)之后运行。

3. 属性

  value:指定切入点表达式;
  pointcut:也是指定切入点表达式,如果设置了,就覆盖value属性;
  returning:目标方法的返回值绑定到返回通知的参数中;
  argNames:获取切入点表达式的参数。

@AfterThrowing
1. 源码定义
package org.aspectj.lang.annotation;

import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterThrowing {

    String value() default "";

    String pointcut() default "";

    String throwing() default "";

    String argNames() default "";
}
2. 作用

  作用在方法上,被注解的方法是异常通知,在目标方法出现异常以后运行(如果没有异常则不运行)。

3. 属性

  value:指定切入点表达式;
  pointcut:也是指定切入点表达式,如果设置了,就覆盖value属性;
  throwing:目标方法的异常绑定到异常通知的参数中;
  argNames:获取切入点表达式的参数。

@Pointcut
1. 源码定义
package org.aspectj.lang.annotation;

import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Pointcut {

    String value() default "";
    
    String argNames() default "";
}
2. 作用

  作用在方法上,被注解的方法等价于切入点表达式,其他通知需要指定切入点表达式时引用被@Pointcut作用的方法的方法名即可。

3. 属性

  value:指定切入点表达式;
  argNames:获取切入点表达式的参数。

1.6 声明式事务

1.6.1 使用流程

  首先,在配置类中添加事务管理器TransactionManager,同时使用注解@EnableTransactionManagement开启事务支持:

@Configuration
@EnableTransactionManagement
@ComponentScan({"cn.klb.dao", "cn.klb.service"})
public class MainConfigOfTx {

    @Bean
    public DataSource dataSource() throws Exception {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("root");
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/db1");
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) throws Exception {
        return new DataSourceTransactionManager(dataSource);
    }
}

  然后,在需要事务管理的地方用@Transactional注解:

@Service("bookservice")
public class BookService {

    @Autowired
    public BookDao bookdao;

    @Transactional
    public void insert(){
        bookdao.insert();
        System.out.println("插入完成");
        int a = 3/0;
    }
}

1.6.2 用到的注解

@EnableTransactionManagement
1. 源码定义
package org.springframework.transaction.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Import;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({TransactionManagementConfigurationSelector.class})
public @interface EnableTransactionManagement {
    boolean proxyTargetClass() default false;

    AdviceMode mode() default AdviceMode.PROXY;

    int order() default 2147483647;
}
2. 作用

  作用在配置类上,告诉spring开启事务支持功能,同时必须在容器中注册事务管理器,同时这个事务管理器注入数据源。

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) throws Exception {
	return new DataSourceTransactionManager(dataSource);
}
@Transactional
1. 源码定义
package org.springframework.transaction.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";

    Propagation propagation() default Propagation.REQUIRED;

    Isolation isolation() default Isolation.DEFAULT;

    int timeout() default -1;

    boolean readOnly() default false;

    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}
2. 作用

  作用在方法上,被注释的方法整体变为一个事务,当出现异常时,所有数据库操作都回滚,不会生效。

3. 属性

  value:等同于属性transactionManager,指定事务管理器;
  propagation:事务的传播行为,默认为“新建事务”;
  isolation:事务的隔离级别,默认为“默认级别”;
  timeout:超时时间,默认值-1,表示没有超时限制。如果赋值了,则以秒为单位;
  readOnly:是否只读事务,默认值为false。(建议查询时设置为只读);
  rollbackFor
  rollbackForClassName:
  noRollbackFor:
  noRollbackForClassName:

2、web

2.1 servlet 3.0

2.1.1 Runtimes Pluggability(运行时插件)

  Servlet容器启动的时候,会扫描当前应用里面每一个jar包的ServletContainerInitializer的实现,具体说是每一个jar包里面的META-INF/services/路径下的有javax.servlet.ServletContainerInitializer名字的文件,里面保存着ServletContainerInitializer实现类的全限定类名。
  比如我现在有个类实现了ServletContainerInitializer

public class MyServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        
    }
}

在这里插入图片描述
  javax.servlet.ServletContainerInitializer文件内容为:

cn.klb.servlet.MyServletContainerInitializer

  Servlet启动后就扫描到这个实现类,可以在实现类上注释@HandlesTypes这个注解,注解的属性就是我们想传入的感兴趣的类型。如:

@HandlesTypes(value = {UserService.class})
public class MyServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        System.out.println("感兴趣的类型:");
        for (Class<?> claz : set) {
            System.out.println(claz);
        }
    }
}

  那么,onStartup方法的set参数就可以获取UserService接口的实现类、子接口等。
  比如现在有一个实现了UserService接口的实现类HelloService,启动tomcat可以看到:
在这里插入图片描述

2.1.2 自定义注册三大组件

  首先,定义三个组件:

public class UserFilter implements Filter {
	// ...
}
public class UserListener implements ServletContextListener {
	// ...
}
public class UserServlet extends HttpServlet {
	// ...
}

  然后,在ServletContainerInitializer中手动注册:

public class MyServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
    
        ServletRegistration.Dynamic userServlet = servletContext.addServlet("userServlet", new UserServlet());
        userServlet.addMapping("/user");

        FilterRegistration.Dynamic userFilter = servletContext.addFilter("userFilter", new UserFilter());
        userFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");

        servletContext.addListener(UserListener.class);
    }
}

2.2 SpringMVC整合原理

  web容器在启动的时候,会扫描每个jar包下的META-INF/services/javax.servlet.ServletContainerInitializer,然后加载这个文件指定的类SpringServletContainerInitializer
  我们导入spring-webmvc依赖后,会有个间接依赖:spring-web:
在这里插入图片描述
  文件内容为:

org.springframework.web.SpringServletContainerInitializer

  找到这个类:

package org.springframework.web;

import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;

import org.springframework.core.annotation.AnnotationAwareOrderComparator;

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

	@Override
	public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

		List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();

		if (webAppInitializerClasses != null) {
			for (Class<?> waiClass : webAppInitializerClasses) {
				// Be defensive: Some servlet containers provide us with invalid classes,
				// no matter what @HandlesTypes says...
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						initializers.add((WebApplicationInitializer) waiClass.newInstance());
					}
					catch (Throwable ex) {
						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
					}
				}
			}
		}

		if (initializers.isEmpty()) {
			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
			return;
		}

		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
		AnnotationAwareOrderComparator.sort(initializers);
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}
}

  也就是说,servlet容器启动后,会加载感兴趣的WebApplicationInitializer接口的下的所有组件;并且为WebApplicationInitializer组件创建对象(组件不是接口,不是抽象类)。
  找到WebApplicationInitializer实现类:
在这里插入图片描述
  这三个实现类是继承关系。

2.2.1 AbstractContextLoaderInitializer

  定位到AbstractContextLoaderInitializer

package org.springframework.web.context;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.web.WebApplicationInitializer;

public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {

	// ...

	protected abstract WebApplicationContext createRootApplicationContext();

	// ...
}

  AbstractContextLoaderInitializer从名字看出它叫做“抽象容器加载初始化器”,里面唯一的抽象方法是createRootApplicationContext(),用于创建根容器。

2.2.2 AbstractDispatcherServletInitializer

  从名字可以看出它叫做“抽象DispatcherServlet初始化器”,继承了上面的AbstractContextLoaderInitializer,定位到源码:

package org.springframework.web.servlet.support;

import java.util.EnumSet;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterRegistration;
import javax.servlet.FilterRegistration.Dynamic;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.core.Conventions;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.AbstractContextLoaderInitializer;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.FrameworkServlet;

public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {

	// ...

	protected abstract WebApplicationContext createServletApplicationContext();

	protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
		return new DispatcherServlet(servletAppContext);
	}

	// 注册 DispatcherServlet 到 ServletContext 中
	protected void registerDispatcherServlet(ServletContext servletContext) {
		// ...
	}

	protected abstract String[] getServletMappings();

	// ...
}

  抽象方法createServletApplicationContext()用于创建一个web的ioc容器,createDispatcherServlet()方法创建了DispatcherServlet,并且注册到ServletContext中。

2.2.3 AbstractAnnotationConfigDispatcherServletInitializer

  抽象注解配置DispatcherServlet初始化器,继承了上面的AbstractDispatcherServletInitializer,定位到源码:

package org.springframework.web.servlet.support;

import org.springframework.util.ObjectUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;

public abstract class AbstractAnnotationConfigDispatcherServletInitializer
		extends AbstractDispatcherServletInitializer {

	@Override
	protected WebApplicationContext createRootApplicationContext() {
		Class<?>[] configClasses = getRootConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses)) {
			AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
			rootAppContext.register(configClasses);
			return rootAppContext;
		}
		else {
			return null;
		}
	}

	@Override
	protected WebApplicationContext createServletApplicationContext() {
		AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
		Class<?>[] configClasses = getServletConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses)) {
			servletAppContext.register(configClasses);
		}
		return servletAppContext;
	}

	protected abstract Class<?>[] getRootConfigClasses();

	protected abstract Class<?>[] getServletConfigClasses();
}

  它实现父类方法createRootApplicationContext()来创建根容器,方面里面调用了getRootConfigClasses()来获取配置类,所以我们只需要重写getRootConfigClasses()方法,把我们自定义的配置类返回即可,根容器就会获得我们的配置类。
  它实现父类方法createServletApplicationContext,用于创建web的IOC容器,里面调用了getServletConfigClasses()方法,同样是获取配置类。

2.2.4 小结

  如果我们以注解的方式来启动SpringMVC,那么就继承AbstractAnnotationConfigDispatcherServletInitializer,然后实现抽象方法来指定配置类。
  这个就是spring和servlet整合的关键,继承了这个抽象类后,servlet容器会扫描到实现类,同时spring的配置类也读取到了。
  最关键的就是ServletContext注册到了spring的IOC容器中。然后spring就可以为所欲为了。

2.3 定制SpringMVC

  ServletContext注册到了spring的IOC容器中,所以SpringMVC可以定制很多功能。
  首先是在自定义的配置类中开启WebMvc功能:
  并且实现WebMvcConfigurer接口,为了方便,SpringMVC做了一个适配器WebMvcConfigurerAdapter,对接口的所有方法做了空实现,然后我们继承这个适配器,要定制什么方法就去重写即可,不用重写WebMvcConfigurer的所有方法。

@EnableWebMvc
public class AppConfig  extends WebMvcConfigurerAdapter  {
	// ...
}

  参考:https://docs.spring.io/spring/docs/5.3.0-SNAPSHOT/spring-framework-reference/web.html#mvc-config

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值