注解形式的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注解
-
默认优先按照类型去容器寻找对应的组件
-
如果找到多个类型相同的组件,则将标注了@AutoWired的属性名称作为组件id去容器中寻找。
-
可以使用**@Qualifier**(“xxx”) 指定想要自动装配的组件id
- 自动装配默认必须给属性赋值,否则会报错(可用**@AutoWired(required=false)**使其默认不自动装配)
-
通过给想要被用于注入的组件标注**@Primary**,可以使该组件被作为装配的首选Bean
-
@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
声明式事务
以数据库操作为例,使用声明式事务(即触发异常时,已发生的数据库操作应该回滚,而不是仍然让其发生)
配置类中:
- 将数据源放入容器
- 将JdbcTemplate组件放入容器
- 将事务管理器注册入容器
- 必须在配置类上标注**@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转发到正确的页面。