Spring注解驱动开发

Spring注解驱动开发

1.传统的spring的xml配置方式

<!--pom.xml文件中的依赖信息-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.0.RELEASE</version>
</dependency>

1.新建一个Person类

public class Person {
	private String name;
	private int age;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
}

2.新建一个spring的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">

	<!-- 传统的方式创建bean并加入到ioc容器中 -->
	<bean id="user" class="com.spring.annotation.beans.Person">
        <!--为该类的实例设置属性-->
		<property name="name" value="张三"></property>
		<property name="age" value="18"></property>
	</bean>
</beans>

3.新建一个测试类

public class TestMain {

	public static void main(String[] args) {
		// 获取配置文件
		ApplicationContext act =  new ClassPathXmlApplicationContext("beans.xml");
		// 通过id获取容器中的bean
		Person user = (Person) act.getBean("user");
		System.out.println(user);
	}
}
输出内容:
Person [name=张三, age=18]

上面一个基于xml的spring的配置就完成了

2.通过@Configuration和@Bean实现注入bean对象

1.新建一个类为配置类来代替xml文件

@Configuration //表示这是一个配置类,等同于一个xml文件
public class MainConfig {
	
	@Bean //等同于bean标签,像spring容器中注册一个bean对象,bean对象的id就是方法的名
	public Person user() {
		Person user = new Person();
		user.setName("李四");
		user.setAge(13);
		return user;
	}

}

2.创建一个测试方法

public class AnnotationTestMain {

	public static void main(String[] args) {
		//加载配置,从配置类中加载,得到一个ioc容器对象
		ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
		//从容器 中通过id获取对应的bean对象
		Person user = (Person) applicationContext.getBean("user");
		System.out.println(user);
	}
	
}
控制台输出:
Person [name=李四, age=13]

3.运行测试类,检查一下对应的一些信息

public class AnnotationTestMain {

	public static void main(String[] args) {
		//加载配置,从配置类中加载,得到一个ioc容器对象
		ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
		//从容器 中通过id获取对应的bean对象
		Person user = (Person) applicationContext.getBean("user");
		System.out.println(user);
		//获取id为user的bean的类类型
		Class<?> type = applicationContext.getType("user");
		System.out.println(type); //Person
		//获取Person这个类在ioc容器中存在的实例bean的id(bean的名字)
		String[] namesForType = applicationContext.getBeanNamesForType(Person.class);
		for (String name: namesForType) {
			System.out.println(name); //user
		}
	}
	
}

4.通过@Bean的value属性指定bean的名字

@Configuration //表示这是一个配置类,等同于一个xml文件
public class MainConfig {
	
	@Bean //等同于bean标签,像spring容器中注册一个bean对象,bean对象的id就是方法的名
	public Person user() {
		Person user = new Person();
		user.setName("李四");
		user.setAge(13);
		return user;
	}

	// 通过@Bean的value属性指定注入到ioc容器中的bean的id(名字)
	@Bean(value = "wangwu")
	public Person createUser() {
		Person person = new Person();
		person.setName("王五");
		person.setAge(12);
		return person;
	}
}

5.运行测试类

public class AnnotationTestMain {

	public static void main(String[] args) {
		//加载配置,从配置类中加载,得到一个ioc容器对象
		ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
		//从容器 中通过id获取对应的bean对象
		Person user = (Person) applicationContext.getBean("user");
		System.out.println(user);
		//获取id为user的bean的类类型
		Class<?> type = applicationContext.getType("user");
		System.out.println(type); //Person
		//获取Person这个类在ioc容器中存在的实例bean的id(bean的名字)
		String[] namesForType = applicationContext.getBeanNamesForType(Person.class);
		for (String name: namesForType) {
			System.out.println(name); //user wangwu
            //注意,此时容器中有两个Person对应的bean对象
            //id分别为user和wangwu
		}
	}
	
}

3.使用@ComponentScan扫描对应的Bean对象

1.创建如下所示的类和包结构

在这里插入图片描述

2.分别为HelloController、HelloService、HelloDao添加@Controller、@Service、@Repository注解

@Service
public class HelloService {

}

@Repository
public class HelloDao {

}

@Controller
public class HelloController {

}

3.新建一个配置类

// @Configuration 标记这是一个配置类
@Configuration
// 组件扫描,配置扫描路径
@ComponentScan(value = {"com.spring.annotation.controller", "com.spring.annotation.service","com.spring.annotation.dao"})
public class ComponentScanConfig {
	
}


4.新建一个junit测试方法

<!--pom.xml新增依赖-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
/**
	 * junit测试
	 */
	@Test
	public void test01() {
		// 加载配置类,获取spring容器
		ApplicationContext app = new AnnotationConfigApplicationContext(ComponentScanConfig.class);
        // 获取窗口中所有bean对象的名字
		String[] names = app.getBeanDefinitionNames();
		for (String name : names) {
            // 遍历输出所有bean对象的名字
			System.out.println(name);
		}
		
	}

4.@ComponentScan的一些常规用法

1.新建一个配置类

// @Configuration标识这是一个配置类
@Configuration
// 配置包扫描 
// value = {"com.spring.annotation.*"} 配置扫描路径是com.spring.annotation.*
// useDefaultFilters 默认为true 扫描路径组件全加载,这里设置为false,不需要全加载,按自定义的过滤规则加载即可
// includeFilters 设置包含过滤规则 即配置要加载的组件
// @Filter(type=FilterType.ANNOTATION, classes = {Controller.class, Service.class} 通过注解加载,加载@Controller,@Service标识的类
@ComponentScan(value = {"com.spring.annotation.*"},useDefaultFilters = false,includeFilters = {
			@Filter(type=FilterType.ANNOTATION, classes = {Controller.class, Service.class}),
		}
)
public class ComponentScanOtherConfig {

}

