【笔记】Spring注解

注解形式的Spring

注解形式存放Bean时分为——功能性的类(三层或其他功能类)和实体类

三层类或功能性类注解

​ @Repository

​ @Service

​ @Controller

​ @Component

实体类注解

​ @Bean

产生 SpringIOC 容器有两种形式:

①通过XML文件配置

②通过带有**@Configuration**注解的类(也叫配置类)来获得IOC容器

注意:两种形式获得的IoC容器是独立的。

下面介绍通过注解得到的IoC容器的各种使用方式:

取Bean:

//传参为标注了@Configuration的配置类
ApplicationContext context = new AnnotationConfigApplicationContext(xxx.class) ;

通过反射得到带有@Configuration注解的类,从而得到上下文对象。之后依旧使用**getBean()**方法。

注册Bean:

① 对三层组件:

先给三层组件加注解(@Controller、@Service、@Respository)/ @Component

再将注解所在的包加入Ioc的扫描器:

通过注解@ComponentScan配置扫描器

basePackages属性:指定要扫描的包

过滤器Filter规则:

includeFilters属性: 代表包含的过滤器(默认情况会包含三层的注解,因此指定时需要先设置useDefaultFilters = false)。

excludeFilters属性: 代表不包含的过滤器

​ 两种过滤器内部:通过@ComponentScan.Filter()指定过滤类型和过滤的值

过滤类型:FilterType(ANNOTATION,ASSIGNABLE_TYPE,CUSTOM)

ANNOTATION 代表三层注解类型 value中放选定层次的类(如Service.class)

ASSIGNABLE_TYPE 代表具体的类 value中放选定具体的类(如StudentController.class)

CUSTOM 代表使用自定义规则 value中放过滤器的类(如MyFilter.class)

自定义Filter:

自定义规则过滤器的类需要实现FilterType接口,并重写match方法

public class MyFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {       
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        //获得此时经过过滤器的类名
        String className = classMetadata.getClassName();
        //当全类名中包含Dao时,将该类加入容器
        return className.contains("Dao");
    }
}
//match方法中metadataReader的各种方法的作用:    
//获取当前类的注解信息
metadataReader.getAnnotationMetadata();
//获取当前类的资源信息(如类路径)
metadataReader.getResource();
//获取当前正在扫描的类信息
metadataReader.getClassMetadata();
@ComponentScan示例:
@ComponentScan(basePackages = "org.fall",
               excludeFilters = @ComponentScan.Filter(type= FilterType.ASSIGNABLE_TYPE,value = StudentController.class) ) 

@ComponentScan(basePackages = "org.fall",
               excludeFilters = @ComponentScan.Filter(type= FilterType.ANNOTATION,value = Service.class)) 

@ComponentScan(basePackages = "org.fall",
               excludeFilters = @ComponentScan.Filter(type= FilterType.CUSTOM, value = {MyFilter.class} )) 

@ComponentScan(basePackages = "org.fall",includeFilters = 
               @ComponentScan.Filter(type= FilterType.CUSTOM, value = {MyFilter.class}),useDefaultFilters = false)

② 对其他组件:

一、@Bean注解:

​ @Bean+方法名+返回值:

​ 得到的实例id默认是方法名,可以通过@Bean(“id”)改变id

​ 加入容器的实例类型就是返回值的类型

二、@Import注解:

​ @Import注解源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
	/**
	 * {@link Configuration @Configuration}, {@link ImportSelector},
	 * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
	 * 表示value中可以填配置类、ImportSelector实现类、ImportBeanDefinitionRegistrar和常规的Bean类
	 */
	Class<?>[] value();

}
1)、value填入自定义Bean类:

​ 直接在配置类上标注@Import,将想导入的类直接放入容器(可以是数组形式),id默认为导入类的全类名。

@Configuration
@Import({Dog.class,xxx.class,xxxx.class})
public class MySpringConfig {
    ... ...
}
2)、value填入ImportSelector实现类:

​ 通过实现ImportSelector接口的selectImports方法,该方法返回值就是想要导入的类的全类名:

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //返回一个String数组,数组中写想要导入的类的全类名,注意绝对不能返回null,否则会触发空指针异常
        return new String[]{"org.fall.bean.Blue", "org.fall.bean.Red"};
    }
}
@Configuration
//在@Import中加入自定义的ImportSelector接口的实现类,将selectImports返回数组对应对象加入容器
@Import({MyImportSelector.class})
public class MySpringConfig {
    ... ...
}
3)、value填入ImportBeanDefinitionRegistrar的实现类

