Spring 注解驱动

一、组件注册

1、@Configuration和@Bean 【给容器中注册组件】

  • 使用配置文件beans.xml,通过bean标签注册组件
<?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">
    
    <!--注意:Person对象,要有setter方法,不然无法注入值-->
    <bean id="person" class="com.zxc.bean.Person">
        <property name="name" value="小红"/>
        <property name="age" value="18"/>
    </bean>
</beans>
public class MainTest {
	public static void main(String[] args) {
        //获取IOC容器
		ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
		//1、方式一: 根据id,从IOC容器中获取bean
		Person person = (Person) context.getBean("person");
		System.out.println("person:" + person);
		//2、方式二:根据类型,从IOC容器中获取bean
		Person person2 = context.getBean(Person.class);
		System.out.println("person2:" + person2);
		//3、方式三:根据id和类型,从IOC容器中获取bean
		Person person3 = context.getBean("person", Person.class);
		System.out.println("person3:" + person2);
		//IOC容器bean为单实例
		System.out.println(person==person2); //true
		System.out.println(person==person3); //true
	}
}
//output
/*
person:Person(name=小红, age=18)
person2:Person(name=小红, age=18)
person3:Person(name=小红, age=18)
*/
  • 使用注解@Configuration@Bean给容器中注册组件
    • @Configuration是 Spring 3.0 添加的一个注解,用来代替 applicationContext.xml 配置文件,所有这个配置文件里面能做到的事情都可以通过这个注解所在类来进行注册。替代了xml形式的配置文件,用配置类开发。
//配置类==配置文件
@Configuration  //告诉Spring这是一个配置类
public class MainConfig {
	//使用@Bean注解给容器中注册一个Bean
    //id默认使用方法名,类型为返回值类型
    //也可以使用value指定id
	@Bean(value="person00")
	public Person person() {
		return new Person("小黑",18);
	}
}
public class MainTest {
	public static void main(String[] args) {
        //获取IOC容器
	    ApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class);
		Person person = (Person) ac.getBean("person00");
		System.out.println(person);
		//根据类型获取容器中的实例及其子类的名称,组成一个字符串数组返回
		String[] namesForType = ac.getBeanNamesForType(Person.class);
		for (String string : namesForType) {
			System.out.println(string);
		}
	}
}
//output
/*
Person(name=小黑, age=18)
person00
*/

2、@ComponentScan 【自动扫描组件】和扫描规则

  • 使用配置文件beans.xml,扫描指定包下的组件(扫描标注了@Controller、@Service、@Repository、@Component的组件)
<!--包扫描,扫描该包下标注了@Controller、@Service、@Repository、@Component的组件  -->
<context:component-scan base-package="com.zxc"/>
  • 使用注解@ComponentScan,扫描指定包下的组件(扫描标注了@Controller、@Service、@Repository、@Component的组件)
    • @ComponentScan是 Spring 3.1 添加的一个注解,用来代替配置文件中的 component-scan 配置,开启组件扫描,即自动扫描包路径下的 @Component 注解进行注册 bean 实例到 context 中。
//配置类==配置文件
@Configuration  //告诉Spring这是一个配置类
@ComponentScan(value="com.zxc") //value: 指定要扫描的包
public class MainConfig {
	
}

添加controller、service、dao组件

public class MainTest {
	public static void main(String[] args) {
        //获取IOC容器
		ApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class);
		//获取IOC容器里的Bean名字
		String[] beanDefinitionNames = ac.getBeanDefinitionNames();
		for (String string : beanDefinitionNames) {
			System.out.println(string);
		}
	}
}
//output
/*
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
employeeController
employeeDao
employeeService
*/
2.1 扫描规则

在注解@ComponentScan里有两个属性:

  • Filter[] includeFilters() default {}:指定扫描的时候,只包含哪些组件 (使用该属性时,要将useDefaultFilters设为 false,关闭默认过滤规则)

  • Filter[] excludeFilters() default {}:指定扫描的时候,按照扫描规则,来排除哪些组件

//配置类==配置文件
@Configuration  //告诉Spring这是一个配置类
@ComponentScan(value="com.zxc",excludeFilters={
    //FilterType.ANNOTATION  按照注解类型排除
	@Filter(type=FilterType.ANNOTATION,classes= {Controller.class}),
    //FilterType.ASSIGNABLE_TYPE  按照指定类型排除
    @Filter(type=FilterType.ASSIGNABLE_TYPE, classes= {EmployeeDao.class}
}) 
public class MainConfig {
	
}

执行MainTest里的主方法。由下面结果可见,扫描指定包下的组件时,排除EmployeeDao类和贴有Controller注解的类。

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
employeeService

FilterType,是一个枚举类。

public enum FilterType {
    
	//按照注解
	ANNOTATION,

	//按照给定的类型
	ASSIGNABLE_TYPE,

	//按照ASPECTJ表达式(基本不用)
	ASPECTJ,

	//按照正则表达式
	REGEX,

	//按照自定义规则
	CUSTOM
}

@ComponentScan上有@Repeatable(ComponentScans.class),因此可以多写几个@ComponentScan或者使用@ComponentScans。

2.2 自定义过滤规则(TypeFilter)
public class MyTypeFilter implements TypeFilter{
	 /**
     * metadataReader:读取到的当前正在扫描的类的信息
     * metadataReaderFactory:可以获取到其他任何类的信息
     */
	@Override
	public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
		//获取当前类注解的信息
		AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
		//获取当前正在扫描的类的信息
		ClassMetadata classMetadata = metadataReader.getClassMetadata();
		//获取当前类资源的信息(类的路径)
		Resource resource = metadataReader.getResource();
        //获取当前类的类名
		String className = classMetadata.getClassName();
        //打印当前扫描到类
		System.out.println("当前扫描到的类-->"+className);
        //指定过滤规则
		if(className.contains("er")) {
            //匹配成功
			return true;
		}
		//匹配失败
		return false;
	}
}