2.新建一个测试方法

/**
	 * junit测试
	 */
	@Test
	public void test02() {
		//通过配置类得到spring容器
		ApplicationContext act = new AnnotationConfigApplicationContext(ComponentScanOtherConfig.class);
		//从容器中获取bean的名字
		String[] names = act.getBeanDefinitionNames();
		for (String name: names) {
			//遍历输出每一个名字
			System.out.println(name);
		}
	}
	
控制台输出结果:
componentScanOtherConfig //这是配置类本身,也在spring容器中
helloController //这两个是配置的只扫描的两个注解所在的类
helloService

###3.上面这种形式在xml中的配置如下

**1.新建一个componentscan.xml文件 **

在这里插入图片描述

2.在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"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">


	<!-- 配置包扫描,base-package="com.spring.annotation 配置扫描路径 use-default-filters="false" 配置默认扫描规则-->
	<context:component-scan base-package="com.spring.annotation" use-default-filters="false">
		<!-- 配置只扫描包 -->
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
	</context:component-scan>

</beans>


2.测试类中测试

/**
	 * juniti测试
	 */
	@Test
	public void test03() {
		ApplicationContext act = new ClassPathXmlApplicationContext("componentscan.xml");
		String[] names = act.getBeanDefinitionNames();
		for (String name: names) {
			System.out.println(name);
		}
	}
控制台输出:
helloController
helloService

5.使用@ComponentScans配置多个包扫描

1.新建一个配置类

@Configuration
// @ComponentScans配置多个包扫描
@ComponentScans({
	//其中一个包扫描
	@ComponentScan(value = "com.spring.annotation.controller"),
	//另一个包扫描,扫描了dao和service,其中配置了一个excludeFilters,并通过注解排除了@Repository这个注解
	@ComponentScan(value = {"com.spring.annotation.dao", "com.spring.annotation.service"}, excludeFilters = {
			@Filter(type = FilterType.ANNOTATION, classes = {Repository.class})
	})
})
public class ComponentScanAnotherConfig {

}
//useDefaultFilters默认扫描全部,这里不需要禁用

2.在测试类中测试

/**
	 * juniti测试
	 */
	@Test
	public void test04() {
		ApplicationContext act = new AnnotationConfigApplicationContext(ComponentScanAnotherConfig.class);
		String[] names = act.getBeanDefinitionNames();
		for (String name: names) {
			System.out.println(name);
		}
	}
	
控制台输出:(@Repository标注的类已经不在了)
componentScanAnotherConfig
helloController
helloService

1.以上在种形式在xml中的配置如下

1.在类路径下(/src/main/resources)新建一个componentscan-another.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"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

	<!-- 设置扫描三个包 base-package -->
	<context:component-scan base-package="com.spring.annotation.service,com.spring.annotation.Controller,com.spring.annotation.Controller">
		<!-- 排除包扫描 -->
		<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
	</context:component-scan>

</beans>

2.新建一个测试类测试

/**
	 * juniti测试
	 */
	@Test
	public void test05() {
		ApplicationContext act = new ClassPathXmlApplicationContext("componentscan-another.xml");
		String[] names = act.getBeanDefinitionNames();
		for (String name: names) {
			System.out.println(name);
		}
	}
控制台输出:
helloService
helloController

6.@Filter过滤规则

1.修改配置类ComponentScanAnotherConfig

@Configuration
@ComponentScans({
	@ComponentScan(value = "com.spring.annotation.controller"),
	@ComponentScan(value = {"com.spring.annotation.dao", "com.spring.annotation.service"}, excludeFilters = {
			//@Filter注解配置过滤规则,FilterType.ANNOTATION通过注解过滤
			@Filter(type = FilterType.ANNOTATION, classes = {Repository.class}),
			//@Filter注解配置过滤规则,FilterType.ASSIGNABLE_TYPE通过类型过滤
			@Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {HelloService.class})
	})
})
public class ComponentScanAnotherConfig {

}

2.测试类中测试

@Test
	public void test04() {
		ApplicationContext act = new AnnotationConfigApplicationContext(ComponentScanAnotherConfig.class);
		String[] names = act.getBeanDefinitionNames();
		for (String name: names) {
			System.out.println(name);
		}
	}
控制台输出:
helloController

3.@Filter的其他过滤规则

public enum FilterType {

	/**
	 * Filter candidates marked with a given annotation.
	 * @see org.springframework.core.type.filter.AnnotationTypeFilter
	 */
	ANNOTATION,

	/**
	 * Filter candidates assignable to a given type.
	 * @see org.springframework.core.type.filter.AssignableTypeFilter
	 */
	ASSIGNABLE_TYPE,

	/**
	 * Filter candidates matching a given AspectJ type pattern expression.
	 * @see org.springframework.core.type.filter.AspectJTypeFilter
	 */
	ASPECTJ,

	/**
	 * Filter candidates matching a given regex pattern.
	 * @see org.springframework.core.type.filter.RegexPatternTypeFilter
	 */
	REGEX,

	/** Filter candidates using a given custom
	 * {@link org.springframework.core.type.filter.TypeFilter} implementation.
	 * 通过实现TypeFilter实现自定义过滤规则
	 */
	CUSTOM

}
  • ANNOTATION 通过注解类型过滤
  • ASSIGNABLE_TYPE 通过类类型过滤
  • ASPECTJ 通过ASPECTJ 表达式过滤
  • REGEX 通过正则表达式过滤
  • CUSTOM 自定义过滤规则