​ 编写ImportBeanDefinitionRegistrar的实现类,并重写其方法

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //判断容器中是否注册了Blue和Red两个类的实例
        boolean isContain = registry.containsBeanDefinition("org.fall.bean.Blue");
        boolean isContain2 = registry.containsBeanDefinition("org.fall.bean.Red");
        //如果两个实例均已经注册,则将Color实例注册入容器
        if (isContain && isContain2){
            //使用BeanDefinition的实现类RootBeanDefinition,将Color放入其中
            BeanDefinition beanDefinition = new RootBeanDefinition(Color.class);
            //registerBeanDefinition方法手动注册入容器,
            // id=color,BeanDefinition传入想要注册的Bean信息。
            registry.registerBeanDefinition("color",beanDefinition);
        }
    }
}

​ 配置类中@Import导入该自定义类:

​ 因为前面Blue和Red是通过MyImportSelector注册入容器,想要满足此处的注册条件,必须也把该Selector类Import进来

@Configuration
@Import({MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class MySpringConfig {
    ... ...
}
三、通过自定义BeanFactory实现类

通过编写BeanFactory的实现类,在其中指定想通过工厂创建的对象类型。

//泛型对应的就是通过工厂创建的Bean类型
public class ColorFactoryBean implements FactoryBean<Color> {

    //返回加入容器的对象
    @Override
    public Color getObject() throws Exception {
        return new Color();
    }
	//返回对象类型
    @Override
    public Class<?> getObjectType() {
        return Color.class;
    }

    /**
     * @return true:单例模式 ; false:非单例模式
     */
    @Override
    public boolean isSingleton() {
        return true;
    }
}

在配置类中通过@Bean注解,修饰返回值为xxxFactoryBean的方法:

@Configuration
public class MySpringConfig {
    ... ...
        
    @Bean
    public ColorFactoryBean colorFactoryBean(){
        return new ColorFactoryBean();
    }
}

可以在测试类中发现,虽然@Bean注解修饰的方法返回值是ColorFactoryBean,但是通过方法名得到的Bean类型确是Color;而如果想获得工厂本身的Bean,则需要在id前面加上**”&“**

@Test
public void test04(){
    ApplicationContext context = new AnnotationConfigApplicationContext(MySpringConfig.class);
    Object colorFactoryBean = context.getBean("colorFactoryBean");
    Object factoryBean = context.getBean("&colorFactoryBean");
    System.out.println("通过colorFactoryBean取得的Bean:" + colorFactoryBean);
    System.out.println("通过&colorFactoryBean取得的Bean" + factoryBean);
}

输出结果

常用注解

@Scope注解:设置组件的作用域。

​ Scope的value属性的值:

prototype:多实例的

​ 多实例在每次getBean()时被添加到容器,且每次getBean得到的对象不同。

singleton:单实例的(默认)

​ 单实例在ioc容器启动时被创建并放入容器,之后每次getBean都是从容器中获取。

request:同一次请求只创建一个实例

session:同一个session只创建一个实例

@Lazy:懒加载

​ 针对单实例模式,在IOC容器创建的时候不加载,只在第一次获取Bean时加载

@Conditional 根据条件注册Bean

Since Spring4.0

public @interface Conditional {
	//@Conditional包含一个属性——Condition数组;
    //通过这个数组,指定需要的条件
   Class<? extends Condition>[] value();
}

​ 该注解标注在@Bean的方法上:则满足条件时该对象注册入容器;

​ 标注在配置类上时:满足条件时,类中的所有标注了@Bean的对象才会注册入容器

示例

​ 根据操作系统,决定是否将组件注册入容器

​ 编写两个Condition实现类,分别对应Windows操作系统与Linux操作系统

//LinuxCondition
public class LinuxCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //从上下文对象得到环境变量
        Environment environment = context.getEnvironment();
        //得到key为os.name的属性
        String osName = environment.getProperty("os.name");
        //判断
        return osName.contains("Linux");    }
}

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

​ 在配置类中编写两个标注了@Bean的方法,并且标注Conditional,指定注册入容器的条件