注意:使用自定义注解记得需要关闭默认过滤器useDefaultFilters = false

@Configuration  //告诉Spring这是一个配置类
@ComponentScan(value="com.zxc",includeFilters={//只包含满足下面过滤规则的组件
		//扫描时按照自定义规则的类过滤
	@Filter(type=FilterType.CUSTOM,classes= {MyTypeFilter.class})
},useDefaultFilters = false) //扫描注册
public class MainConfig {
	
}

执行MainTest里的主方法。由下面结果可见,扫描指定包下的组件时,只包含满足自定义规则的组件。

当前扫描到的类-->com.zxc.bean.Person
当前扫描到的类-->com.zxc.controller.EmployeeController
当前扫描到的类-->com.zxc.dao.EmployeeDao
当前扫描到的类-->com.zxc.filter.MyTypeFilter
当前扫描到的类-->com.zxc.service.EmployeeService
当前扫描到的类-->com.zxc.service.IEmployeeService
当前扫描到的类-->com.zxc.test.MainTest
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig   		//配置类
person				//满足自定义匹配规则,含有er
employeeController	//同上
myTypeFilter		//同上
employeeService		//同上
2.3 @Scope 设置组件作用域
作用域描述
singleton (单例)在IOC容器中仅存在一个bean实例(默认的scope)
prototype (多例)在IOC容器中每次从容器中获取bean时,都返回一个新的实例。
request(请求)每次HTTP请求都会创建一个新的bean。
session(会话)同一个session共享一个bean,不同的 HTTP Session 不同。
globalSession(全局)一般应用于Porle应用环境,分布式系统存在全局session概念(单点登录),如果不是porlet环境,globalSession等同于session。

在默认单例作用域下,在IOC容器启动时,就已经把组件放入了IOC容器,以后每次获取都是从IOC容器中获取。

@Configuration  //告诉Spring这是一个配置类
public class MainConfig {
	@Bean
	public Person person() {
        System.out.println("给容器中添加 person...");
		return new Person("小黑",18);
	}
}
public class MainTest {
	public static void main(String[] args) {
        //获取IOC容器
		ApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class);
		System.out.println("容器创建完成...");
	}
}
//output
/*
给容器中添加 person...
容器创建完成...
*/

在多例作用域下,IOC容器启动时,不会创建对象,而是在每次调用getBean()时,相当于执行 new xxxBean(),才会创建。

@Configuration  //告诉Spring这是一个配置类
public class MainConfig {
    
    @Scope("prototype")
	@Bean
	public Person person() {
        System.out.println("给容器中添加 person...");
		return new Person("小黑",18);
	}
}

执行上面MainTest主方法,输出如下:

容器创建完成...
2.4 @Lazy 懒加载
  • 主要针对的是,默认在容器启动的时候创建对象的单实例bean

  • 加上@Lazy注解,容器启动时不会创建对象。而是在第一次使用Bean创建对象。

3、@Import 【给容器快速导入一个组件】

给容器注册组件:

  • 包扫描+组件标注注解(@Controller、@Service、@Repository、@Component),导入自己写的类

  • @Bean,导入第三方包里的组件

  • @Import,快速给容器导入一个组件

    • @Import,要导入容器的组件, id默认是全类名。这是 Spring 3.0 添加的新注解,用来导入一个或者多个 @Configuration 注解修饰的类,这在 Spring Boot 里面应用很多。

    • 实现ImportSelector接口,返回需要导入的组件全类名数组(用的最多)。

    • 实现ImportBeanDefinitionRegistrar接口,手动注册bean到容器中。

    • 实现Spring提供的FactoryBean接口(工厂Bean)

      • 默认获取到的是工厂bean调用getObject()创建的对象

      • 要获取工厂Bean本身,需要在id前面加一个&

        Object bean3 = ac.getBean("&colorFactoryBean");
        
3.1 @Import 直接导入
@Configuration  //告诉Spring这是一个配置类
@Import({Red.class,Blue.class})   //关注点
public class MainConfig {
	
}
3.2 实现ImportSelector接口
public class MyImportSelector implements ImportSelector {
	/**
	 * 返回值就是需要导入容器中的组件全类名
	 *  annotationmetadata:当前标注@Import注解类的所有注解信息
	 */
	@Override
	public String[] selectImports(AnnotationMetadata annotationmetadata) {
		// 不能返回null,不然空指针
		return new String[] { "com.zxc.bean.Black", "com.zxc.bean.Orange" };
	}
}

配置类:

@Configuration  //告诉Spring这是一个配置类
@Import({Red.class,Blue.class,MyImportSelector.class})   //关注点
public class MainConfig {
	
}

测试:

public class MainTest {
	public static void main(String[] args) {
        //获取IOC容器
		ApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class);
        //获取IOC容器中所有bean的id
		String[] beanDefinitionNames = ac.getBeanDefinitionNames();
		for (String string : beanDefinitionNames) {
			System.out.println(string);
		}
	}
}
//output
/*
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig             //配置类
com.zxc.bean.Red       //@Import 直接注册
com.zxc.bean.Blue      //@Import 直接注册
com.zxc.bean.Black     //实现ImportSelector接口返回需要注册的全类名方式
com.zxc.bean.Orange    //同上
*/
3.3 实现ImportBeanDefinitionRegistrar接口
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar{
	/**
	 * AnnotationMetadata: 当前类的注解信息
	 * BeanDefinitionRegistry:BeanDefinition注册类;
	 * 把所有需要注册的Bean,调用BeanDefinitionRegistry.registerBeanDefinition()方法手动注册
	 */
	@Override
	public void registerBeanDefinitions(AnnotationMetadata annotationmetadata,
			BeanDefinitionRegistry registry) {
		//容器中是否有id为com.zxc.bean.Red的Bean
		boolean definition = registry.containsBeanDefinition("com.zxc.bean.Red");
		boolean definition2 = registry.containsBeanDefinition("com.zxc.bean.Blue");
		if(definition && definition2) {
			//指定bean名
			RootBeanDefinition beandefinition = new RootBeanDefinition(RainBow.class);
			registry.registerBeanDefinition("rainBow", beandefinition);
		}
	}
}

配置类:

@Configuration  //告诉Spring这是一个配置类
@Import({Red.class,Blue.class,MyImportSelector.class,MyImportBeanDefinitionRegistrar.class})
public class MainConfig {
	
}

执行MainTest主方法结果如下:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
com.zxc.bean.Red
com.zxc.bean.Blue
com.zxc.bean.Black
com.zxc.bean.Orange
rainBow            //手动注册
3.4 实现 FactoryBean接口
public class ColorFactoryBean implements FactoryBean<Color> {
	//生成bean对象,并加载进容器中
	@Override
	public Color getObject() throws Exception {
		return new Color();
	}
	//返回生成的Bean类型
	@Override
	public Class<?> getObjectType() {
		return Color.class;
	}
	//是否是单例
	@Override
	public boolean isSingleton() {
		return true;
	}

}

配置类:

@Configuration  //告诉Spring这是一个配置类
public class MainConfig {
	@Bean
	public ColorFactoryBean colorFactoryBean() {
		return new ColorFactoryBean();
	}
}

测试:

public class MainTest {
	public static void main(String[] args) {
        //获取IOC容器
		ApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class);
        //获取工厂
		Object bean = ac.getBean("colorFactoryBean");
        //虽然id是colorFactoryBean,但是类型却是com.zxc.bean.Color
		System.out.println("bean的类型-->"+bean.getClass());
		Object bean2 = ac.getBean("colorFactoryBean");
        //true说明是单例
		System.out.println(bean==bean2);
		//获取工成本身
		Object bean3 = ac.getBean("&colorFactoryBean");
		System.out.println(bean3.getClass());
	}
}
//output
/*
bean的类型-->class com.zxc.bean.Color
true
class com.zxc.bean.ColorFactoryBean
*/

4、@Conditional ({condition})【按照条件注册bean】

需求:

  • 如果是windows,给容器注册Bill

  • 如果是linux系统,给容器注册Tom

配置类:

@Configuration  //告诉Spring这是一个配置类
public class MainConfig {
	@Bean("小红")
	public Person person() {
		return new Person("小红",19);
	}
	//如果是Windows环境,则注册Bean
	@Conditional(WindowsCondition.class)
	@Bean("Bill")
	public Person person1() {
		return new Person("Bill",66);
	}
    
	//如果是Linux环境,则注册Bean
	@Conditional(Linux.class)
	@Bean("Tom")
	public Person person2() {
		return new Person("Tom",66);
	}
}

判断条件需要实现Condition接口:

//判断是否是Windows系统
public class WindowsCondition implements Condition {
	/**
	 * conditionContext:判断条件能使用的上下文(环境)
     * annotatedTypeMetadata:注释信息
	 */
	@Override
	public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        //获取环境
        Environment environment = conditionContext.getEnvironment();
		// 获取操作系统的名称
		String osName = environment.getProperty("os.name");
        
		if (osName.contains("Windows")) {
			//匹配成功            
			return true;
		}
		return false;
	}
}
//判断是否是Linux系统
public class Linux implements Condition{
	@Override
	public boolean matches(ConditionContext conditioncontext, AnnotatedTypeMetadata annotatedtypemetadata) {
        //获取环境
		Environment environment = conditioncontext.getEnvironment();
        //获取操作系统的名称
		String osName = environment.getProperty("os.name");
		
		if(osName.contains("Linux")) {
            //匹配成功  
			return true;
		}
		return false;
	}
}

ConditionContext类有以下方法:

// 1.获取IOC使用的beanFactory
ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
// 2.获取类加载器
ClassLoader classLoader = conditionContext.getClassLoader();
// 3.获取当前环境信息
Environment environment = conditionContext.getEnvironment();
// 4.获取bean定义的注册类
BeanDefinitionRegistry registry = conditionContext.getRegistry();
// 判断容器中bean注册情况,也可以给容器注册一个bean
boolean containsBeanDefinition = registry.containsBeanDefinition("小红");
public class MainTest {
	public static void main(String[] args) {
        //获取IOC容器
		ApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class);
		// 获取环境信息
		Environment environment = ac.getEnvironment();
        //获取操作系统的名称
		String name = environment.getProperty("os.name");
		System.out.println(name);