4.实现自定义规则
1.新建一个类并实现TypeFilter接口

public class CustomFilter implements  TypeFilter {

	/**
	 * 实现这个match方法
	 * MetadataReader 表示当前要匹配的类(当前被扫描的类)
	 * MetadataReaderFactory 其他类信息,超类或接口
	 */
	public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
			throws IOException {
		
		// 获取注解相关信息
		AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
		System.err.println(annotationMetadata);
		System.err.println(annotationMetadata.getClassName());
		// 获取类相关信息
		ClassMetadata classMetadata = metadataReader.getClassMetadata();
		System.err.println(classMetadata);
		System.err.println(classMetadata.getClassName() + " ------------------<<<<<<<<<<<");
		System.err.println(classMetadata.getEnclosingClassName());
		System.err.println(classMetadata.getSuperClassName());
		System.err.println(classMetadata.getInterfaceNames());
		System.err.println(classMetadata.getMemberClassNames());
		// 获取资源相关信息
		Resource resource = metadataReader.getResource();
		System.err.println(resource.getClass());
		//这里就是自定义的匹配规则
		if (classMetadata.getClassName().contains("HelloService")) {
            //classMetadata.getClassName()这个方法获取的是全类名
			return true; //返回true表示匹配
		}
		
		return false; //返回false表示不匹配
	}

}

2.创建配置类

@Configuration
@ComponentScan(value = {"com.spring.annotation.controller","com.spring.annotation.service","com.spring.annotation.dao"}, useDefaultFilters = false, 
						includeFilters = {
				//使用自定义过滤规则,扫描的路径下的所有类都会来匹配
								@Filter(type = FilterType.CUSTOM, classes = CustomFilter.class)
						})
public class ComponentScanCustom {

}

3.测试类

@Test
	public void test06() {
		ApplicationContext act = new AnnotationConfigApplicationContext(ComponentScanCustom.class);
		String[] names = act.getBeanDefinitionNames();
		for (String name : names) {
			System.out.println(name);
		}
	}
	
控制台输出:
helloService

4.上面这种形式通过xml实现

1.新建一个spring配置文件

<context:component-scan base-package="com.spring.annotation.dao,com.spring.annotation.controller,com.spring.annotation.service"
		 use-default-filters="false"><!-- 注意这里的默认过滤规则是关闭的 -->
		 <!-- 指定type类型为自定义  并指定自定义的类 -->
		<context:include-filter type="custom" expression="com.spring.annotation.filter.CustomFilter"/>
	</context:component-scan>

2.在测试类中测试

@Test
	public void test08() {
		ApplicationContext act = new ClassPathXmlApplicationContext("componentscan-custom.xml");
		String[] names = act.getBeanDefinitionNames();
		for (String name: names) {
			System.out.println(name);
		}
	}
	
控制台输出结果:
helloService

7.通过@Scope设置作用域

1.在MainConfig.java中注册两个bean@Configuration //表示这是一个配置类,等同于一个xml文件
public class MainConfig {

@Configuration //表示这是一个配置类,等同于一个xml文件
public class MainConfig {
	
	@Bean //等同于bean标签,像spring容器中注册一个bean对象,bean对象的id就是方法的名
	public Person user() {
		Person user = new Person();
		user.setName("李四");
		user.setAge(13);
		return user;
	}

	// 通过@Bean的value属性指定注入到ioc容器中的bean的id(名字)
	@Bean(value = "wangwu")
	public Person createUser() {
		Person person = new Person();
		person.setName("王五");
		person.setAge(12);
		return person;
	}
	
	@Bean("xiaoming")
	public Person scope1() {
		Person person = new Person();
		person.setName("小明");
		person.setAge(18);
		System.out.println(person);
		return person;
	}
	
	/**
	 * @see ConfigurableBeanFactory#SCOPE_PROTOTYPE 多实例的
	 * @see ConfigurableBeanFactory#SCOPE_SINGLETON 单实例的
	 * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST request请求创建一次
	 * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION session一个session创建一次
	 * @return
	 */
	@Scope(value = "prototype")
	@Bean("xiaohong")
	public Person scope2() {
		Person person = new Person();
		person.setName("小红");
		person.setAge(17);
		System.out.println(person);
		return person;
	}
	
}

2.在测试类中测试

@Test
	public void test07() {
		ApplicationContext act = new AnnotationConfigApplicationContext(MainConfig.class);
		String[] names = act.getBeanDefinitionNames();
		for (String name: names) {
			System.out.println(name);
		}
	}
控制台输出
Person [name=小明, age=18]   --->> 单实例的在初始化容器时就创建
user
wangwu
xiaoming
xiaohong

3.在测试类中获取

@Test
	public void test07() {
		ApplicationContext act = new AnnotationConfigApplicationContext(MainConfig.class);
		String[] names = act.getBeanDefinitionNames();
		for (String name: names) {
			System.out.println(name);
		}
		Person p = (Person) act.getBean("xiaohong");
		
	}
Person [name=小明, age=18]
user
wangwu
xiaoming
xiaohong
Person [name=小红, age=17]    -->> 多实例的bean只要在被调用时被创建

8.Bean的懒加载@Lazy

1.在MainConfig.java中注册一个Bean

@Configuration //表示这是一个配置类,等同于一个xml文件
public class MainConfig {
	