@Configuration
public class MySpringConfig { 
    //Windows系统时放入id=Bill的Person对象
	@Conditional({WindowsCondition.class})
    @Bean("Bill")
    public Person person02(){
        return new Person("Bill Gates",70);
    }
    
	//Linux系统时放入id=Linus的对象
    @Conditional({LinuxCondition.class})
    @Bean("Linus")
    public Person person03(){
        return new Person("Linus",50);
    }
}

​ 运行测试类,在默认环境下发现只将Bill放入了容器

生命周期

​ bean的生命周期:

​ Bean创建——初始化——销毁

​ 可以自定义初始化和销毁方法,容器会在Bean进行到相应的生命周期时调用对应的方法。

自定义Bean生命周期

①通过@Bean的属性指定

​ 要加入容器的实体类:

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

    public void init(){
        System.out.println("Car... ... init...");
    }

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

​ 在配置类中加入Bean,并且通过@Bean的对应属性指定初始化与销毁方法

@Configuration
public class MyConfigOfLifeCycle {

    @Bean(initMethod = "init", destroyMethod = "destroy")
    public Car car(){
        return new Car();
    }
}

​ 运行测试类,就会依次执行Car构造方法、初始化方法。但是默认并不会执行销毁操作。

​ 这是因为销毁操作只在容器被关闭的时候会执行。

@Test
public void test01(){
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfigOfLifeCycle.class);
    //调用close方法,触发销毁操作
    context.close();
}

​ 如果在配置类中,对方法标注了**@Scope(“prototype”)**,则创建与初始化只会在进行获取Bean时才触发,不获取则不触发,且此时的close方法执行时,并不会触发销毁操作,需要手动调用销毁。

②通过InitializingBean、DisposableBean接口

​ 编写实现了InitializingBean, DisposableBean接口的类

public class Cat implements InitializingBean, DisposableBean {
    public Cat(){
        System.out.println("cat... ... constructor");
    }

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

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

​ 在配置类中调用

@Bean
public Cat cat(){
    return new Cat();
}

InitializingBean提供的**afterPropertiesSet()**方法即初始化方法

DisposableBean提供的**destroy()**方法即销毁方法

通过@PostConstruct与@PostConstruct注解

注两个解都是JSR250提供的注解

**@PostConstruct:**标注出初始化方法

@PreDestroy: 标注出销毁方法

//为了方便,这里直接使用@Component加入容器,记得要在配置类中扫描对应的包
@Component
public class Bird {
    public Bird(){
        System.out.println("bird constructor");
    }
    //在对象创建后执行
    @PostConstruct
    public void init(){
        System.out.println("bird init");
    }
    //在对象被从容器中销毁前执行
    @PreDestroy
    public void destroy(){
        System.out.println("bird destroy");
    }

}
BeanPostProcessor接口

在Spring底层也有对BeanPostProcessor的使用:如对bean赋值时、注入其他组件时、@AutoWired、生命周期中的注解功能、@Async (异步)等。

@Component
public class MyPostProcessor implements BeanPostProcessor {
    
    //在执行初始化操作前触发
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization " + beanName + "==>" + bean.getClass());
        return bean;
    }

    //在执行完初始化操作之后触发
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization " + beanName + "==>" + bean.getClass());

        return bean;
    }
}

postProcessBeforeInitialization:在初始化操作之前触发该方法

postProcessAfterInitialization:在初始化操作之后触发该方法

属性赋值( @Value )

通过@Value注解赋值主要有三种方法:

	1. 直接赋值

 		2. 通过SpEL表达式**#{}**赋值
             		3. 通过**${}**从配置文件取值
public class Person {

    //直接赋值
    @Value("张三")
    private String name;
    
    //通过SpEL表达式赋值
    @Value("#{20-3}")
    private Integer age;
    
	//通过${}赋值,从引入的配置文件中,通过key取value
    @Value("${person.nickName}")
    private String nickName;
    
    /* Getter and Setter */
}

​ 前两种赋值方法可以直接使用,而通过${}从配置文件中取值时,必须引入配置文件(通过配置类引入):

​ @PropertySource():可以在其中放入String数组以指定多个配置文件,也可以多次使用该注解(即该注解可重用)