		// 打印容器中注册的Bean名字
		String[] beanNamesForType = ac.getBeanNamesForType(Person.class);
		for (String string : beanNamesForType) {
			System.out.println(string);
		}
		// 获取容器中所有满足Person类型的bean
		Map<String, Person> person = ac.getBeansOfType(Person.class);
		System.out.println(person);
	}
}
//output
/*
Windows 10  //当前操作系统类型
小红         //没有判断条件,直接注册  
Bill        //满足windows操作系统的条件,被注册到IOC容器中
{小红=Person(name=小红, age=19), Bill=Person(name=Bill, age=66)}
*/

补充:@Conditional 贴到类上,表示满足当前条件,该配置类才能生效

二、组件生命周期

IOC容器负责管理bean的生命周期,bean的生命周期:创建——初始化——销毁 。

  • 创建

    • 单实例:在容器启动时创建对象
    • 多实例:在每次获取bean的时候创建对象
  • 初始化

    • 对象创建完成,并赋值好,调用初始化方法
  • 销毁

    • 单实例:容器关闭的时候调用销毁方法

    • 多实例:容器不管理这个Bean,所以不会调用销毁方法

1、@Bean 【指定初始化和销毁方法】

通过@Bean的属性init-method和destory-method,来指定初始化和销毁方法

  • init-method:bean生命周期初始化方法,对象创建后就进行调用
  • destroy-method:容器被销毁的时候,如果bean被容器管理,会调用该方法。
1.1 单实例bean指定初始化和销毁方法

配置类:

@Configuration//这是一个配置类
public class MainConfigOfLifeCycle {
	@Bean(initMethod="init",destroyMethod="destory")
	public Car car() {
		return new Car();
	}
}

测试:

public class IOCTest_LifeCycle {
	@Test
	public void test1() throws Exception {
		//获取IOC容器
		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
		System.out.println("容器创建完成...");
		//关闭容器
		ac.close();
	}
}

单实例结果:

img
1.2 多实例bean指定初始化和销毁方法

配置类:

@Configuration//这是一个配置类
public class MainConfigOfLifeCycle {
	@Scope("prototype")  //多例
	@Bean(initMethod="init",destroyMethod="destory")
	public Car car() {
		return new Car();
	}
}

当IOC容器创建好没有获取bean对象时,是不会创建对象的,也不会调用初始化方法

img

当获取bean的时候,就会创建对象,调用初始化方法。容器只负责创建和初始化,它并不会被spring容器管理。销毁动作交给用户自己调用。

public class IOCTest_LifeCycle {
	@Test
	public void test1() throws Exception {
		//1.创建IOC容器
		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
		System.out.println("容器创建完成...");
		//获取bean
		 ac.getBean("car");
		//关闭容器
		ac.close();
	}
}

结果:

img

2、实现InitializingBean接口和DisposableBean接口

@Component
//InitializingBean接口,定义初始化逻辑
//DisposableBean接口,定义销毁逻辑
public class Car implements InitializingBean, DisposableBean {
	public Car() {
		System.out.println("Car...construct");
	}
	//销毁
	@Override
	public void destroy() throws Exception {
		System.out.println("Car.destroy()");
		
	}
	//初始化
	@Override
	public void afterPropertiesSet() throws Exception {
		System.out.println("Car.afterPropertiesSet()");
	}
}

配置类

@Configuration//这是一个配置类
@ComponentScan("com.zxc.bean")  //扫描该包下的,贴有@Component的组件Car
public class MainConfigOfLifeCycle {
}

测试

public class IOCTest_LifeCycle {
	@Test
	public void test1() throws Exception {
		//1.创建IOC容器
		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
		System.out.println("容器创建完成...");
		//关闭容器
		ac.close();
	}
}

结果

img

3、@PostConstruct&@PreDestory

JSR250的两个注解:

  • @PostConstruct:在bean创建完成,构造器初始化后,执行贴有该注解的方法,进行初始化。

  • @PreDestory:在容器销毁bean之前,执行贴有该注解的方法,进行清理。

组件:

public class Dog {
	public Dog() {
		System.out.println("Dog...construct");
	}
	
	@PostConstruct//在创建对象,构造器后调用
	public void init() {
		System.out.println("Dog.init()");
	}
	
	@PreDestroy //在销毁之前调用
	public void destory() {
		System.out.println("Dog.destory()");
	}
}

配置类:

@Configuration//这是一个配置类
@Import(Dog.class)
public class MainConfigOfLifeCycle {
	
}

结果:

img

4、实现BeanPostProcessor接口【bean的后置处理器】

在bean初始化前后完成一些处理工作

  • postProcessBeforeInitialization方法:在初始化方法执行之前进行调用

  • postProcessAfterInitialization方法: 在初始化方法执行之后进行调用

//后置处理器,初始化方法前后调用
public class MyBeanPostProcessor implements BeanPostProcessor {
	
    @Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("postProcessBeforeInitialization()..."+beanName+"-->"+bean);
		return bean;
	}
    
	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("postProcessAfterInitialization()..."+beanName+"-->"+bean);
		return bean;
	}
}

配置类:

@Configuration//这是一个配置类
@Import({Dog.class,MyBeanPostProcessor.class})
public class MainConfigOfLifeCycle {
	
}

结果:

postProcessBeforeInitialization()...org.springframework.context.event.internalEventListenerProcessor-->org.springframework.context.event.EventListenerMethodProcessor@7181ae3f
postProcessAfterInitialization()...org.springframework.context.event.internalEventListenerProcessor-->org.springframework.context.event.EventListenerMethodProcessor@7181ae3f
postProcessBeforeInitialization()...org.springframework.context.event.internalEventListenerFactory-->org.springframework.context.event.DefaultEventListenerFactory@6e2c9341
postProcessAfterInitialization()...org.springframework.context.event.internalEventListenerFactory-->org.springframework.context.event.DefaultEventListenerFactory@6e2c9341
postProcessBeforeInitialization()...mainConfigOfLifeCycle-->com.zxc.config.MainConfigOfLifeCycle$$EnhancerBySpringCGLIB$$600b1690@32464a14
postProcessAfterInitialization()...mainConfigOfLifeCycle-->com.zxc.config.MainConfigOfLifeCycle$$EnhancerBySpringCGLIB$$600b1690@32464a14
Dog...construct
postProcessBeforeInitialization()...com.zxc.bean.Dog-->com.zxc.bean.Dog@76a4d6c
Dog.init()
postProcessAfterInitialization()...com.zxc.bean.Dog-->com.zxc.bean.Dog@76a4d6c
容器创建完成...
Dog.destory()
BeanPostProcessor原理
//AbstractAutowireCapableBeanFactory:
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd)
{
    Object wrappedBean = bean;
    if(mbd == null || !mbd.isSynthetic())
        //applyBeanPostProcessorsBeforeInitialization
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    try
    {
        invokeInitMethods(beanName, wrappedBean, mbd); //调用初始化方法
    }

    if(mbd == null || !mbd.isSynthetic())
        //applyBeanPostProcessorsAfterInitialization
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    return wrappedBean;
}

进入applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName)方法,遍历得到所有的后置处理器,逐个执行postProcessBeforeInitialization方法,一旦返回null,则跳出循环。

public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
    throws BeansException
{
    Object result = existingBean;
    //迭代所有的后置处理器
    for(Iterator iterator = getBeanPostProcessors().iterator(); iterator.hasNext();)
    {
        BeanPostProcessor beanProcessor = (BeanPostProcessor)iterator.next();
        //执行postProcessBeforeInitialization方法
        result = beanProcessor.postProcessBeforeInitialization(result, beanName);
        if(result == null)
            return result;
    }

    return result;
}
20190304094347995

Spring底层对BeanPostProcessor的使用:Bean赋值、注入其他组件、@Autowired、生命周期注解功能、@Async等等都使用到了BeanPostProcessor这个接口的实现类

三、组件属性赋值

@Value注解,进行属性赋值的三种方式:

  1. 基本数值赋值

  2. SpEL赋值,#{}

@Data@AllArgsConstructor@NoArgsConstructor
public class Person {
	
	@Value("小红")         //第一种方式,直接赋值
	private String name;
	@Value("#{20-2}")       //第二种方式,SqEL赋值
	private int age;
	@Value("${person.nickNamee}")   //第三种方式,${}取配置文件中的值
	private String nickName;
}
  1. ${},取出配置文件中的值 (在运行环境变量里面的值)

配置类:

使用@PropertySource读取外部配置文件中的k/v,保存到运行环境变量中。加载完外部配置文件后就可以用${}取值

//@PropertySource 读取外部配置文件
@PropertySource(value = { "classpath:person.properties" })//可以写多个配置文件
@Configuration//这是一个配置类==配置文件
public class ManConfigOfProperty {
    
    //只有在容器中的组件,才能将读取到的配置注入
	@Bean
	public Person person() {
		return new Person();
	}
}

测试

@Test
public void test1() throws Exception {
    //1.创建IOC容器
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ManConfigOfProperty.class);
    System.out.println("容器创建完成...");
    //根据类型获取bean
    Person bean = ac.getBean(Person.class);
    System.out.println(bean);
    System.out.println("=====================");
    ConfigurableEnvironment environment = ac.getEnvironment();
    String property = environment.getProperty("person.nickNamee");
    System.out.println(property);

}
//output
/*
容器创建完成...
//!!!name是通过第一种方式赋值,age是通过SpEL表达式赋值,nickName是通过第三种方式${},取出配置文件中的值
Person(name=小红, age=18, nickName=小张三)   
=====================
小张三
*/

如果是使用配置文件加载外部属性配置,如下:

<!--将外部属性配置文件加载进运行环境-->
<context:property-placeholder location="classpath:person.properties"/>
<!--通过spring的xml配置文件,将组件注册到IOC容器-->
<bean id="person" class="com.zxc.bean.Person">
    <property name="name" value="小红"/>
    <property name="age" value="18"/>
    <property name="nickName" value="${person.nickNamee}"/>
</bean> 

四、组件自动装配

自动装配:spring利用依赖注入(DI),来对IOC容器中各个组件的依赖关系进行赋值。

1、@Autowired 自动注入 【Spring定义的注解】

  1. 首先默认按照依赖对象的类型,去容器中找对应的组件。
  2. 如果找到多个相同类型的组件, 则将属性名作为组件的id去找。
  3. 使用@Qualifier可以明确指定需要装配组件的id ,而不是使用属性名。
  4. 默认情况下@Autowired必须要能找到对应的对象,否则报错。不过,可使用required=false来避免该问题:@Autowired(required=false)
//报错异常
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.zxc.service.EmployeeService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: 
  1. @Primary,让Spring进行自动装配的时候,默认使用贴了该注解的bean。也可以继续使用@Qualifier指定要装配的bean的id。

service:

@Service
@Getter@Setter
public class EmployeeService {
	private int label = 1;
	@Override
	public String toString() {
		return "EmployeeService [label=" + label + "]";
	}
}

controller:

@Controller
public class EmployeeController {
	@Qualifier("employeeService2")
	@Autowired
	private EmployeeService employeeService;

	@Override
	public String toString() {
		return "EmployeeController [employeeService=" + employeeService + "]";
	}
}

配置类:

@Configuration
@ComponentScan({"com.zxc.service","com.zxc.controller"})
public class MainConfigOfAutowired {
	
	@Bean("employeeService2")
	public EmployeeService employeeService() {
		EmployeeService employeeService = new EmployeeService();
		employeeService.setLabel(2);
		return employeeService;
	}
}

测试

@Test
public void test1() throws Exception {
	//创建IOC容器
	AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MainConfigOfAutowired.class);
	System.out.println("容器创建完成...");
    //EmployeeController里的employeeService字段,被自动装配
	EmployeeController bean = (EmployeeController) ac.getBean("employeeController");
	System.out.println(bean);
    //根据id,获取IOC容器里的组件
	Object bean2 = ac.getBean("employeeService");
	System.out.println(bean2);
	
}
//output
/*
容器创建完成...
//使用了@Qualifier指定了注入的对象
EmployeeController [employeeService=EmployeeService [label=2]]
EmployeeService [label=1]
*/

总结:

@Autowired
private EmployeeService employeeService;

1、首先按照类型EmployeeService去IOC容器中查找,但是此时IOC容器里有两个该类型的组件,其id分别为employeeService和employeeService2。
2、其次按照id去IOC容器种查找,这里默认将属性名employeeService作为id。
3、如果我们想要明确指定组件的id,可以使用@Qualifier("employeeService2")注解来指定。

@Autowired除了可以贴在属性上,还可以贴在构造器,参数,方法上,都是从IOC容器中自动获取组件。

  • 标注在构造器上

    @Autowired //自动从IOC容器中获取car组件进行装配
    public Boss(Car car){
    	this.car = car;
    }
    
  • 标注在方法上

    @Autowired //自动从IOC容器中获取car组件进行装配
    public void setter(Car car){
    	this.car = car;
    }
    
    //@Bean+方法参数,参数从容器自动注入(默认不写,也可以自动装配)
    @Bean
    public Color color(@Autowired Car car){
        Color color = new Color();
        color.setCar(car);
        return color;
    }
    
  • 标注在参数上

     //自动从IOC容器中获取car组件进行装配
    public Boss(@Autowired Car car){
    	this.car = car;
    }
     //自动从IOC容器中获取car组件进行装配
    public void setter(@Autowired Car car){
    	this.car = car;
    }
    

2、@Resource(JSR250)和 @Inject(JSR330) 【java规范的注解】

@Resource

@Autowired一样可以实现自动装配,但是默认将属性名作为组件的id进行自动装配。不支持@Primary@Autowired(required=false)功能。

@Inject

需要导入javax.inject的包,才能使用该注解。和@Autowired一样可以实现自动装配,但是没有required=false的功能 。

AutowiredAnnotationBeanPostProcessor:该后置处理器解析完成自动装配功能。

3、自动装配Spring底层组件

自定义组件想要使用Spring容器底层的一些组件(比如ApplicationContextBeanFactory等等)。可以使自定义组件实现xxxAware,在创建对象的时候会spring会自动调用自定义组件实现接口里规定的方法,注入相关的组件。xxxAware的功能是通过xxxProcessor来实现的。比如ApplicationContextAware是通过ApplicationContextAwareProcessor(后置处理器)来实现的。

image-20210513202001215

比如下面的方法,在Red组件被添加到容器时,因其实现了ApplicationContextAware接口。通过ApplicationContextAwareProcessor后置处理器调用该接口setApplicationContext(ApplicationContext applicationcontext)方法,将容器中的组件注入。

//实现几个常见的Aware接口
@Component//被扫描进IOC容器
public class Red implements ApplicationContextAware,BeanNameAware,EmbeddedValueResolverAware{

	@Override  //Embedded 植入的,深入的,内含的
	public void setEmbeddedValueResolver(StringValueResolver stringvalueresolver) {
		String stringValue = stringvalueresolver.resolveStringValue("解析字符串"+"${os.name} "+"#{10*10}");
		System.out.println(stringValue);
	}

	@Override
	public void setBeanName(String s) {
		System.out.println("当前Bean的名字:"+s);
	}
	
	@Override
	public void setApplicationContext(ApplicationContext applicationcontext) throws BeansException {
		System.out.println("传入的IOC容器:"+applicationcontext);
	}

}

测试结果:

当前Bean的名字:com.zxc.bean.Red
解析字符串Windows 10 100
传入的IOC容器:org.springframework.context.annotation.AnnotationConfigApplicationContext@11028347: startup date [Mon Mar 04 20:16:52 CST 2019]; root of context hierarchy
ApplicationContextAwareProcessor后置处理器的原理:

Debug定位到ApplicationContextAwareProcessor.postProcessBeforeInitialization()方法

public Object postProcessBeforeInitialization(final Object bean, String beanName)
    throws BeansException{
    AccessControlContext acc = null;
    if(System.getSecurityManager() != null && ((bean instanceof EnvironmentAware) || (bean instanceof EmbeddedValueResolverAware) || (bean instanceof ResourceLoaderAware) || (bean instanceof ApplicationEventPublisherAware) || (bean instanceof MessageSourceAware) || (bean instanceof ApplicationContextAware)))
        acc = applicationContext.getBeanFactory().getAccessControlContext();
    if(acc != null)
        AccessController.doPrivileged(new PrivilegedAction() {
            public Object run(){
                invokeAwareInterfaces(bean);
                return null;
            }

            final Object val$bean;
            final ApplicationContextAwareProcessor this$0; {
                this.this$0 = ApplicationContextAwareProcessor.this;
                bean = obj;
                super();
            }
        }, acc);
    else
        invokeAwareInterfaces(bean);
    return bean;
}