    // 设置这个bean为懒加载,一般的bean是在初始化容器的时候被加载,而采用@Lazy懒加载的bean只会在第一次获取时被加载到容器
	@Lazy  //懒加载,
	@Bean
	public Person lazyBean() {
		Person lazy = new  Person();
		lazy.setAge(18);
		lazy.setName("lazy");
		// 向容器中注册bean的时候,会执行这个
		System.out.println(lazy);
		return lazy;
	}
	...
}

2.在测试类中测试

@Test
	public void testt09() {
		ApplicationContext act = new AnnotationConfigApplicationContext(MainConfig.class);
		//注释下面这句,只初始化容器,可以看到gean没有被加载到容器中,打开注释,从容器中获取bean,可以看到bean被创建
		//Person person = (Person) act.getBean("lazyBean");
	}

9.@Conditonal注解的使用

1.新建一个maven工程
2.pom.xm中的依赖

<dependencies>

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

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>

	</dependencies>

3.新建一个Person类

public class Person {

	private String name;
	private Integer age;
	
	public Person() {
		super();
	}
	public Person(String name, Integer age) {
		super();
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
	
}

4.Conditionall注解

// 这个注解能标注在类和方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

	/**
	 * All {@link Condition}s that must {@linkplain Condition#matches match}
	 * in order for the component to be registered.
	 */
    // value的值是Condition的子类
	Class<? extends Condition>[] value();

}

5.新建一个类MyCondition继承Condition


public class MyCondition implements Condition {

	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		Environment environment = context.getEnvironment();
		BeanDefinitionRegistry registry = context.getRegistry();
		ResourceLoader resourceLoader = context.getResourceLoader();
		System.out.println(environment.getProperty("os.name"));
        // 匹配规则,true即满足条件,false不满足条件
		if (environment.getProperty("os.name").contains("Windows")) {
			return true;
		}
		
		return false;
	}

}

6.新建一个配置类

@Configuration
public class BeanConfig {
	
	@Bean
	public Person boy() {
		return new Person("小明", 18);
	}
	
    // 使用@Condition注解,直接类使用自己定义的类
	@Conditional(value = MyCondition.class)
	@Bean
	public Person girl() {
		return new Person("小红", 17);
	}

}

测试

@Test
	public void test01() {
		ApplicationContext act = new AnnotationConfigApplicationContext(BeanConfig.class);
		String[] names = act.getBeanDefinitionNames();
		for (String name: names) {
			System.out.println(name);
		}
	}
控制台输出:
beanConfig
boy
girl

更改匹配条件后,会返现如果不满足匹配的条件的bean不会被注册到Spring容器中*

在方法test01上右键选择
在这里插入图片描述

在这里插入图片描述

在修改好配置参数后执行,可以发现不满足条件的bean没有被加载到spring容器中

控制台输出:
beanConfig
boy

@Condition注解可以标注在方法上也可以标注在类上,如果标注在类上的话,条件对类中的所有方法都起作用

10.@Import注解导入spring组件

1.新建一个配置类

@Configuration
// 使用@Import注解导入要注册到容器中的类的对象
@Import({ Person.class}) //这里并不需要对Person类做任何操作
public class ImportConfig {

}

2.测试

@Test
	public void test02() {
		ApplicationContext act = new AnnotationConfigApplicationContext(ImportConfig.class);
		String[] names = act.getBeanDefinitionNames();
		for (String name: names) { 
			System.out.println(name);
		}
		System.out.println(act.getBean(Person.class));
	}
	
控制台输出:
importConfig
com.spring.annotation.bean.Person
Person [name=null, age=null]   ---->> 默认为初始值

11.使用ImportSelector

1.新建一个实体类

public class Color {
	
	String name;

	public String getName() {
		return name;
	}

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

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

2.新建一个类,实现ImportSelector

public class MyImportSelector implements ImportSelector {

	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		//这里返回一个String数组,数组里是刚才新建的color类的全类名
		return new String[] {"com.spring.annotation.bean.Color"};
	}
}

3.修改配置类

@Configuration
// 使用@Import注解导入要注册到容器中的类的对象,数组接受自定义的MyImportSelector类
@Import({ Person.class, MyImportSelector.class})
public class ImportConfig {

}

4.测试

	@Test
	public void test02() {
		ApplicationContext act = new AnnotationConfigApplicationContext(ImportConfig.class);
		String[] names = act.getBeanDefinitionNames();
		for (String name: names) { 
			System.out.println(name);
		}
		System.out.println(act.getBean(Person.class));
	}
	
控制台输出:
importConfig
com.spring.annotation.bean.Person
com.spring.annotation.bean.Color   ---> 这个类被加载到了容器中
Person [name=null, age=null]

12.使用ImportBeanDefinitionRegistrar

1.新建一个类继承ImportBeanDefinitionRegistrar

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		if (registry.containsBeanDefinition("com.spring.annotation.bean.Color") ) {
			// 设置要注册到容器中的bean对象的类
			RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Color.class);
			// 设置要注册的bean对象的名字
			registry.registerBeanDefinition("lightCollor", rootBeanDefinition);
		}
	}
}

3.配置类中使用自定义的类