@Configuration
//引入类路径下的person.properties文件
@PropertySource("classpath:person.properties")
public class ConfigOfProperty {
    @Bean
    public Person person(){
        return new Person();
    }
}

自动装配

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

1)、@AutoWired

通过在需要自动装配的属性上(或其对应的set方法)加@AutoWired注解

  1. 默认优先按照类型去容器寻找对应的组件

  2. 如果找到多个类型相同的组件,则将标注了@AutoWired的属性名称作为组件id去容器中寻找。

  3. 可以使用**@Qualifier**(“xxx”) 指定想要自动装配的组件id

    1. 自动装配默认必须给属性赋值,否则会报错(可用**@AutoWired(required=false)**使其默认不自动装配)
  4. 通过给想要被用于注入的组件标注**@Primary**,可以使该组件被作为装配的首选Bean

  5. @Qualifier与@Primary不能同时使用

    正常@AutoWired的使用:

    @Repository
    public class PersonDao {
    }
    
    @Repository
    public class PersonService {
    
        @Autowired
        private PersonDao personDao;
        /* Getter and Setter */
    }
    
    //将两个包下的注解扫描入容器
    @ComponentScan({"org.fall.dao","org.fall.service"})
    @Configuration
    public class ConfigOfAutoWired {
    
    }
    
    //测试类
    public class MainTest_AutoWire {
    
        @Test
        public void test01(){
            ApplicationContext context = new AnnotationConfigApplicationContext(ConfigOfAutoWired.class);
            System.out.println(context.getBean("personService"));
        }
    }
    

    输出结果:

    PersonService{personDao=org.fall.dao.PersonDao@1ed1993a}

    @AutoWired标注在set方法上

@Component
public class Boss {
    private Car car;

	@AutoWired
    public void setCar(Car car) {
        this.car = car;
    }

}

​ @AutoWired标注在有参构造器上(标注在构造器上的@AutoWired可以省略,因为当类中只有一个有参构造器时,默认会从IOC容器中寻找对应的组件,注入到参数中)

//@Component
public class Boss {


    private Car car;

    @Autowired
    public Boss(Car car) {
        this.car = car;
        System.out.println("调用Boss参构造器");
    }
	/** Getter and Setter **/
}

​ @AutoWired标注在@Bean+返回值形式的方法上(也可以省略@AutoWired,会从容器中寻找对应组件注入到传参中):

@Configuration
public class ConfigOfAutoWired {

    @Bean
    public Car car(){
        return new Car();
    }

    @Bean
    @AutoWired
    public Boss boss(Car car){
        Boss boss = new Boss();
        boss.setCar(car);
        return boss;
    }
}

​ @AutoWired也可以标注在传参的前面(测试时发现标注在set方法的参数前面未生效)

@Bean
public Boss boss(@AutoWired Car car){
    Boss boss = new Boss();
    boss.setCar(car);
    return boss;
}

2)、@Resource&@Inject

@Resource根据JSR250规范;@Inject根据JSR330规范

@Resource:

​ 其用途与@AutoWired相同,但是不支持@Primary注解,默认根据属性名寻找注入组件,但是可以通过内部的name属性,指定想要注入的组件的id;同样不支持required=false设置。

@Inject:

​ 需要导入javax.inject包,用途与@AutoWired相同,支持@Primary注解,但不支持required=false设置。

3)、通过Aware接口注入Spring底层组件

​ 自定义组件想要使用Spring底层的一些组件时(如ApplicationContext、BeanFactory等),可以通过自定义组件实现具体的xxxAware接口,然后实现接口的方法,可以达到注入底层组件的目的。

如下,实现了ApplicationContextAware, BeanNameAware两个接口的Flower:

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

    private String beanName;
    private ApplicationContext context;

    //BeanNameAware接口的方法
    @Override
    public void setBeanName(String name) {
        System.out.println("Flower的beanName为:" + name);
        this.beanName = name;
    }

    //ApplicationContextAware接口的方法
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
        System.out.println("Flower的applicationContext为:" + applicationContext);
    }
    //EmbeddedValueResolverAwared接口的方法,可以通过传参的resolveStringValue方法,在其中使用${}/#{}表达式
    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        System.out.println(resolver.resolveStringValue("#{30-5}"));
    }
}

会在项目启动时,自动通过两个set方法,给该组件的两个属性注入值,setApplicationContext()注入的context就是创建的ioc容器的context。