调用下面方法进行判断该bean实现了xxxAware接口,然后调用方法将组件注入。

private void invokeAwareInterfaces(Object bean){
    //是否实现了Aware接口
    if(bean instanceof Aware){
        //是否实现了EnvironmentAware接口
        if(bean instanceof EnvironmentAware)
            ((EnvironmentAware)bean).setEnvironment(applicationContext.getEnvironment());
        //是否实现了EmbeddedValueResolverAware接口
        if(bean instanceof EmbeddedValueResolverAware)
            ((EmbeddedValueResolverAware)bean).setEmbeddedValueResolver(embeddedValueResolver);
         //是否实现了ResourceLoaderAware接口
        if(bean instanceof ResourceLoaderAware)
            ((ResourceLoaderAware)bean).setResourceLoader(applicationContext);
        //是否实现了ApplicationEventPublisherAware接口
        if(bean instanceof ApplicationEventPublisherAware)
            ((ApplicationEventPublisherAware)bean).setApplicationEventPublisher(applicationContext);
      	//是否实现了MessageSourceAware接口
        if(bean instanceof MessageSourceAware)
            ((MessageSourceAware)bean).setMessageSource(applicationContext);
        //是否实现了ApplicationContextAware接口
        if(bean instanceof ApplicationContextAware)
            ((ApplicationContextAware)bean).setApplicationContext(applicationContext);
    }
}

4、@Profile 【指定环境】

profile:英文意思是外形,轮廓 。在计算机中可以理解为预环境

Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能

  • 贴了@Profile的bean,只有这个环境被激活才能注册到容器。default是默认环境。没贴,任何环境都可以注册这个bean
  • 贴在配置类上,只有是指定环境的时候,整个配置类里面的所有配置才能生效

二种方式指定(激活)环境:

  • 使用命令动态参数激活:虚拟机参数位置加载 -Dspring.profiles.active=test
  • 使用代码激活环境

例子:根据不同环境,动态切换激活数据源。

开发环境,测试环境,生产环境

数据源:(/A)(/B)(/C)

配置类:

@Configuration // 配置类==配置文件
@PropertySource("classpath:jdbc.properties")
public class MainConfig_Profile  {

	@Value("${jdbc.username}")
	private String userName;

	@Value("${jdbc.password}")
	private String password;

	@Value("${jdbc.driverClassName}")
	private String driverClassName;
	
	@Value("${jdbc.url}")
	private String url;

	// 测试环境下的数据源
	@Profile("test")
	@Bean("testDateSource")
	public DataSource DataSourceTest() {
		DruidDataSource ds = new DruidDataSource();
		ds.setDriverClassName(driverClassName);
		ds.setUrl(url);
		ds.setPassword(password);
		ds.setUsername(userName);
		return ds;
	}

	// 开发环境下的数据源
	@Profile("dev")
	@Bean("devDateSource")
	public DataSource DataSourceDevelopMent() {
		DruidDataSource ds = new DruidDataSource();
		ds.setDriverClassName(driverClassName);
		ds.setUrl(url);
		ds.setPassword(password);
		ds.setUsername(userName);
		return ds;
	}
}

数据源jdbc.properties配置文件:

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///test
jdbc.username=root
jdbc.password=admin

测试:

@Test
public void test1() throws Exception {
	// 1.使用无参构造器创建一个applicationContext。使用有参的话,配置类被加载,容器被刷新,环境还没设置。
		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
		// 2.设置需要激活的环境
		ac.getEnvironment().setActiveProfiles("test");
		// 3.注册配置类
		ac.register(MainConfig_Profile.class);
		// 4. 启动刷新容器
		ac.refresh();
		// 获取容器中的bean名称打印
		String[] definitionNames = ac.getBeanDefinitionNames();
		for (String string : definitionNames) {
			System.out.println(string);
		}
}
//output
/*
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig_Profile
testDateSource
*/

五、AOP

1、简介

**AOP概念:**AOP是指在程序运行期间,动态的将某段代码切入到指定方法位置,进行运行的编程方式。

AOP的目的:AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,

便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

AOP的优势:降低模块的耦合度、使系统容易扩展、更好的代码复用性。

AOP的原理:

  • 静态代理:事先写好代理类,缺点是每个业务类度需要一个代理类,不灵活。

  • 动态代理:运行时动态生成代理类。缺点生成代理类和调用方法需要额外花费时间。

    • JDK动态代理:基于Java反射机制,必须要实现接口的业务类才能用这种方法生成代理类。
    • cglib动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。

AOP当中的概念:

  • 切入点(Pointcut):在哪些类,哪些方法上切入(where)。
    • execution(<访问修饰符>? <返回类型> <声明类型>? <方法名>(<参数>) <异常>?)
  • 增强(Advice):早期翻译为通知,什么时机执行什么方法(when:方法前/方法后/方法前后,what:增强的功能)。
    • 前置通知(@Before):在目标方法运行之前运行
    • 后置通知(@After):在目标方法运行结束后运行。(无论方法正常还是异常)
    • 返回通知(@AfterReturning):在目标方法正常返回之后运行
    • 异常通知(@@AfterThrowing):在目标方法出现异常之后运行
    • 环绕通知(@Around):动态代理,手动推进目标方法执行(joinPoint.procced())
  • 切面(Aspect):切面=切入点+通知,(什么地点,什么时机,做什么)。
  • 织入(Weaving):把切面加入到对象,并创建出代理对象的过程。(该过程由Spring来完成)。