@Configuration
// 使用@Import注解导入要注册到容器中的类的对象,数组接受自定义的MyImportSelector类,接受自定义的MyImportBeanDefinitionRegistrar类
@Import({ Person.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class ImportConfig {

}

4.测试类中测试

@Test
	public void test02() {
		ApplicationContext act = new AnnotationConfigApplicationContext(ImportConfig.class);
		String[] names = act.getBeanDefinitionNames();
		for (String name: names) { 
			System.out.println(name );
		}
		System.out.println(act.getBean(Person.class));
	}
控制台输出结果:
importConfig
com.spring.annotation.bean.Person
com.spring.annotation.bean.Color
lightCollor   -->> 这个就是通过自定义的类来添加的组件
Person [name=null, age=null]

13使用FactoryBean注册组件

1.新建一个类FactoryBean的实现类

public class MyBeanFactory implements FactoryBean<Color>{

	/**
	 * 返回值作为bean被注册到容器中
	 */
	public Color getObject() throws Exception {
		Color color = new Color();
		return color;
	}

	public Class<?> getObjectType() {
		
		return Color.class;
	}
	
    //是否是单例的,返回为true的话就是单例的,返回为false的话返回是多例的
	public boolean isSingleton() {
		return true;
	}

}

2.注册FactoryBean实现类到容器中

@Configuration
public class FactoryBeanConfig {
	
	@Bean
	public MyBeanFactory createColorBean() {
		return new MyBeanFactory();
	}

}

3.测试

@Test
	public void test03() {
		ApplicationContext act = new AnnotationConfigApplicationContext(FactoryBeanConfig.class);
		String[] names = act.getBeanDefinitionNames();
		for (String name: names) {
			System.out.println(name);
		}
		// 通过id来获取容器中的bean
		System.out.println(act.getBean("createColorBean"));
		// 通过&id来获取容器中自定义的FactoryBean
		System.out.println(act.getBean("&createColorBean"));
	}
	
控制台输出:
factoryBeanConfig
createColorBean
Color [name=null]

14.Bean的生命周期

1.新建一个实体类

public class Dog {

	private String name;

	public String getName() {
		return name;
	}

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

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

	public Dog(String name) {
		super();
		System.out.println("创建完成。。。");
		this.name = name;
	}

	public Dog() {
		
		super();
		System.out.println("创建完成。。。");
	}
	//创建一个方法,用于之后指定这个方法是初始化方法
	public void init() {
		System.out.println("初始化方法执行......");
	}
	
	//创建一个方法,用于之后指定这个方法是销毁方法
	public void destroy() {
		System.out.println("销毁方法执行.......");
	}
	
}

2.创建一个配置类

@Configuration
public class LifecycleConfig {
	//指定初始化和销毁方法
	@Bean(initMethod = "init", destroyMethod = "destroy")
	public Dog smallDog() {
		Dog dog = new Dog();
		dog.setName("small");
		return dog;
	}
}

3.测试

@Test
	public void test04() {
		AnnotationConfigApplicationContext act = new AnnotationConfigApplicationContext(LifecycleConfig.class);
		//关闭IOC容器,bean被销毁,ApplicationContext是没有关闭方法的,他的子类才有
		act.close();
	}

对于单例bean来说,它的生命周期是,在初始化容器时,bean被创建,创建完成后执行初始化方法,在容器被关闭的时候,bean被销毁


对于多实例的bean来说,它的生命周期是,在bean被 调用时,bean被创建,bean被创建后执行初始化方法,但是这个bean不会被销毁,这个bean在被获取后,由程序员决定何时使用和销毁,而不由容器管理


15.通过实现InitializingBean和DisposableBean指定bean的初始化和销毁方法

1.新建一个类并实现InitiallizingBean和DisposableBean


/**
 * 实现InitializingBean、DisposableBean两个接口
 * @author Administrator
 *
 */
public class Cat implements InitializingBean, DisposableBean {

	private String name;
	
	public Cat(String name) {
		super();
		this.name = name;
	}

	public String getName() {
		return name;
	}

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

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

	/**
	 * 销毁方法,关闭容器时执行
	 */
	public void destroy() throws Exception {
	
		System.out.println("Cat销毁方法");
	}

	/**
	 * 初始化方法,这对象属性被赋值时执行
	 */
	public void afterPropertiesSet() throws Exception {
		System.out.println("Cat初始化方法");
	}

}

2.在配置类中配置

	@Bean
	public Cat smallCat() {
		return new Cat("smallCat");
	}
	

3.在测试类中测试

	@Test
	public void test05() {
		
		AnnotationConfigApplicationContext act = new AnnotationConfigApplicationContext(LifecycleConfig.class);
		act.close();
	}
控制台输出:
Cat初始化方法   <-- 初始化方法被执行
创建完成。。。
初始化方法执行......
八月 31, 2018 3:55:21 下午 
信息: Closing 
销毁方法执行.......
Cat销毁方法   <--  销毁方法被执行

16.使用@PostConstruct和@PreDestroy为bean指定初始化后销毁方法

1.定义一个实体类

// @Component把对象添加到容器中
@Component
public class Duck {
	
	//使用两个注解都是在javax.annotation这个包下的
	// PostConstruct 构造器之后,就是在构造器之后执行的方法
	@PostConstruct
	public void init() {
		System.out.println("Duck 初始化方法......");
	}
	
	//销毁之前,就是在bean对象被销毁之前执行的方法
	@PreDestroy
	public void destory() {
		System.out.println("Duck 销毁方法.......");
	}
}

2.在配置类上扫描这个实体类

@Configuration
@ComponentScan(value = {"com.spring.annotation.bean.lifecycle"})
public class LifecycleConfig {
	......
}

3.在测试类中测试

	@Test
	public void test05() {
		
		AnnotationConfigApplicationContext act = new AnnotationConfigApplicationContext(LifecycleConfig.class);
		act.close();
	}	
控制台输出:
Duck 初始化方法......  <---------@PostConstruct标注的方法被执行
Cat初始化方法
创建完成。。。
初始化方法执行......
销毁方法执行.......
Cat销毁方法
Duck 销毁方法.......  <----------@PreDestroy标注的方法被执行 

17.使用BeanPostProcessor做初始化前置后置方法

1.新建一个类实现BeanPostProcessor类

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		System.out.println(bean + "初始化之前执行&");
		return bean;
	}

	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		System.out.println(bean + "初始化之后执行&");
		return bean;
	}
}