注:这些xxxAware接口,注入底层组件的方法,其传入的参数都是由接口对应的xxxAwareProcessor(都是BeanPostProcessor的实现类)来提供的。

xxxAwareProcessor源码解析

调试观察源码(以ApplicationContextAware为例):

ApplicationContextAwareProcessor.postProcessBeforeInitialization()

	@Override
	@Nullable
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        //如果不是下列xxxAware之一的实例,则进入判断
		if (!(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
				bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
				bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)){
			return bean;
		}

		AccessControlContext acc = null;

		if (System.getSecurityManager() != null) {
			acc = this.applicationContext.getBeanFactory().getAccessControlContext();
		}

		if (acc != null) {
			AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
				invokeAwareInterfaces(bean);
				return null;
			}, acc);
		}
        
        //进入else,执行invokeAwareInterfaces(bean)
		else {
			invokeAwareInterfaces(bean);
		}
		return bean;
	}

ApplicationContextAwareProcessor.invokeAwareInterfaces()

//依次判断是否是某一个接口的实例,如果是,就执行接口定义的set方法
private void invokeAwareInterfaces(Object bean) {
   if (bean instanceof EnvironmentAware) {
      ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
   }
   if (bean instanceof EmbeddedValueResolverAware) {
      ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
   }
   if (bean instanceof ResourceLoaderAware) {
      ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
   }
   if (bean instanceof ApplicationEventPublisherAware) {
      ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
   }
   if (bean instanceof MessageSourceAware) {
      ((MessageSourceAware) bean).setMessageSource(this.applicationContext);
   }
    //这里因为实现了ApplicationAware接口,因此调用其set方法,传入ApplicationContext
   if (bean instanceof ApplicationContextAware) {
      ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
   }
}

4)、@Profile 根据环境注入

​ Profile:Spring提供的使可以根据当前环境,动态激活与切换一系列组件的功能

@Profile:指定组件在哪个环境的情况下才能被注册到容器中,不标注则任何环境下都能注册这个组件

​ 标注在@Bean方法上:只有对应环境激活时,才会注册到容器中,@Profile不带括号,表示默认的default环境

​ 标注在配置类上:只有对应环境激活时,整个配置类中的配置才会生效。

示例

​ 下面以通过@Profile控制不同环境(测试、开发、生产环境)使用的数据源信息:

​ 同时演示了对属性进行注入的几种方法。

//导入database.properties配置文件
@PropertySource("classpath:database.properties")
@Configuration
public class ConfigOfProfile implements EmbeddedValueResolverAware {

    //${}表达式,从配置文件中取值
    @Value("${db.userName}")
    private String user;

    private String driverClassName;

    //标注为test环境下生效
    @Profile("test")
    @Bean("dbTest")
    public DataSource dataSourceTest(@Value("${db.password}") String pwd){
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setUsername(user);
        dataSource.setPassword(pwd);
        //test环境连接class
        dataSource.setUrl("jdbc:mysql://localhost:3306/class?serverTimezone=UTC");
        dataSource.setDriverClassName(driverClassName);
        return dataSource;
    }

    //标注为dev环境时生效
    @Profile("dev")
    @Bean("dbDev")
    public DataSource dataSourceDev(@Value("${db.password}") String pwd){
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setUsername(user);
        dataSource.setPassword(pwd);
        //dev环境连接dogdb
        dataSource.setUrl("jdbc:mysql://localhost:3306/dogdb?serverTimezone=UTC");
        dataSource.setDriverClassName(driverClassName);
        return dataSource;
    }

    //标注为prod环境时生效
    @Profile("prod")
    @Bean("dbProd")
    //通过@Value+${}表达式放在传参前赋值
    public DataSource dataSourceProd(@Value("${db.password}") String pwd){
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setUsername(user);
        dataSource.setPassword(pwd);
        //prod环境连接student
        dataSource.setUrl("jdbc:mysql://localhost:3306/student?serverTimezone=UTC");
        dataSource.setDriverClassName(driverClassName);
        return dataSource;
    }

    //通过实现EmbeddedValueResolverAware接口,用其resolveStringValue方法,解析${}表达式
    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        driverClassName = resolver.resolveStringValue("${db.driverClassName}");
    }
}

​ database.properties:

db.userName=root
db.password=root
db.driverClassName=com.mysql.cj.jdbc.Driver

​ 测试是否生效:

​ 在测试时,有两种方法改变环境变量中的profile:

 		1. 通过命令行参数传入(或编译器中的VM -options):**-Dspring.profiles.active=test**
             		2. 通过代码激活环境: **addActiveProfile("xxx")** 
@Test
public void test02(){
    //先使用无参构造创建对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    //得到环境变量,并通过addActiveProfile()设置激活的环境
    context.getEnvironment().addActiveProfile("dev");
    //通过register注册主配置类
    context.register(ConfigOfProfile.class);
    //启动刷新容器
    context.refresh();
    //遍历打印容器中的组件
    String[] names = context.getBeanDefinitionNames();
    for (String name : names) {
        System.out.println(name);
    }
}

AOP:面向切面编程

​ 需要导入的包:spring-aspects

<!-- AOP需要导入的包 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>

AOP中的重要注解:

​ @Aspect:标注在类前,表示该类为通知类(是下面各种通知注解的前提)。

​ @Before():标注在方法前,表示前置通知

​ @After():标注在方法前,表示后置通知

​ @AfterReturning():标注在方法前,表示返回通知

​ @AfterThrowing():标注在方法前,表示异常通知

​ @Around:标注在方法前,表示环绕通知

​ @Pointcut():标注在方法上,设置了其value值(被操作方法全名)后,可以通过调用该方法方法名,来调用操作方法。

@EnableAspectJAutoProxy:标注在配置类上,表示开启AOP支持(使用AOP时必须标注)

几个通知注解中的属性:

​ **pointcut:**指定要操作的方法的全名,并用execution(xxx)存放。

​ **returning:**返回通知中的属性,returning的值就是该方法传参中对应返回值参数的参数名。

​ **throwing:**异常通知中的属性,throwing的值就是该方法传参中对应触发的异常类型的参数名。

JoinPoint

​ 放在通知方法的传参中(必须放在第一个位置),可以通过该对象,得到方法名、参数列表等属性。

基础的AOP代码示例:

通知类:

//必须标注@Aspect,标出是通知类
@Aspect
public class LogAspects {

    @Pointcut(value = "execution(public int org.fall.aop.MathCalculator.*(..))")
    private void pointCut(){}

    //前置通知
    @Before("execution(public int org.fall.aop.MathCalculator.*(..)))")
    public void logStart(JoinPoint joinPoint){
        System.out.println("方法【" + joinPoint.getSignature().getName() + "】的 @Before...参数列表:"+Arrays.asList(joinPoint.getArgs()) );
    }

    //后置通知
    @After("pointCut()")
    public void logEnd(JoinPoint joinPoint){
        System.out.println("方法【"+ joinPoint.getSignature().getName() +"】的 @After...");
    }

    //返回通知
    @AfterReturning(pointcut = "pointCut()",returning = "result")
    public void logReturn(JoinPoint joinPoint,Object result){
        System.out.println("方法【"+ joinPoint.getSignature().getName() +"】的 @AfterReturning...返回值为:" + result);
    }

    //异常通知
    @AfterThrowing(pointcut = "pointCut()",throwing = "exception")
    public void logException(JoinPoint joinPoint,Exception exception){
        System.out.println("方法【"+ joinPoint.getSignature().getName() +"】的 @AfterThrowing...异常:" + exception);
    }

被操作的方法:

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

配置类:

@EnableAspectJAutoProxy //开启对注解形式AOP的支持
@Configuration
public class ConfigOfAOP {

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

    @Bean
    public LogAspects logAspects(){
        return new LogAspects();
    }
}

​ 在测试类中测试,可以发现依次==执行的顺序==:

正常运行:@Before–>@AfterReturning–>@After

触发异常:@Before–>@AfterThrowing–>@After

声明式事务

以数据库操作为例,使用声明式事务(即触发异常时,已发生的数据库操作应该回滚,而不是仍然让其发生)

配置类中:

  1. 将数据源放入容器
  2. 将JdbcTemplate组件放入容器
  3. 将事务管理器注册入容器
  4. 必须在配置类上标注**@EnableTransactionManagement**

想要使用这些注解,前提必须引入spring-tx依赖(其他的如数据库驱动、dbcp连接池等也要引入)

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>

​ 配置类:

//必须的注解,作用相当于配置文件中的<tx:annotation-driven/>
@EnableTransactionManagement
//扫描放在tx包中的dao、service
@ComponentScan("org.fall.tx")
@Configuration
public class ConfigOfTx {

