<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>4.3.11.RELEASEversion>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>servlet-apiartifactId>
<version>3.0-alpha-1version>
<scope>providedscope>
dependency>
早期的spring使用最多的是配置文件,现在的spring都是用配置类,用来代替配置文件:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<context:property-placeholder location="classpath:person.properties"/>
<bean id="person" class="cn.klb.bean.Person" scope="prototype" >
<property name="age" value="${}">property>
<property name="name" value="zhangsan">property>
bean>
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
beans>
使用配置类效果如下:
@Configuration
public class MainConfig {
@Bean("person")
public Person person01(){
return new Person("lisi", 20);
}
}
配置文件有什么,配置类就可以配置什么,用来代替配置文件的功能。
配置文件使用以下语句来包扫描:
<context:component-scan base-package="cn.klb" use-default-filters="false">context:component-scan>
那么配置类就用注解来包扫描:
@Configuration
@ComponentScan("cn.klb",useDefaultFilters = false)
public class MainConfig2 {
// ...
}
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.type.filter.TypeFilter;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
boolean useDefaultFilters() default true;
Filter[] includeFilters() default {};
Filter[] excludeFilters() default {};
boolean lazyInit() default false;
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
}
默认会扫描该被注解的类所在的包下所有的配置类,相当于之前的
。
value:等同于属性basePackages,指定要扫描的包,如果没有取值,则默认就是被注解的类所在的包;
basePackageClasses:类型安全的basePackages替代方法,用于指定扫描带注释组件的包。扫描指定的每个类所在的包。
includeFilters:指定扫描的时候按照什么规则排除哪些组件;
excludeFilters:指定扫描的时候只需要包含哪些组件;
includeFilters和excludeFilters的取值是@Filter
,其源码如下:
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
type的取值有:
FilterType.ANNOTATION:按照注解;
FilterType.ASSIGNABLE_TYPE:按照给定的类型;
FilterType.CUSTOM:按照自定义规则
FilterType.ASPECTJ:按照ASOECTJ表达式;
FilterType.REGEX:按照正则指定;
使用示例:
@ComponentScan(useDefaultFilters = false,
includeFilters = {
@Filter(type = FilterType.ANNOTATION,classes = {Controller.class}),
@Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {BookService.class}),
@Filter(type = FilterType.CUSTOM,classes = MyTypeFilter.class)
})
public class MainConfig {
//...
}
需要注意的是,要关闭默认的过滤规则,否则自定义的失效。
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
@AliasFor("name")
String[] value() default {};
@AliasFor("value")
String[] name() default {};
Autowire autowire() default Autowire.NO;
String initMethod() default "";
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}
@Target
说明了@Bean
注解可以作用在方法和注解上,产生一个Bean对象,然后这个Bean对象交给Spring管理。它用于显式声明单个bean,而不是让Spring像@Component
那样自动执行它。它将bean的声明与类定义分离,并允许您精确地创建和配置bean。
value:等同于name,定义Bean的名称,如果有多个名称,则为一个主bean名称加上别名。如果未指定,bean的名称就是被注解的方法的名称;
autowire:判断依赖关系是否通过基于约定的名字或类型自动装配注入的;
initMethod:指定创建Bean时的初始化方法;
destroyMethod:指定销毁Bean时的销毁方法。
在组件的定义类中写好初始化方法和销毁方法,然后在注册组件时,给@Bean
注解的initMethod
属性和destroyMethod
赋值方法名即可。例如:
@Bean(value = "person",initMethod = "init",destroyMethod = "destroy")
public Person person01(){
return new Person("lisi", 20);
}
使用接口InitializingBean
和DisposableBean
。
首先组件实现这两个接口,然后重写接口方法:
public class Blue implements InitializingBean, DisposableBean {
public Blue(){
System.out.println("Blue..Constructor...");
}
public void destroy() throws Exception {
System.out.println("Blue..destroy..");
}
public void afterPropertiesSet() throws Exception {
System.out.println("Blue...afterPropertiesSet...");
}
}
注册组件时,spring会自动识别实现了这两个接口的方法,进行初始化和销毁:
@Configuration
@ComponentScan("cn.klb")
public class MainConfig {
@Bean("blue")
public Blue getBlue(){
return new Blue();
}
}
JSR250是java规范,里面有两个注解@PostConstruct
和@PreDestroy
,使用起来很简单,就是在组件定义类中的初始化方法加上@PostConstruct
注解,构造器执行完后会执行该方法;@PreDestroy
作用在销毁方法上,容器销毁之前会调用该方法。例如:
public class Car {
public Car() {
System.out.println("Car..Construct..");
}
@PreDestroy
public void destroy(){
System.out.println("Car..PreDestroy...");
}
@PostConstruct
public void init() throws Exception {
System.out.println("Car..PostConstruct..");
}
}
注册组件时,spring会自动识别实现了这两个注解作用的方法,进行初始化和销毁:
```java
@Configuration
@ComponentScan("cn.klb")
public class MainConfig {
@Bean("car")
public Car getCar(){
return new Car();
}
}
package org.springframework.beans.factory.config;
import org.springframework.beans.BeansException;
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
BeanPostProcessor
是一个接口,也称为Bean后置处理器,该接口有两个方法:
postProcessBeforeInitialization
:在初始化之前进行后置处理操作;
postProcessAfterInitialization
:在初始化之后进行后置处理操作;
实现了这个接口的实现类作为一个组件注册到IOC容器中,然后spring会识别出这个组件,并执行这两个方法。是所有组件初始化的基础。
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Before.."+bean+"->"+beanName);
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("After.."+bean+"->"+beanName);
return bean;
}
}
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
String value() default "";
}
@Target(ElementType.TYPE)
表示@Configuration
可以作用在类、接口(包括注解类型) 或enum声明,被注解的类称为配置类,内部包含有一个或多个被@Bean
注解的方法,这些方法将会被AnnotationConfigApplicationContext
或AnnotationConfigWebApplicationContext
类进行扫描,并用于构建bean定义,初始化Spring容器。
value:显式指定与此配置类关联的Spring bean定义的名称。如果不指定(常见情况),将自动生成一个bean名称。只有当配置类通过组件扫描获取或直接提供给AnnotationConfigApplicationContext
时,自定义名称才会应用。如果配置类注册为传统的XML bean定义,则bean元素的名称/id将优先。
package org.springframework.stereotype;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Component {
String value() default "";
}
用于自动检测和使用类路径扫描自动配置bean。被@Component
注解的类和bean之间存在隐式的一对一映射(即每个类一个bean)。这种方法对需要进行逻辑处理的控制非常有限,因为它纯粹是声明性的。
value:该值可以指示逻辑组件名称的建议,以便在自动检测组件的情况下转换为Spring bean。
等价于@Component
,主要是为了增强代码可读性,作用在业务层(service)。
等价于@Component
,主要是为了增强代码可读性,作用在表现层(web)。
等价于@Component
,主要是为了增强代码可读性,作用在持久层(dao)。
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
满足条件才会生成bean注册到IOC容器中。
value:条件,要求是继承抽象类Condition
并且实现matches
方法。
要使用@Condition
注解,主要工作是定义一个类继承Condition
,举例如下:
定义条件类并实现matches方法:
public class MyCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String property = environment.getProperty("os.name");
return property.contains("Windows");
//return property.contains("Linux");
}
}
然后使用赋值:
@Bean("klb")
@Conditional(MyCondition.class)
public Person getPerson01(){
return new Person("klb",12);
}
当matches方法的返回值为true时,名称为”klb“的bean才会注册到IOC容器中。
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Primary {
}
作用在注册Bean的地方(类、方法等),使得Spring进行自动装配的时候,默认首选的bean。这个注解主要是针对存在2个以上同个类型的bean的情况。
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lazy {
boolean value() default true;
}
可以作用在类、方法、构造器和元素上,被注解的组件可以选择延迟加载操作,延迟加载意思是该bean不会随着IOC容器创建和马上创建,直等到要用到这个bean时再创建它。
value:是否启动延迟加载,默认值为开启。
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
@AliasFor("scopeName")
String value() default "";
@AliasFor("value")
String scopeName() default "";
ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}
springIoc容器中的一个作用域,在 Spring IoC 容器中具有以下几种作用域:基本作用域singleton(单例)、prototype(多例),Web 作用域(reqeust、session、globalsession)
可以作用在类和方法上。
scopeName:等同于value,指定要为带注释的组件/bean使用的范围的名称;取值有:
ConfigurableBeanFactory.SCOPE_PROTOTYPE
:表示bean是单例的;
ConfigurableBeanFactory.SCOPE_SINGLETON
:表示bean是多实例的;
WebApplicationContext.SCOPE_REQUEST
:web作用域request;
WebApplicationContext.SCOPE_SESSION
:web作用域session。
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
Class<?>[] value();
}
可以作用在类、接口、注解或枚举上,快速给容器导入一个组件。
value:Class类型数组,可以有三种取值,分别为:
Configuration
:要导入IOC容器中的组件,容器中就会自动注册这个组件,组件的id默认为全类名;
ImportSelector
:一个选择器,其实就是实现了ImportSelector
接口的类,重写selectImports
方法返回要导入的组件的全类名;
ImportBeanDefinitionRegistrar
:一个Bean注册器,其实就是实现了ImportBeanDefinitionRegistrar
接口的类,重写registerBeanDefinitions
方法中注册要导入的组件。
定义三个组件:
package cn.klb.bean;
public class Car {
// ...
}
package cn.klb.bean;
public class Color {
// ...
}
package cn.klb.bean;
public class Rainbow {
// ...
}
定义一个ImportSelector
选择器:
package cn.klb.selector;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
/**
* @Author: Konglibin
* @Description:
* @Date: Create in 2020/5/13 18:48
* @Modified By:
*/
public class MySelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
String[] s = {"cn.klb.bean.Color"};
return s;
}
}
定义一个ImportBeanDefinitionRegistrar
Bean注册器:
package cn.klb.registrar;
import cn.klb.bean.RainAndBow;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
/**
* @Author: Konglibin
* @Description:
* @Date: Create in 2020/5/13 20:20
* @Modified By:
*/
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 要注册组件的前提条件(自定义)
boolean hasPerson = registry.containsBeanDefinition("person");
if(hasPerson){
// 定义待注册Bean
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Rainbow.class);
// 注册Bean,并且这个Bean的id(或者叫名字)为 rainbow
registry.registerBeanDefinition("rainbow",rootBeanDefinition);
}
}
}
快速导入组件:
@Import({Car.class,MySelector.class, MyImportBeanDefinitionRegistrar.class})
public class MainConfig {
// ...
}
除了直接在方法上加上@Bean
注解来注册组件,Spring还提供FactoryBean
来注册。
首先是实现FactoryBean
接口并实现接口方法:
public class MyFactoryBean implements FactoryBean<Person> {
public Person getObject() throws Exception {
System.out.println("MyFactoryBean..getObject");
return new Person();
}
public Class<?> getObjectType() {
return Person.class;
}
public boolean isSingleton() {
return true;
}
}
然后在被@Configuration
作用的配置类中添加:
@Configuration
public class MainConfig {
@Bean
public MyFactoryBean factoryBean(){
return new MyFactoryBean();
}
}
Spring会识别出这个Bean是一个工厂,所以注册的时候不会把它的类型当成MyFactoryBean
,而是它所生产出来的Person
类型。如果我们就是要Spring把它看成普通的Bean,只需要在获取Bean的时候加一个&
符号,使用示例:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
// factoryBean 类型为 Person
Object factoryBean = context.getBean("factoryBean");
// factoryBean 类型为 MyFactoryBean
Object factoryBean = context.getBean("&factoryBean");
package org.springframework.beans.factory.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {
String value();
}
给组件的属性赋值,有三种方式:基本数值、SpEL表达式、${}取出配置文件的值。
value,取值为我们要赋值的值。
首先在配置类中使用@PropertySource
来指定配置文件的名称。
@Configuration
@PropertySource("classpath:/person.properties")
public class MainConfig2 {
@Bean("person")
public Person getPerson(){
return new Person();
}
}
自定义组件:
public class Person {
@Value("klb")
private String name;
@Value("#{20-3}")
private Integer age;
@Value("${person.hobby}")
private String hobby;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", hobby='" + hobby + '\'' +
'}';
}
}
由于配置类是早于Bean创建,所以在初始化Person组件时,${person.hobby}
已经可以读取到值了。
package org.springframework.beans.factory.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
实现对组件的依赖关系赋值,Spring检测到被@Autowired
注解的组件时,优先按照类型去容器找对应的组件,如果找到多个相同类型的组件,再将待赋值的变量的名称作为组件的id去容器中查找。
required:被作用的组件是否一定要被赋值,默认是true。如果设置为false,当在IOC容器中找不到可以赋值的组件时,赋值为null;true时,如果找不到可赋值的组件会抛出异常。
Spring要实现自动注入时,优先选择同类型的组件,如果我们要选择特定的组件来赋值,可以使用@Qualifier
注解:
public class Color {
@Qualifier("ylo")
@Autowired
private Yello yello;
// ...
}
这样Spring会从容器中找到名字为ylo的bean来执行赋值。
@Resource
和@Inject
分别是JSR250和JSR330规范下的注解,属于Java原生注解,不支持指定方式赋值,而且@Inject
还要导入javax.inject
包才能使用,如果使用spring框架,不建议使用这两个注解。
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.ConfigurableEnvironment;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
String[] value();
}
指定组件在哪个环境下才被注册到容器中。不指定则默认任何环境都把组件注册到容器中。如果作用在配置类中,那么在指定环境下配置类才会生效。没有环境标识的Bean在任何环境下都生效。
value:环境的名称。默认是default环境。
在虚拟机参数位置加载:
-Dspring.profiles.active=test
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.getEnvironment().setActiveProfiles("test");
context.register(MainConfig3.class);
context.refresh();
@Autowired
除了可以作用在组件的属性上,还可以作用在方法参数、构造器上。
作用在方法上时,直接从IOC容器找组件来赋值,默认不写@Autowired
,以下三种写法是一样的:
@Bean("red")
public Red getRed(Yello yello){
return new Red(yello);
}
@Bean("red")
@Autowired
public Red getRed(Yello yello){
return new Red(yello);
}
@Bean("red")
public Red getRed(@Autowired Yello yello){
return new Red(yello);
}
作用在构造器上时,Spring会从IOC容器中找到组件来给构造器的参数赋值,如果组件只有一个有参构造器,那么@Autowired
可以省略不写。
@Component("red")
public class Red {
private Yello yello;
public Yello getYello() {
return yello;
}
public void setYello(Yello yello) {
this.yello = yello;
}
// 注解省略也可以生效,前提是只有一个有参构造
// @Autowired
public Red(Yello yello) {
this.yello = yello;
}
}
如果我们自定义的组件要使用Spring底层的组件,比如ApplicationContext
、BeanFactory
等等,可以通过实现响应接口来进行使用。
比如我要使用ApplicationContext
组件,那么我们就实现ApplicationContextAware
接口,并实现它的方法;又或者我要使用BeanName
这个组件,那么就实现BeanNameAware
接口;又或者我要使用EmbeddedValueResolver
组件,就实现EmbeddedValueResolverAware
接口。举例:
@Component
public class Red implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("applicationContext:" + applicationContext);
this.applicationContext = applicationContext;
}
public void setBeanName(String name) {
System.out.println("当前bean的名字:" + name);
}
public void setEmbeddedValueResolver(StringValueResolver resolver) {
String s = resolver.resolveStringValue("你好${os.name},我是#{3*6}");
System.out.println(s);
}
}
Spring底层组件肯定是在我们自定义组件注册之前就存在了,因此可以通过实现xxxAware
接口来调用Spring底层组件。
每一个xxxAware
都是由一个xxxAwareProcessor
来处理。而xxxAwareProcessor
是后置处理器。
1、导入AOP依赖:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
<version>4.3.12.RELEASEversion>
dependency>
2、定义业务逻辑类(被增强):
package cn.klb.aop;
public class MathCalculator {
public int div(int i,int j){
System.out.println("MathCalculator...div...");
return i/j;
}
}
3、定义日志切面类:
package cn.klb.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import java.util.Arrays;
/**
* @Author: Konglibin
* @Description:
* @Date: Create in 2020/5/15 19:43
* @Modified By:
*/
@Aspect
public class LogAspect {
@Pointcut("execution(public int cn.klb.aop.MathCalculator.*(..))")
public void pointCut() {
}
;
@Before("pointCut()")
public void logStart(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String name = joinPoint.getSignature().getName();
System.out.println("@Before...name=" + name + ",args={" + Arrays.asList(args) + "}");
}
@After("cn.klb.aop.LogAspect.pointCut()")
public void logEnd(JoinPoint joinPoint) {
System.out.println("@After...");
}
@AfterReturning(value = "pointCut()", returning = "result")
public void logReturn(JoinPoint joinPoint, Object result) {
System.out.println("@AfterReturning...result=" + result);
}
@AfterThrowing(value = "pointCut()", throwing = "exception")
public void logException(JoinPoint joinPoint, Exception exception) {
System.out.println("@AfterThrowing...exception=" + exception);
}
}
4、配置类中将业务逻辑组件和切面类都加入到容器中:
package cn.klb.config;
import cn.klb.aop.LogAspect;
import cn.klb.aop.MathCalculator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* @Author: Konglibin
* @Description:
* @Date: Create in 2020/5/15 19:17
* @Modified By:
*/
@Configuration
@EnableAspectJAutoProxy
public class MainConfigOfAOP {
@Bean
public MathCalculator calculator(){
return new MathCalculator();
}
@Bean
public LogAspect logAspect(){
return new LogAspect();
}
}
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false;
boolean exposeProxy() default false;
}
作用在配置类上,告诉Spring开启AOP注解支持。
package org.aspectj.lang.annotation;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Aspect {
public String value() default "";
}
告诉spring,被这个注解作用的类是一个切面类,而不是一个普通bean组件。
value:每个子句表达式,默认为单例切面。
package org.aspectj.lang.annotation;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Before {
String value();
String argNames() default "";
}
作用在方法上,被注解的方法是前置通知,在目标方法运行之前运行。
value:指定切入点表达式,即目标方法;
argNames:用于获取切入点表达式的参数;
package org.aspectj.lang.annotation;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface After {
String value();
String argNames() default "";
}
作用在方法上,被注解的方法是后置通知,在目标方法运行结束之后运行(无论方法正常结束还是异常结束)。
value:指定切入点表达式;
argNames:获取切入点表达式的参数;
package org.aspectj.lang.annotation;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterReturning {
String value() default "";
String pointcut() default "";
String returning() default "";
String argNames() default "";
}
作用在方法上,被注解的方法是返回通知,在目标方法正常返回(目标方法异常就不会执行)之后运行。
value:指定切入点表达式;
pointcut:也是指定切入点表达式,如果设置了,就覆盖value属性;
returning:目标方法的返回值绑定到返回通知的参数中;
argNames:获取切入点表达式的参数。
package org.aspectj.lang.annotation;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterThrowing {
String value() default "";
String pointcut() default "";
String throwing() default "";
String argNames() default "";
}
作用在方法上,被注解的方法是异常通知,在目标方法出现异常以后运行(如果没有异常则不运行)。
value:指定切入点表达式;
pointcut:也是指定切入点表达式,如果设置了,就覆盖value属性;
throwing:目标方法的异常绑定到异常通知的参数中;
argNames:获取切入点表达式的参数。
package org.aspectj.lang.annotation;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Pointcut {
String value() default "";
String argNames() default "";
}
作用在方法上,被注解的方法等价于切入点表达式,其他通知需要指定切入点表达式时引用被@Pointcut
作用的方法的方法名即可。
value:指定切入点表达式;
argNames:获取切入点表达式的参数。
首先,在配置类中添加事务管理器TransactionManager
,同时使用注解@EnableTransactionManagement
开启事务支持:
@Configuration
@EnableTransactionManagement
@ComponentScan({"cn.klb.dao", "cn.klb.service"})
public class MainConfigOfTx {
@Bean
public DataSource dataSource() throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("root");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/db1");
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) throws Exception {
return new DataSourceTransactionManager(dataSource);
}
}
然后,在需要事务管理的地方用@Transactional
注解:
@Service("bookservice")
public class BookService {
@Autowired
public BookDao bookdao;
@Transactional
public void insert(){
bookdao.insert();
System.out.println("插入完成");
int a = 3/0;
}
}
package org.springframework.transaction.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Import;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({TransactionManagementConfigurationSelector.class})
public @interface EnableTransactionManagement {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default 2147483647;
}
作用在配置类上,告诉spring开启事务支持功能,同时必须在容器中注册事务管理器,同时这个事务管理器注入数据源。
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) throws Exception {
return new DataSourceTransactionManager(dataSource);
}
package org.springframework.transaction.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
作用在方法上,被注释的方法整体变为一个事务,当出现异常时,所有数据库操作都回滚,不会生效。
value:等同于属性transactionManager,指定事务管理器;
propagation:事务的传播行为,默认为“新建事务”;
isolation:事务的隔离级别,默认为“默认级别”;
timeout:超时时间,默认值-1,表示没有超时限制。如果赋值了,则以秒为单位;
readOnly:是否只读事务,默认值为false。(建议查询时设置为只读);
rollbackFor:
rollbackForClassName:
noRollbackFor:
noRollbackForClassName:
Servlet容器启动的时候,会扫描当前应用里面每一个jar包的ServletContainerInitializer
的实现,具体说是每一个jar包里面的META-INF/services/路径下的有javax.servlet.ServletContainerInitializer名字的文件,里面保存着ServletContainerInitializer
实现类的全限定类名。
比如我现在有个类实现了ServletContainerInitializer
:
public class MyServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
}
}
javax.servlet.ServletContainerInitializer文件内容为:
cn.klb.servlet.MyServletContainerInitializer
Servlet启动后就扫描到这个实现类,可以在实现类上注释@HandlesTypes
这个注解,注解的属性就是我们想传入的感兴趣的类型。如:
@HandlesTypes(value = {UserService.class})
public class MyServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
System.out.println("感兴趣的类型:");
for (Class<?> claz : set) {
System.out.println(claz);
}
}
}
那么,onStartup
方法的set参数就可以获取UserService
接口的实现类、子接口等。
比如现在有一个实现了UserService
接口的实现类HelloService
,启动tomcat可以看到:
首先,定义三个组件:
public class UserFilter implements Filter {
// ...
}
public class UserListener implements ServletContextListener {
// ...
}
public class UserServlet extends HttpServlet {
// ...
}
然后,在ServletContainerInitializer
中手动注册:
public class MyServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
ServletRegistration.Dynamic userServlet = servletContext.addServlet("userServlet", new UserServlet());
userServlet.addMapping("/user");
FilterRegistration.Dynamic userFilter = servletContext.addFilter("userFilter", new UserFilter());
userFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
servletContext.addListener(UserListener.class);
}
}
web容器在启动的时候,会扫描每个jar包下的META-INF/services/javax.servlet.ServletContainerInitializer,然后加载这个文件指定的类SpringServletContainerInitializer
。
我们导入spring-webmvc依赖后,会有个间接依赖:spring-web:
文件内容为:
org.springframework.web.SpringServletContainerInitializer
找到这个类:
package org.springframework.web;
import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer) waiClass.newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
也就是说,servlet容器启动后,会加载感兴趣的WebApplicationInitializer
接口的下的所有组件;并且为WebApplicationInitializer
组件创建对象(组件不是接口,不是抽象类)。
找到WebApplicationInitializer
实现类:
这三个实现类是继承关系。
定位到AbstractContextLoaderInitializer
:
package org.springframework.web.context;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.web.WebApplicationInitializer;
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
// ...
protected abstract WebApplicationContext createRootApplicationContext();
// ...
}
AbstractContextLoaderInitializer
从名字看出它叫做“抽象容器加载初始化器”,里面唯一的抽象方法是createRootApplicationContext()
,用于创建根容器。
从名字可以看出它叫做“抽象DispatcherServlet初始化器”,继承了上面的AbstractContextLoaderInitializer
,定位到源码:
package org.springframework.web.servlet.support;
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterRegistration;
import javax.servlet.FilterRegistration.Dynamic;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.core.Conventions;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.AbstractContextLoaderInitializer;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.FrameworkServlet;
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
// ...
protected abstract WebApplicationContext createServletApplicationContext();
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
return new DispatcherServlet(servletAppContext);
}
// 注册 DispatcherServlet 到 ServletContext 中
protected void registerDispatcherServlet(ServletContext servletContext) {
// ...
}
protected abstract String[] getServletMappings();
// ...
}
抽象方法createServletApplicationContext()
用于创建一个web的ioc容器,createDispatcherServlet()
方法创建了DispatcherServlet,并且注册到ServletContext
中。
抽象注解配置DispatcherServlet初始化器,继承了上面的AbstractDispatcherServletInitializer
,定位到源码:
package org.springframework.web.servlet.support;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
public abstract class AbstractAnnotationConfigDispatcherServletInitializer
extends AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(configClasses);
return rootAppContext;
}
else {
return null;
}
}
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
servletAppContext.register(configClasses);
}
return servletAppContext;
}
protected abstract Class<?>[] getRootConfigClasses();
protected abstract Class<?>[] getServletConfigClasses();
}
它实现父类方法createRootApplicationContext()
来创建根容器,方面里面调用了getRootConfigClasses()
来获取配置类,所以我们只需要重写getRootConfigClasses()
方法,把我们自定义的配置类返回即可,根容器就会获得我们的配置类。
它实现父类方法createServletApplicationContext
,用于创建web的IOC容器,里面调用了getServletConfigClasses()
方法,同样是获取配置类。
如果我们以注解的方式来启动SpringMVC,那么就继承AbstractAnnotationConfigDispatcherServletInitializer
,然后实现抽象方法来指定配置类。
这个就是spring和servlet整合的关键,继承了这个抽象类后,servlet容器会扫描到实现类,同时spring的配置类也读取到了。
最关键的就是ServletContext
注册到了spring的IOC容器中。然后spring就可以为所欲为了。
ServletContext
注册到了spring的IOC容器中,所以SpringMVC可以定制很多功能。
首先是在自定义的配置类中开启WebMvc功能:
并且实现WebMvcConfigurer
接口,为了方便,SpringMVC做了一个适配器WebMvcConfigurerAdapter
,对接口的所有方法做了空实现,然后我们继承这个适配器,要定制什么方法就去重写即可,不用重写WebMvcConfigurer
的所有方法。
@EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {
// ...
}
参考:https://docs.spring.io/spring/docs/5.3.0-SNAPSHOT/spring-framework-reference/web.html#mvc-config