2.在测试类中测试

	@Test
	public void test05() {
		
		AnnotationConfigApplicationContext act = new AnnotationConfigApplicationContext(LifecycleConfig.class);
		act.close();
	}	
	
控制台输出:
org.springframework.context.event.EventListenerMethodProcessor@2a225dd7初始化之前执行&
org.springframework.context.event.EventListenerMethodProcessor@2a225dd7初始化之后执行&

org.springframework.context.event.DefaultEventListenerFactory@61eaec38初始化之前执行&
org.springframework.context.event.DefaultEventListenerFactory@61eaec38初始化之后执行&

com.spring.annotation.bean.lifecycle.LifecycleConfig$$EnhancerBySpringCGLIB$$5201202d@6fa34d52初始化之前执行&
com.spring.annotation.bean.lifecycle.LifecycleConfig$$EnhancerBySpringCGLIB$$5201202d@6fa34d52初始化之后执行&

com.spring.annotation.bean.lifecycle.Duck@490caf5f初始化之前执行&
Duck 初始化方法......
com.spring.annotation.bean.lifecycle.Duck@490caf5f初始化之后执行&
					
Cat [name=smallCat]初始化之前执行&
Cat初始化方法
Cat [name=smallCat]初始化之后执行&

创建完成。。。
Dog [name=small]初始化之前执行&
初始化方法执行......
Dog [name=small]初始化之后执行&

销毁方法执行.......
Cat销毁方法
Duck 销毁方法.......

18.@Value和@PropertiesSource的使用

1.新建一个porperties文件

animal.name=\u963F\u9EC4

2.新建一个实体类


@Component //把实体类加载到spring容器中
public class Animal {

	//给type赋值String
	@Value("Dog")
	private String type;
    //读取properties中的文件为name赋值
	@Value("${animal.name}")
	private String name;
    //使用SpEL表达式为age属性赋值
	@Value("#{20 - 1}")
	private String age;

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}

	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;
	}

	@Override
	public String toString() {
		return "Animal [type=" + type + ", name=" + name + ", age=" + age + "]";
	}
	
}

3.新建一个配置类

// @PropertySource 导入properties文件
@PropertySource(value = {"classpath:/animal.properties"})
@Configuration
// 配置组件扫描路径
@ComponentScan("com.spring.annotation.bean.value")
public class ValueConfig {

}

4.测试类

	@Test
	public void test06() {
		ApplicationContext act = new AnnotationConfigApplicationContext(ValueConfig.class);
		Animal a = act.getBean(Animal.class);
		System.out.println(a);
	}	
控制台输出:
Animal [type=Dog, name=阿黄, age=19]

5.@PropertyResource和@PropertyResources

和@ComponentScan与@ComponentScans一样,@PropertyResources可以指定多个@PropertyResource
新建一个属性文件

animal.price=18\uFFE5

在Animal实体类中添加一个属性

@Component
public class Animal {
	@Value("Dog")
	private String type;
	@Value("${animal.name}")
	private String name;
	@Value("#{20 - 1}")
	private String age;
	//这连个从属性文件中获取值,获取的值来自于两个属性文件
	@Value(value = "${animal.price}")
	private String price;
	......	
}

在配置文件中配置@PropertyResources

// @PropertySource 导入properties文件
//@PropertySource(value = {"classpath:/animal.properties"})
@Configuration
// 配置组件扫描路径
@ComponentScan("com.spring.annotation.bean.value")
//@PropertySources中配置多个@PropertySource
@PropertySources(value = {
		@PropertySource({"classpath:/animal.properties"}),
		@PropertySource({"classpath:/price.properties"})
})
public class ValueConfig {

}
控制台输出:
Animal [type=Dog, name=阿黄, age=19, price=18¥]

19.自动装配@Autowired

1.新建一个类

@Service
public class AutowiredService {

	public String name = "1";
	
	public String getName() {
		return name;
	}
	
	public void setName(String name) {
		this.name = name;
	}

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

2.新建一个类,注入上一个类


@Controller
public class AutowiredController {

	//自动注入AutowiredService类
    //注入时,默认以类类型去容器中查找bean对象,如果存在一个对象,就注入
    //如果通过类类型查找到多个bean,就以属性名(这里是serivce)为bean对象的id去查找
	@Autowired
	private AutowiredService service;
	
	public void test() {
		System.out.println(service);
	}
	
}

3.新建一个配置类

@Configuration
//设置包扫描
@ComponentScan({"com.spring.autowired"})
public class AutowiredConfig {

}

4.测试

	@Test
	public void test() {
		ApplicationContext act = new AnnotationConfigApplicationContext(AutowiredConfig.class);
		AutowiredController bean = act.getBean(AutowiredController.class);
		bean.test();
	}
控制台输出:
AutowiredService [name=1]

##20.@Primary指定默认装配的Bean

1.在配置类中向ioc容器中加入一个AutowiredSerive对象

@Configuration
@ComponentScan({"com.spring.autowired"})
public class AutowiredConfig {

    //此时ioc容器中有两个@AutowiredSerice的对象,一个是1,一个是2
    //@Primary用来指定自动装配是以当前这个2为首选的
	@Primary
	@Bean("service")
	public AutowiredService creat() {
		AutowiredService autowiredService = new AutowiredService();
		autowiredService.setName("2");
		return autowiredService ;
	}
	
}

2.在Controller中装配

@Controller
public class AutowiredController {

	//自动注入AutowiredService类
    //默认查找类对应的bean,找到两个,但是其中一个标注了@Primary,此时注入的应该是2
	@Autowired
	private AutowiredService autowiredService;
	