    //数据源
    @Bean
    public DataSource dataSource(){
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/student?serverTimezone=UTC");
        return dataSource;
    }

    //Spring提供的快捷操作数据库的组件
    @Bean
    public JdbcTemplate jdbcTemplate(){
        return new JdbcTemplate(dataSource());
    }

    //必须将事务管理器注册到容器中
    @Bean
    public PlatformTransactionManager transactionManager(){
        return new DataSourceTransactionManager(dataSource());
    }

}

​ Dao层:

@Repository
public class PersonDao {
	//自动注入JdbcTemplate
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void insert(){
        String sql = "insert into person(name,age) VALUES(?,?)";
        //生成随机name
        String name = UUID.randomUUID().toString().substring(0,5);
        //生成随机age
        int age = (int)(Math.random()*100);
        //使用update方法,传入sql语句 操作数据库
        jdbcTemplate.update(sql,name,age);
    }
}

​ Service层:

一般建议将@Transactional标注在Service层的方法上

@Service
public class PersonService {

    @Autowired
    PersonDao personDao;
    
	//标注@Transactional,表示此方法为事务方法。
    @Transactional
    public void insert(){
        personDao.insert();
        System.out.println("操作完成... ...");
        //触发异常时,应该回滚
        System.out.println(1/0);
    }
}
测试类中测试时:
public class MainTest_TX {

    @Test
    public void test01(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigOfTx.class);
        PersonService personService = context.getBean(PersonService.class);
        personService.insert();
    }
}

可以发现,当未触发异常时,数据库的插入操作正常进行了,触发异常后,数据库操作发生了回滚。

Spring扩展原理

1)、BeanFactoryPostProcessor

​ BeanFactoryPostProcessor是beanFactory的后置处理器;

​ 在BeanFactory标准初始化后调用,用来定制和修改BeanFactory的内容;

​ 此时所有的bean定义已经被加载保存到beanFactory中,但是bean实例还未创建。

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor  {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        String[] names = configurableListableBeanFactory.getBeanDefinitionNames();
        System.out.println("MyBeanFactoryPostProcessor.postProcessBeanFactory()打印工厂中的bean:");
        for(String name : names){
            System.out.println(name);
        }
        System.out.println("打印结束");
    }
}

2)、BeanDefinitionRegistryPostProcessor

BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor:

​ 继承自BeanFactoryPostProcessor;

​ 发生在所有的bean的定义信息将要被加载的时候。

​ 也就是说BeanDefinitionRegistryPostProcessor优先于BeanFactoryPostProcessor;

​ 其中的postProcessBeanDefinitionRegistry方法又优先于postProcessBeanFactory方法;

​ 可以在postProcessBeanDefinitionRegistry()方法中,利用BeanDefinitionRegistry给容器中添加一些组件等。

@Component
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        System.out.println("MyBeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry(),bean数量是:" + beanDefinitionRegistry.getBeanDefinitionCount());
        RootBeanDefinition beanDefinition = new RootBeanDefinition(Red.class);
        beanDefinitionRegistry.registerBeanDefinition("registryBean",beanDefinition);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        System.out.println("MyBeanDefinitionRegistryPostProcessor.postProcessBeanFactory(),bean数量是:" + configurableListableBeanFactory.getBeanDefinitionCount());
    }
}

​ BeanFactoryPostProcessor与BeanDefinitionRegistryPostProcessor中,各方法的执行顺序:

BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry -->

BeanDefinitionRegistryPostProcessor.postProcessBeanFactory -->

BeanFactoryPostProcessor.postProcessBeanFactory

​ Spring底层中,先寻找所有的BeanDefinitionRegistryPostProcessor,依次执行postProcessBeanDefinitionRegistry方法、postProcessBeanFactory方法; 再寻找所有的BeanFactoryPostProcessor,执行其postProcessBeanFactory方法。

3)、ApplicationListener

​ 一、通过实现ApplicationListener接口,来监听发生的事件,并自定义操作。

