2.21@PostConstruct 注解
源码:
@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PostConstruct {
}
@PostConstruct和bean标签的属性init-method有同样的作用,可以标记bean生命周期回调方法的初始化方法。
案例:
/**
* @author 戴着假发的程序员
*
* @description
*/
@Component
public class AuthorDAO implements IAutorDAO {
public AuthorDAO(){
System.out.println("实例化AuthorDAO");
}
@PostConstruct
public void init(){
System.out.println("执行AuthorDAO的初始化方法-init");
}
@Override
public String get() {
return "戴着假发的程序员";
}
}
当我们初始化容器时控制台输出:
ApplicationContext ac =
new AnnotationConfigApplicationContext("com. st.dk.demo4");
实例化AuthorDAO
执行AuthorDAO的初始化方法-init
2.22@PreDestroy
源码:
@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PreDestroy {
}
@PreDestroy和bean标签的destory-method有同样的作用,用于标注bean生命周期中的释放资源的方法.
案例:
/**
* @author 戴着假发的程序员
*
* @description
*/
@Component
public class AuthorDAO implements IAutorDAO {
public AuthorDAO(){
System.out.println("实例化AuthorDAO");
}
@PostConstruct
public void init(){
System.out.println("执行AuthorDAO的初始化方法-init");
}
@PreDestroy
public void destory(){
System.out.println("执行AuthorDAO的释放资源的方法 -destory");
}
@Override
public String get() {
return "戴着假发的程序员";
}
}
当我们关闭容器时,控制台输出如下:
AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext("com. st.dk.demo4");
System.out.println("容器初始化完成...");
ac.close();
System.out.println("容器关闭完成...");
实例化AuthorDAO
执行AuthorDAO的初始化方法-init
容器初始化完成...
执行AuthorDAO的释放资源的方法 -destory
容器关闭完成...
2.23@ComponentScan和@ComponentScans注解
@ComponentScan往往是注解在@Configuration的类中,用于添加自动扫描的包。我们可以通过属性basePackages或者value指定一个或者多个要扫描的包。
简单案例如下:
/**
* @author 戴着假发的程序员
*
* @description
*/
@Configuration
@ComponentScan("com. st.dk.demo4")
public class AppConfig {
}
或者
/**
* @author 戴着假发的程序员
*
* @description
*/
@Configuration
@ComponentScan({"com. st.dk.demo4","com. st.dk.otherpk"})
public class AppConfig {
}
@ComponentScans是另外一个扫描包的注解,有一个数组形势的属性value,而数组的类型就是@ComponentScan,也就是一个@ComponentScans中可以配置多个@ComponentScan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface ComponentScans {
ComponentScan[] value();
}
我们可以这样配置:
/**
* @author 戴着假发的程序员
*/
@Configuration
@ComponentScans({
@ComponentScan("com.st.demo1"),
@ComponentScan("com.st")
})
public class AppConfig {
}
这时我们创建容器的方式应该是下面的方式:
@Test
public void testComponentScan(){
ApplicationContext ac =
new AnnotationConfigApplicationContext(AppConfig.class);
}
spring会扫描所有ComponentScan指定的包以及他们的子孙包中所有的类。 当然同样的我们其实可以在配置文件中使用context:component-scan标签配置扫描的包。
例如:
<?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.xsd">
<context:component-scan base-package="com. st.dk.demo"/>
</beans>
这里要注意,如果要使用context需要引入对应的namespace。
通常情况下context:component-scan标签会隐式启用context:annotation-config的功能。
2.24@ComponentScan注解的basePackageClasses 属性
basePackageClasses 用来指定某个类所在包下的所有组件。springboot中的主应用类就是一个BasePackageClass。
看案例:
我们准备如下结构的类:
其中每个类源码:
Person类:在beans包下
/**
* @author 戴着假发的程序员
*
* @description
*/
@Component
public class Person {
public Person(){
System.out.println("实例化Person类");
}
}
Car类:在otherbeans包下:
/**
* @author 戴着假发的程序员
*
* @description
*/
@Component
public class Car {
public Car(){
System.out.println("实例化Car类 ");
}
}
Truck类:在otherbeans包下:
/**
* @author 戴着假发的程序员
*
* @description
*/
@Component
public class Truck {
public Truck(){
System.out.println("实例化Truck");
}
}
Bus类:在otherbeans.pub包下
/**
* @author 戴着假发的程序员
*
* @description
*/
@Component
public class Bus {
public Bus(){
System.out.println("实例化Bus");
}
}
配置类,制定扫描beans包下的所有类,另外通过basePackageClasses指定Car.calss(这里注意 basePackageClasses属性是一个数组,可以同时配置多个class)
/**
* @author 戴着假发的程序员
*
* @description
*/
@Configuration
@ComponentScan(basePackages = "com. st.dk.demo6.beans",basePackageClasses = Car.class)
public class AppConfig {
}
测试:
@Test
public void testBasePackageClasses(){
ApplicationContext ac =
new AnnotationConfigApplicationContext(AppConfig.class);
}
结果:
我们会发现,spring不光帮我们加载了beans包下的Person类,而且同时加载了Car所在包下的Car和Truck类,而且加载Car所在包的子孙包下的Bus类。
2.25@ComponentScan注解的useDefaultFilters属性
这里的useDefaultFilters和配置contentx:component-sacn的属性useDefaultFilters有一样的作用。默认是true。表示使用默认的过滤器。spring的默认Filter就会处理@Component、@Controller、@Service、@Repository这些注解的Bean。
如果use-default-filters配置为false,则spring就不会再扫描和处理上面这些注解的Bean。
案例:
修改上一章节的配置类:useDefaultFilters配置为false
/**
* @author 戴着假发的程序员
*
* @description
*/
@Configuration
@ComponentScan(basePackages = "com. st.dk.demo6",useDefaultFilters = false)
public class AppConfig {
}
在创建容器,我们会发现,spring不会加载任何类。
2.26@ComponentScan注解的resourcePattern属性
这里的resourcePattern和配置contentx:component-sacn的属性resourcePattern有一样的作用。
是用来配置要扫描的资源的正则表达式的,一般这里都是一个粗略的配置。
默认的配置是”**.*class“ 表示扫描配置包下的所有class文件。
我们可以修改配置类测试一下:我修改resourcePattern,只扫描us结尾的类:
/**
* @author 戴着假发的程序员
*
* @description
*/
@Configuration
@ComponentScan(basePackages = "com. st.dk.demo6",resourcePattern = "**/*us.class")
public class AppConfig {
}
测试创建容器查看结果:
我们会发现spring只是加载Bus类,并没有加载其类。
2.27@ComponentScan注解的lazyInit属性
lazyInit用于通知spring对扫描的类是要延迟初始化。默认是false,就是不延迟。我们可以修改为true测试一下:
修改配置类,将lazyInit修改为true。
/**
* @author 戴着假发的程序员
*
* @description
*/
@Configuration
@ComponentScan(basePackages = "com. st.dk.demo6",lazyInit = true)
public class AppConfig {
}
修改测试类:
@Test
public void testLazyInit(){
ApplicationContext ac =
new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println("容器创建完成");
Bus bean = ac.getBean(Bus.class);
System.out.println("获取bean:"+bean);
}
结果:
我们发现容器创建完成之后,spring并没有实例化任何类,当我们获取Bus类对象时,spring才会实例化Bus类对象。
2.28@ComponentScan注解的nameGenerator属性
nameGenerator和contentx:component-sacn的属性name-generator作用一致:
这个属性指定你的构造型注解,注册为Bean的ID生成策略,这个生成器基于接口BeanNameGenerator实现generateBeanName方法,你可以自己写个类去自定义策略。这边,我们可不显示配置,它是默认使用org.springframework.context.annotation.AnnotationBeanNameGenerator生成器,也就是类名首字符小写的策略,如Account类,它注册的Bean的ID为account.并且可以自定义ID,.这边简单贴出这个默认生成器的实现。
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
if (definition instanceof AnnotatedBeanDefinition) {
String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
if (StringUtils.hasText(beanName)) {
// Explicit bean name found.
return beanName;
}
}
// Fallback: generate a unique default bean name.
return buildDefaultBeanName(definition, registry);
}
我们可以自定义一个BeanName的生成器:
/**
* @author 戴着假发的程序员
*
* @description
*/
public class MyBaenNamegenerator implements BeanNameGenerator {
@Override
public String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry beanDefinitionRegistry) {
String beanName = null;
//从beanDefinition中取出类名
String beanClassName = beanDefinition.getBeanClassName();
if(beanClassName.indexOf(".")!=-1){
beanClassName = beanClassName.substring(beanClassName.lastIndexOf(".")+1);
//我们来添加一个前缀
beanName = "dk_"+beanClassName;
}
return beanName;
}
}
修改配置类,配置nameGenerator为我们自定义的类:
/**
* @author 戴着假发的程序员
*
* @description
*/
@Configuration
@ComponentScan(basePackages = "com. st.dk.demo6",nameGenerator = MyBaenNamegenerator.class)
public class AppConfig {
}
给Person类实现BeanNameAware接口:
/**
* @author 戴着假发的程序员
*
* @description
*/
@Component
public class Person implements BeanNameAware {
public Person(){
System.out.println("实例化Person类");
}
@Override
public void setBeanName(String s) {
System.out.println("Person的beanName:"+s);
}
}
创建容器测试:
@Test
public void testNameGenerator(){
ApplicationContext ac =
new AnnotationConfigApplicationContext(AppConfig.class);
}
结果:
我们会发现Person类的beanName会按照我们的定义的规则进行制定。
2.29@ComponentScan注解的scopedProxy属性
这里的scopedProxy属性和context:component-scan的scoped-proxy属性作用完全一致。
scoped-proxy 往往是使用在web工程中,比如下面的场景:
我们有一个Bean的score是session,那么就是每一个回话都会创建一个Bean。spring容器在初始化时不会创建这个Bean对象。 但是如果我们将这个Bean注入到其他的单利的bean中,就会出现问题。这是spring的解决方案就是产生一个代理对象。
那么scoped-proxy 就是用来配置代理方式的,总共有三个值
no(默认值):如果有接口就使用JDK代理,如果没有接口就使用CGLib代理
interfaces: 接口代理(JDK代理)
targetClass:类代理(CGLib代理)
2.30@ComponentScan注解的scopeResolver属性
这里的scopeResolver也是和 context:component-scan的scope-resolver属性完全一致:
注意这个属性和scoped-proxy是互斥的。
这个属性跟name-generator有点类似,它是基于接口ScopeMetadataResolver的,实现resolveScopeMetadata方法,目的是为了将@Scope(value="",proxyMode=ScopedProxyMode.NO,scopeName="")的配置解析成为一个ScopeMetadata对象,Spring这里也提供了两个实现,我们一起看下。首先是org.springframework.context.annotation.AnnotationScopeMetadataResolver中,
public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
ScopeMetadata metadata = new ScopeMetadata();
if (definition instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(annDef.getMetadata(), this.scopeAnnotationType);
if (attributes != null) {
metadata.setScopeName(attributes.getAliasedString("value", this.scopeAnnotationType, definition.getSource()));
ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
if (proxyMode == null || proxyMode == ScopedProxyMode.DEFAULT) {
proxyMode = this.defaultProxyMode;
}
metadata.setScopedProxyMode(proxyMode);
}
}
return metadata;
}
org.springframework.context.annotation.Jsr330ScopeMetadataResolver中的实现:
public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
ScopeMetadata metadata = new ScopeMetadata();
metadata.setScopeName(BeanDefinition.SCOPE_PROTOTYPE);
if (definition instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
Set<String> annTypes = annDef.getMetadata().getAnnotationTypes();
String found = null;
for (String annType : annTypes) {
Set<String> metaAnns = annDef.getMetadata().getMetaAnnotationTypes(annType);
if (metaAnns.contains("javax.inject.Scope")) {
if (found != null) {
throw new IllegalStateException("Found ambiguous scope annotations on bean class [" +
definition.getBeanClassName() + "]: " + found + ", " + annType);
}
found = annType;
String scopeName = resolveScopeName(annType);
if (scopeName == null) {
throw new IllegalStateException(
"Unsupported scope annotation - not mapped onto Spring scope name: " + annType);
}
metadata.setScopeName(scopeName);
}
}
}
return metadata;
}
2.31@ComponentScan注解的includeFilters属性
这里的includeFilters和context:include-filter标签的作用完全一致。 我们已经知道ComponentScan的配置可以通知spring扫描拥有spring标准注解的类。这些标注大致是:@Component、@Controller、@Service、@Repository。但是我们也可以通过includeFilters属性配置通知spring扫描一些没有标准注解但是我们希望spring帮我们管理的类。
includeFilters的值是一个数组,可以配置多个,includeFilters中配置的是一个Filter类型的注解。Filter的源码如下:
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@java.lang.annotation.Target({})
static @interface Filter {
org.springframework.context.annotation.FilterType type() default org.springframework.context.annotation.FilterType.ANNOTATION;
@org.springframework.core.annotation.AliasFor("classes")
java.lang.Class<?>[] value() default {};
@org.springframework.core.annotation.AliasFor("value")
java.lang.Class<?>[] classes() default {};
java.lang.String[] pattern() default {};
}
Filter中有4个属性,分别是
type:用来配置Filter的类型,这个类型是一个枚举,这个类型一共有五种,下面再详细解释。
value:根据type的不同,这个表达式的配置方式也不同。
classes:当我们的type为ANNOTATION或者ASSIGNABLE_TYPE时,我们可以将对应的类配置在value属性中也可以配置在calsses属性中
pattern:当我们的type是REGEX时,我们可以将表达式配置的pattern中。
type属性的五个值:
ANNOTATION:-指定扫描使用某个注解的类
ASSIGNABLE_TYPE:指定扫描某个接口派生出来的类
ASPECTJ:指定扫描AspectJ表达式相匹配的类
REGEX:指定扫描符合正则表达式的类
CUSTOM:指定扫描自定义的实现了org.springframework.core.type.filter.TypeFilter接口的类
就这个五个类型,我准备五个案例看看
[1]ANNOTATION:-指定扫描使用某个注解的类,将其加载进spring的容器。
我们准备如下的类目录结构:
其中Person类实现:
Person有Spring的标准注解@Component.
/**
* @author 戴着假发的程序员
*
* @description
*/
@Component//Person类交给spring管理
public class Person{
public Person(){
System.out.println("实例化Person类");
}
}
Dog类的实现:
Dog类并没有Spring的标准注解,添加添加了我们自定义的注解:
/**
* @author 戴着假发的程序员
*
* @description
*/
@DkAnnotation
public class Dog {
public Dog(){
System.out.println("实例化Dog类");
}
}
我们自定的注解@DkAnnotation
/**
* @author 戴着假发的程序员
*
* @description
*/
public @interface DkAnnotation {
}
添加一个配置类:
配置类中给ComponentScan添加属性excludeFilters,类型为ANNOTATION,value为DkAnnotation.class。当然 也可以使用classes代替value,注意value和classes都是数组,可以同时配置多个。
/**
* @author 戴着假发的程序员
*
* @description
*/
@Configuration()
@ComponentScan(
basePackages = "com. st.dk.demo6.beans",
includeFilters = {@ComponentScan.Filter(type= FilterType.ANNOTATION,value= DkAnnotation.class)}
)
public class AppConfig {
}
测试:
@Test
public void testIncludeFilters(){
ApplicationContext ac =
new AnnotationConfigApplicationContext(AppConfig.class);
}
结果:
我们会发现spring会将有标注注解的Person类和我们有我们自定义注解的Dog都加载。
[2]ASSIGNABLE_TYPE:指定扫描某个接口派生出来的类
案例:
我们修改上面的案例,添加一个接口Info。
/**
* @author 戴着假发的程序员
*
* @description
*/
public interface Info {
}
修改Dog类,删除Dog类上方的注解,但是让Dog类实现接口Info。
/**
* @author 戴着假发的程序员
*
* @description
*/
public class Dog implements Info {
public Dog(){
System.out.println("实例化Dog类");
}
}
修改配置类,修改type属性为ASSIGNABLE_TYPE,value或者classes属性配置为Info .class。表示实现了Info接口的类要求spring加载。
/**
* @author 戴着假发的程序员
*
* @description
*/
@Configuration()
@ComponentScan(
basePackages = "com. st.dk.demo6.beans",
includeFilters = {@ComponentScan.Filter(type= FilterType.ASSIGNABLE_TYPE,value={Info.class})}
)
public class AppConfig {
}
再测试,我们会发现spring依然会加载Dog类。
[3]ASPECTJ:指定扫描AspectJ表达式相匹配的类,比如要求加载某个类的派生类
案例:
我们添加一个Animal类:
/**
* @author 戴着假发的程序员
*
* @description
*/
public class Animal {
}
修改Dog,继承Animal。
/**
* @author 戴着假发的程序员
*
* @description
*/
public class Dog extends Animal {
public Dog(){
System.out.println("实例化Dog类");
}
}
修改配置类,type配置为ASPECTJ,这里注意去掉value或者calsses属性,添加pattern属性,值为:com. st.dk.demo6.beans.Animal+
/**
* @author 戴着假发的程序员
*
* @description
*/
@Configuration()
@ComponentScan(
basePackages = "com. st.dk.demo6.beans",
includeFilters = {@ComponentScan.Filter(type= FilterType.ASPECTJ,pattern = "com. st.dk.demo6.beans.Animal+")}
)
public class AppConfig {
}
再测试,我们会发现spring依然会加载Dog类。
[4]REGEX:指定扫描符合正则表达式的类 我们可以通过REGEX配置一个正则表达式,spring会根据正则匹配加载对应的类。
案例:我们修改Dog类,不实现接口,不继承其他类,没有注解。
/**
* @author 戴着假发的程序员
*
* @description
*/
public class Dog{
public Dog(){
System.out.println("实例化Dog类");
}
}
修改配置类,type修改为REGEX,pattern修改为:“.*.*og”,表示加载og结尾的类。
/**
* @author 戴着假发的程序员
*
* @description
*/
@Configuration()
@ComponentScan(
basePackages = "com. st.dk.demo6.beans",
includeFilters = {@ComponentScan.Filter(type= FilterType.REGEX,pattern = ".*.*og")}
)
public class AppConfig {
}
再测试,我们会发现spring依然会加载Dog类。
[5]CUSTOM:指定扫描自定义的实现了org.springframework.core.type.filter.TypeFilter接口的类
CUSTOM允许我们自定义加载策略,我们可以自己实现一个TypeFilter,实现自己的加载策略。
案例:我们自己实现一个Filter,用来加载Dog类。添加一个自己的类实现接口TypeFilter
/**
* @author 戴着假发的程序员
*
* @description
*/
public class DkFilter implements TypeFilter {
//关于metadataReader和metadataReaderFactory可以关注spring源码解读部分
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//获取当前正在扫描的类的类名
String className = metadataReader.getClassMetadata().getClassName();
//判断是不是Dog的class
if(className.equals(Dog.class.getName())){
//返回true表示让spring加载当前的类.
return true;
}
//返回false表示不让spring加载当前类
return false;
}
}
修改配置类,type修改为CUSTOM,value或者classes中配置DkFilter.class
/**
* @author 戴着假发的程序员
*
* @description
*/
@Configuration()
@ComponentScan(
basePackages = "com. st.dk.demo6.beans",
includeFilters = {@ComponentScan.Filter(type= FilterType.CUSTOM,value = DkFilter.cl
)}
)
public class AppConfig {
}
再测试,我们会发现spring依然会加载Dog类。
2.32@ComponentScan注解的excludeFilters属性
我们已经知道ComponentScan的配置可以通知spring扫描拥有spring标准注解的类。这些标注大致是:@Component、@Controller、@Service、@Repository。我们也可通过excludeFilters属性配置排除我们不希望spring容器加载的类。 比如我们再web工程中只希望加载@Controller,就可以排除其他的标准注解的类。
这个属性和includeFilters 都是ComponentScan的属性,作用和includeFilters 正好相反,用于排除类。 但是配置方式和includeFilters 完全一样,同样有5个类型:
有两个必须的属性:
type: 配置filter的类型,这个类型一共有以下五个值:
assignable-指定扫描某个接口派生出来的类 annotation-指定扫描使用某个注解的类 aspectj-指定扫描AspectJ表达式相匹配的类 custom-指定扫描自定义的实现了org.springframework.core.type.filter.TypeFilter接口的类 regex-指定扫描符合正则表达式的类
expression: 根据type的不同,这个表达式的配置方式也不同。
具体的案例可以参考includeFilters 章节。
完结 后续继续更新springAOP相关细节。欢迎关注。。。。