	public void test() {
		System.out.println(autowiredService);
	}
	
}

3.在测试类中测试

	@Test
	public void test() {
		ApplicationContext act = new AnnotationConfigApplicationContext(AutowiredConfig.class);
		AutowiredController bean = act.getBean(AutowiredController.class);
		bean.test();
	}
控制台输出:
AutowiredService [name=2]   <----- 表示已经装配了name为2的AutowiredServie类了

21.使用@Qualifier限定要装配的是哪一个Bean

1.修改AutowiredController

@Controller
public class AutowiredController {

    //限定当前装配的Bean是id为autowiredService的bean
	@Qualifier("autowiredService")
	//自动注入AutowiredService类
	@Autowired
	private AutowiredService autowiredService;
	
	public void test() {
		System.out.println(autowiredService);
	}
	
}

测试结果

控制台输出:
AutowiredService [name=1]

可以使用@Inject和@Resource代替@Autowired,但是@Inject和@Resource是Java规范的注解,@Autowired是spring提供的注解,所以@Autowired支持更多的功能

22.@Autowired注解标注的位置

  • 属性构造器上

1.新建三个类,并分别加到容器中

@Component
public class Cat {

	private String name = "cat";

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

@Component
public class Dog {

	String name = "dog";

	public String getName() {
		return name;
	}

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

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

}

@Component
public class Person {

	//@Autowired可以标注在方法上
	@Autowired
	private Dog dog;
	
	private Cat cat;
	
	//@Autowired可以标注在构造器上
	//@Autowired
	//或者标注在参数位置,也可以省略,省略后也能自动注入 
	public Person(@Autowired Cat cat) {
		this.cat = cat;
	}
	
	public Dog getDog() {
		return dog;
	}

	public void setDog(Dog dog) {
		this.dog = dog;
	}
	@Override
	public String toString() {
		return "Person [dog=" + dog + ", cat=" + cat + "]";
	}
}

2.测试类扫描@Componen标记的类所在的包

测试结果如下:
Person [dog=Dog [name=dog], cat=Cat [name=cat]]
  • 方法上

1新建两个类

@Component //加载到容器中
public class Duck {

	private String name = "duck";

	public String getName() {
		return name;
	}

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

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

public class Animal {

	private Duck duck;

	public Duck getDuck() {
		return duck;
	}

	public void setDuck(Duck duck) {
		this.duck = duck;
	}

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

}

2.配置类中注入

	//这里可以写在方法上也可以写在方法的参数上,同时也可以省略,活力后也能自动注入
	@Autowired
	@Bean
	public Animal createAnimal(Duck duck) {
		Animal animal = new Animal();
		animal.setDuck(duck);
		return animal;
	}

23.@Profile指定测试环境

1.新建一个类


/**
 * 可以通过实现EmbeddedValueResolverAware的setEmbeddedValueResolver方法获取解析对象,
 * 通过解析对象获取属性文件中的信息
 *
 */
@Configuration
//属性文件扫描
@PropertySource({"classpath:jdbc.properties"})
public class ProfileConfig implements EmbeddedValueResolverAware {

	/**
	 * 通过@Value获取属性文件中的值
	 * {@value} 在属性位置获取值的方法
	 */
	@Value("${jdbc.password}")
	private String password;
	
	private StringValueResolver valueResolver;
	
	
	/**
	 * {@value} 可以用在参数位置
	 * @Profile 注解可以指定运行时环境 默认环境是default 即@Profile("default")
	 * 
	 * @param user
	 * @return
	 * @throws PropertyVetoException
	 */
	@Profile("test")
	@Bean("testDataSource")
	public DataSource dataSourceTest(@Value("${jdbc.user}")String user)  throws PropertyVetoException {
		ComboPooledDataSource dataSource = new ComboPooledDataSource();
		dataSource.setPassword(password);
		dataSource.setUser(user);
		String driverClass = valueResolver.resolveStringValue("${driverClass}");
		dataSource.setDriverClass(driverClass);
		dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
		return dataSource;
	}
	
	@Profile("dev")
	@Bean("devDataSource")
	public DataSource dataSourceDev(@Value("${jdbc.user}")String user)  throws PropertyVetoException {
		ComboPooledDataSource dataSource = new ComboPooledDataSource();
		dataSource.setPassword(password);
		dataSource.setUser(user);
		String driverClass = valueResolver.resolveStringValue("${driverClass}");
		dataSource.setDriverClass(driverClass);
		dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
		return dataSource;
	}

	@Profile("pro")
	@Bean("proDataSource")
	public DataSource dataSourcePro(@Value("${jdbc.user}")String user)  throws PropertyVetoException {
		ComboPooledDataSource dataSource = new ComboPooledDataSource();
		dataSource.setPassword(password);
		dataSource.setUser(user);
		String driverClass = valueResolver.resolveStringValue("${driverClass}");
		dataSource.setDriverClass(driverClass);
		dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
		return dataSource;
	}

	public void setEmbeddedValueResolver(StringValueResolver resolver) {
		this.valueResolver = resolver;
	}
		
}

2.测试
在这里插入图片描述

选择运行环境

控制台输出:
profileConfig
devDataSource   <---- 对应的环境被加载

3.代码方式测试

	@Test
	public void test01() {
		AnnotationConfigApplicationContext act = new AnnotationConfigApplicationContext();
		//设置环境信息
		act.getEnvironment().setActiveProfiles("pro");
		//加载配置类
		act.register(ProfileConfig.class);
		//刷新容器
		act.refresh();
		String[] names = act.getBeanDefinitionNames();
		for (String name : names) {
			System.out.println(name);
		}
	}
控制台输出:
profileConfig
proDataSource

@Profile可以写在一个类上

24.Spring面向切面编程

1.pom.xml加入依赖,导入SpringAOP模块

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

2.新建一个类,作为目标类

public class MathCaculator {
	