image-20210514152552336

2、实践

1、导入AOP模块:spring-aspects依赖

2、定义一个业务逻辑类(MathCaculator),在业务逻辑运行时将日志打印(方法前,方法后,方法出现异常)

@Component
public class MathCaculator {
	
	public int calculator(int i, int j) {
		System.out.println("MathCaculator.calculator()");
		return i/j;
	}
}

3、定义一个日志切面类(LogAspects),切面类的方法需要动态感知MathCaculator里的方法运行到哪里了,然后执行。

因为IOC容器中有很多组件,所以要通过@Aspect注解标注当前类的切面类。然后通过@Pointcut切入表达式表明在哪些包,哪些类,哪些方法上进行切入。最后用相关通知注解表明在何时执行。

@Aspect//必须告诉Spring,当前类是切面类
public class LogAspects {
    //公共切入点表达式
	@Pointcut("execution(public int  com.zxc.aop.MathCaculator.calculator(..))")
	public void pointCut() {}
	
	//前置通知
	@Before("pointCut()")
	public void logStart(JoinPoint joinPoint) {
		Object[] args = joinPoint.getArgs();
		System.out.println("前置通知@Before.."+joinPoint.getSignature().getName()+"方法参数{"+args+"}");
	}
	//后置通知,出现异常也会调用
	@After("pointCut()")
	public void logEnd() {
		System.out.println("后置通知@After..");
	}

	//返回通知
	@AfterReturning(value="pointCut()",returning="obj")
	public void logReturn(Object obj) {
		System.out.println("返回通知@AfterReturning.."+"LogAspects.logReturn()..返回值:"+obj);
	}
	
	//异常通知
	//注意JoinPoint一定要出现在 参数表第一位,不然异常
	@AfterThrowing(value="pointCut()",throwing="e")
	public void logException(JoinPoint joinPoint, Exception e) {
		System.out.println("异常通知@AfterThrowing.."+joinPoint.getSignature().getName()+"..LogAspects.logException()..异常信息"+e);
	}
}

4、将切面类和业务逻辑类(目标方法所在类)都加入到容器中;并给配置类加 @EnableAspectJAutoProxy注解,开启基于注解的AOP模式

@Configuration
@EnableAspectJAutoProxy//开启基于注解的AOP模式
public class MainConfigOfAop {
	@Bean
	public MathCaculator mathCal() {
		return new MathCaculator();
	}
	@Bean
	public LogAspects logAspects() {
		return new LogAspects();
	}
}

5、测试

@Test
public void test1() throws Exception {
    //获取IOC容器
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MainConfigOfAop.class);
    //从容器中获取MathCaculator类型的bean
    MathCaculator bean = ac.getBean(MathCaculator.class);
    //执行方法
    bean.calculator(2, 0);
}
//output
/*
前置通知@Before..calculator方法参数{[Ljava.lang.Object;@63070bab}
MathCaculator.calculator()
后置通知@After..
异常通知@AfterThrowing..calculator..LogAspects.logException()..异常信息java.lang.ArithmeticException: / by zero
*/

如果用xml配置文件,开启基于注解的AOP模式用下面代码

<aop:aspectj-autoproxy/>

3、开启基于注解的AOP模式原理

@EnableAspectJAutoProxy是什么? 看给容器注册了什么组件,这个组组件什么时候工作,这个组件的功能

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class) // ==>看这里
public @interface EnableAspectJAutoProxy {

	/**
	 * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
	 * to standard Java interface-based proxies. The default is {@code false}.
	 */
	boolean proxyTargetClass() default false;

	/**
	 * Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
	 * for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
	 * Off by default, i.e. no guarantees that {@code AopContext} access will work.
	 * @since 4.3.1
	 */
	boolean exposeProxy() default false;

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-blU53XxV-1621235618438)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

1.@Import(AspectJAutoProxyRegistrar.class),给容器导入

AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar

2.利用AspectJAutoProxyRegistrar 自定义给容器注册bean;

internalAutoProxyCreator=AnnotationAwareAspectJAutoProxyCreator

给容器注册一个 AnnotationAwareAspectJAutoProxyCreator

3.AnnotationAwareAspectJAutoProxyCreator

-> AspectJAwareAdvisorAutoProxyCreator

​ -> AbstractAdvisorAutoProxyCreator

​ ->AbstractAutoProxyCreator

​ implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware

​ 关注后置处理器(在bean初始化完成前后做事情),自动装配BeanFactory

AbstractAutoProxyCreator.setBeanFactory();

AbstractAutoProxyCreator.后置处理器逻辑

AbstractAdvisorAutoProxyCreator.setBeanFactory(); -->initBeanFactory()

AnnotationAwareAspectJAutoProxyCreator.initBeanFactory()

流程

1.传入主配置类,创建ioc容器

2.注册配置类,调用refresh(),刷新容器

3.// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);; 注册bean的后置处理器来方便拦截bean的创建

1.先获取ioc容器中已经定义了的需要创建对象的所有BeanPostProcessor

2.给容器中加别的BeanPostProcessor

3.优先注册实现PriorityOrdered接口的BeanPostProcessor

4.再给容器中注册实现了Ordered接口的BeanPostProcessor

5.注册没实现优先级接口的BeanPostProcessor

6.注册BeanPostProcessor,实际就是创建BeanPostProcessor对象,保存再容器中

​ 创建 internalAutoProxyCreator的BeanPostProcessor【AnnotationAwareAspectJAutoProxyCreator 】

看不懂,未完待续。。。。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值