@Component
//实现接口,泛型中的类就是要监听的事件(必须是ApplicationEvent或其子类)
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {
    //onApplicationEvent方法就是在事件发生时从触发的方法。
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("事件发生: " + event);
    }
}

【事件发布流程】:

​ publishEvent(new ContextRefreshedEvent(this));

​ 获取事件的多播器(派发器):getApplicationEventMulticaster()

​ multicastEvent派发事件:

​ 获取到所有的ApplicationListener;

​ for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {

​ 1)、如果有Executor,可以支持使用Executor进行异步派发;

​ Executor executor = getTaskExecutor();

​ 2)、否则,同步的方式直接执行listener方法;invokeListener(listener, event);

​ 拿到listener回调onApplicationEvent方法;

​ 二、通过**@EventListener注解**,监听发生的事件,并操作。

@Component
public class MyAnnotationListener {
    @EventListener(classes = {ApplicationEvent.class})
    public void listen(ApplicationEvent event){
        System.out.println("MyAnnotationListener监听到的事件:" + event);
    }
}

自己发布事件:

@Test
public void test02(){
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyExtConfig.class);
    //自己发布一个事件:通过context的publishEvent方法传入一个ApplicationEvent对象。
    context.publishEvent(new ApplicationEvent("my event") {
    });
    context.close();
}

注解方式启动SpringMVC

需要在MAVEN中引入的依赖

    <groupId>org.fall</groupId>
    <artifactId>springmvc-annotation</artifactId>
    <version>1.0-SNAPSHOT</version>
	<!--设置打包方式为war-->
    <packaging>war</packaging>

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

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

<!--引入插件 maven-war-plugin -->
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-war-plugin</artifactId>
            <version>3.2.3</version>
            <configuration>
                <failOnMissingWebXml>false</failOnMissingWebXml>
            </configuration>
        </plugin>
    </plugins>
</build>

​ 最初始方式(创建配置的类,但是未进行具体配置)

//主容器
@ComponentScan(value = "org.fall",excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes ={Controller.class})
})
public class RootConfig {
}
//子容器
@ComponentScan(value = "org.fall",includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
},useDefaultFilters = false)
public class AppConfig {
}

​ 在web容器启动时创建对象,调用方法来初始化容器(前端控制器)

AbstractAnnotationConfigDispatcherServletInitializer自动被加载,负责应用程序中 servlet 上下文中的 DispatcherServlet 和 Spring 其他上下文的配置

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    //获取根容器配置类(相当于Spring配置文件)  父容器
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{RootConfig.class};
    }

    //获取web容器的配置类(相当于SpringMVC配置文件)  子容器
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{AppConfig.class};
    }

    //获取DispatcherServlet的映射信息
    // /  :拦截所有请求,包括静态资源但是不包括*.jsp
    // /* :拦截所有请求,且包括*.jsp
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

​ 此时启动项目就可以使用了:如访问 hello

@Controller
public class MyController {

    @Autowired
    private MyService myService;

    @RequestMapping("/hello")
    @ResponseBody
    public String hello(){
        String hello = myService.sayHello("fall");
        return hello;
    }

}

具体使用配置类来代替xml配置文件配置SpringMVC

RootConfig不变;MyWebAppInitializer不变

主要看AppConfig:

//子容器
@ComponentScan(value = "org.fall",includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
},useDefaultFilters = false)
@EnableWebMvc
//在Spring4.x版本中,建议继承WebMvcConfigurerAdapter
//但是 Spring5.x或Spring Boot2.x版本后,WebMvcConfigurerAdapter被标注为过时,
//这是因为在Java 8中,可以使用default为接口添加默认方法,WebMvcConfigurer也添加了默认方法
//因此不再需要继承WebMvcConfigurerAdapter(此类只是空实现了接口的所有方法)
//而是可以直接实现WebMvcConfigurer接口
public class AppConfig implements WebMvcConfigurer {

    //通过实现WebMvcConfigurer接口,定制SpringMVC

    //设置静态资源访问
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        //enable,可以使mvc解析不了的,交给默认servlet解析。
        configurer.enable();
    }


    //添加拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
    }

    //设置视图解析器
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("WEB-INF/views/", ".jsp");
    }
}

​ 配置完成后,可以正常访问静态资源,拦截器生效,且Controller转发到正确的页面。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值