	/***
	 * 目标方法
	 * @param i
	 * @param j
	 * @return
	 */
	public int devide(int i , int j) {
		System.out.println("目标方法执行...");
		return i / j;
	}

}

3.新建一个切面类


/**
 *说明这是一个切面类,这个注解一定要有 
 */
@Aspect
public class AspectConfig {
	
	//定义切入点表达式,声明切入点,当前类下的所有方法
	@Pointcut("execution(public int com.spring.aop.MathCaculator.*(..))")
	public void pointcut() {
		
	}
	
	//前置通知,指定切入点,对应的具体到某个方法
	@Before("execution(public int com.spring.aop.MathCaculator.*(int , int ))")
	public void before(JoinPoint joinPoint) {
		//joinPoint可以获取到被切入的类的信息
		System.out.println("这是前置方法...." + "\t -> \t方法" + joinPoint.getSignature().getName() + "参数" + Arrays.asList(joinPoint.getArgs()));
	}
	
	//后置通知,使用对应的切入点表达式
	@After("pointcut()")
	public void after() {
		System.out.println("这是后置方法...");
	}
	
	//返回通知,使用定义的切入点表达式定义的切入点,returning指定接收返回值的参数
	@AfterReturning(value = "pointcut()", returning = "result")
	public void afterReturning(JoinPoint joinPoint, int result) {
        // JoinPoint这个参数一定要放在参数中的第一个位置
		System.out.println("返回通知"  + "\t 方法" + joinPoint.getSignature().getName() + "\t参数" + Arrays.asList(joinPoint.getArgs()) + "\t返回值"+ result);
	}
	
	//异常通知 
	//如果在切入点表达式外的一个类使用这个切入点表达式的话可以直接使用对应的方法的全类名
    //throwing 指定接收异常的参数
	@AfterThrowing(value = "com.spring.aop.AspectConfig.pointcut()", throwing = "exception")
	public void exception(Exception exception) {
		System.out.println("这是异常通知..." + "\t 异常信息是:" + exception);
	}

}

4.新建配置类

/**
 *开启切面类配置,这个注解一定要有
 */
@EnableAspectJAutoProxy
@Configuration
public class AOPConfig {

	//加入目标类到容器中
	@Bean
	public AspectConfig createAspect() {
		return new AspectConfig();
	}
	
	//加入切面类到容器中
	@Bean
	public MathCaculator mathCaculator() {
		return new MathCaculator();
	}
	
}

5.测试

	@Test
	public void test() {
		ApplicationContext act = new AnnotationConfigApplicationContext(AOPConfig.class);
		//从容器中获取目标类,目标类一定是从容器中获取的,否则不会触发通知
		MathCaculator mc = act.getBean(MathCaculator.class);
		//执行目标类,对应的通知方法就会执行
		int i = mc.devide(1, 1);
	}
控制台输出:
这是前置方法....	 -> 	方法devide参数[1, 1]
目标方法执行...
这是后置方法...
返回通知	 方法devide	参数[1, 1]	返回值1

25.声明式事务

1.新建一个配置类


//开启基于注解的事务管理功能 
@EnableTransactionManagement
//扫描组件到spring 容器中
@ComponentScan({"com.spring.transion"})
@Configuration
public class TransionConfig {

	/**
	 * 注册数据源
	 */
	@Bean
	public DataSource dataSource() throws PropertyVetoException {
		ComboPooledDataSource dataSource = new ComboPooledDataSource();
		dataSource.setUser("root");
		dataSource.setPassword("123456");
		dataSource.setDriverClass("com.mysql.jdbc.Driver");
		dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC");
		return dataSource;
	}
	
	/**
	 * 注册JdbcTemplate
	 */
	@Bean
	public JdbcTemplate jdbcTemplate() throws PropertyVetoException {
		JdbcTemplate jdbcTemplate = new JdbcTemplate();
		//调用@Bean标注的本地方法,其实就是获取Spring容器中注入的值
		jdbcTemplate.setDataSource(dataSource());
		return jdbcTemplate;
	}
	
	/**
	 * 加入事务管理器到spring容器中
	 */
	@Bean
	public PlatformTransactionManager transactionManager() throws PropertyVetoException {
		//设置事务管理器要控制的事务
		return new DataSourceTransactionManager(dataSource());
	}
	
	
}

2.新建一个Dao类用于操作数据库


@Repository
public class UserDao {

	@Autowired
	private JdbcTemplate jdbcTemplate;
	
	public int insert() {
		String sql = "INSERT INTO `user`(username, age) VALUES(?,?);";
		int update = jdbcTemplate.update(sql, "小明", 19);
		return update;
	}
	
}

3.新建一个Service类模拟业务

@Service
public class UserService {

	@Autowired
	private  UserDao userDao;

	// @Transactional 标注在对应的方法或者类上,说明对应的类中所有方法或者对应的类支持事务
	@Transactional
	public void insert() {
		int i = userDao.insert();
		
		System.out.println("执行成功");
		//模拟数据库操作有异常,数据库的操作应该回滚
		int j = 1 / 0;
		
	}	
}

4.测试类中测试

	@Test
	public void test() {
		ApplicationContext act = new AnnotationConfigApplicationContext(TransionConfig.class);
		UserService userService = act.getBean(UserService.class);
		userService.insert();
	}

执行效果,看数据库中是否有数据插入…

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值