Spring
简介
Spring 就像一个大家族,有众多衍生产品例如 Boot,Security,JPA等等。但他们的基础都是Spring 的 IOC 和 AOP,IOC提供了依赖注入的容器,而AOP解决了面向切面的编程,然后在此两者的基础上实现了其他衍生产品的高级功能。
Spring MVC是基于 Servlet 的一个 MVC 框架,主要解决 WEB 开发的问题;
而Spring Boot 是基于Spring的一套快速开发整合包,Spring Boot遵循的也是约定优于配置原则,它的目的在于实现自动配置,降低项目搭建的复杂度;
Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。
Spring是一个轻量级的**控制反转(IoC)和面向切面(AOP)**的容器框架。
Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。Spring的核心是控制反转(IoC)和面向切面(AOP)。简单来说,Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架。
初衷
1、JAVA EE开发应该更加简单。
2、使用接口而不是使用类,是更好的编程习惯。Spring将使用接口的复杂度几乎降低到了零。
3、为JavaBean提供了一个更好的应用配置框架。
4、更多地强调面向对象的设计,而不是现行的技术如JAVA EE。
5、尽量减少不必要的异常捕捉。
6、使应用程序更加容易测试。
目标
1、可以令人方便愉快的使用Spring。
2、应用程序代码并不依赖于Spring APIs。
3、Spring不和现有的解决方案竞争,而是致力于将它们融合在一起。
基本组成
1、最完善的轻量级核心框架。
2、通用的事务管理抽象层。
3、JDBC抽象层。
4、集成了Toplink, Hibernate, JDO, and iBATIS SQL Maps。
5、AOP功能。
6、灵活的MVC Web应用框架。
发展
Spring框架几乎涉及到了Java企业级服务开发的所有方面,也几乎针对所有开发常用的模式、中间件、数据库进行了整合适配。
当我们写一个业务把逻辑写死写出来是比较容易的,但是把这个逻辑提取成模式进而打包成一个框架来给大家使用,这是比较难的。因为我们只有经历过足够多的场景后才能提取出普适的功能框架,大部分人才能用上,而且我们需要针对核心功能开放出可配置的部分,满足小部分人进一步定制和扩展功能的需要。
Spring框架经历了几个阶段:
1.第一个阶段推出的Core、Security、Data是把单体应用开发服务好。不仅仅提供了便捷的数据库访问、Web MVC等必要功能,而且通过AOP、IOC两大利器让我们的程序内在能够做到低耦合可扩展。
2.第二个阶段推出的Boot的意义不仅仅是加速了开发效率而且能让我们的程序从可用变为好用,应用程序核心业务逻辑可能只有70%的工作量,要让程序在线上跑的愉快还有30%的监控日志打点等工作量需要去做。
3.第三个阶段推出的Cloud的意义在于推动了微服务架构的落地。让不具备开发微服务基础套件的小型互联网公司也能享受到免费的开箱即用的微服务解决方案。其实很多人不是看了微服务的架构思想去寻找解决方案,而是了解到了Spring Cloud才去了解微服务思想从而落地的。
4.目前属于第四个阶段,大力发展Cloud Dataflow(云数据流)+容器。Dataflow的思想是不管是做实时消息处理的服务还是临时运行的任务,都可以认为是服务的组件,如果可以有一套DSL来定义这些组件之间的交互方式,然后在容器中进行自由组合、部署、伸缩,那么架构会非常灵活。下图是Dataflow管理界面的一个示意图。
Spring的发展可以看到互联网架构的发展,Spring给我们带来相当多的技术启发,从软件设计模式的启发慢慢到了架构的启发,甚至我觉得Spring是为Java开发打造了架构风格的模板,接下去Spring继续发展2到3年有望成为架构标准
在Spring4.x中增加了新的特性:如果类只提供了一个带参数的构造方法,则不需要对对其内部的属性写@Autowired注解,Spring会自动为你注入属性。
1 | //查看spring的版本 |
只要用了spring框架,肯定到处都是@Autowired。4.3之后的功能,如果只有一个构造方法,自动用这个构造方法注入配合lombok的@RequiredArgsConstructor使用体验很好:
我们平时开发中的bean大部分都不写构造函数,系统默认一个无参构造函数,这就符合这一条件。
此时helloService已经注入了,但是有些人会说以前加个@Autowired就行了,现在还要加个构造方法,更麻烦了,这时可以使用lombok插件,类上加@AllArgsConstructor就行了。
在编写代码的时候,使用@Autowired注解是,发现IDE报的一个警告,如下:
Spring Team recommends “Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies”.
翻译:
Spring建议”总是在您的bean中使用构造函数建立依赖注入。总是使用断言强制依赖”。
这段代码警告原来的写法是:
@Autowired
private EnterpriseDbService service;
建议后写成下面的样子:
private final EnterpriseDbService service;
@Autowired
public EnterpriseDbController(EnterpriseDbService service) {
this.service = service;
}
奇怪,为何会有这样的建议。
我们知道:@Autowired 可以对成员变量、方法以及构造函数进行注释。那么对成员变量和构造函数进行注释又有什么区别呢?
@Autowired注入bean,相当于在配置文件中配置bean,并且使用setter注入。而对构造函数进行注释,就相当于是使用构造函数进行依赖注入了吧。莫非是这两种注入方法的不同。
以下是:@Autowired和构造方法执行的顺序解析
先看一段代码,下面的代码能运行成功吗?
@Autowired
private User user;
private String school;
public UserAccountServiceImpl(){
this.school = user.getSchool();
}
答案是不能。
因为Java类会先执行构造方法,然后再给注解了@Autowired 的user注入值,所以在执行构造方法的时候,就会报错。
报错信息可能会像下面:
Exception in thread “main” org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘…’ defined in file [….class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate […]: Constructor threw exception; nested exception is java.lang.NullPointerException
报错信息说:创建Bean时出错,出错原因是实例化bean失败,因为bean时构造方法出错,在构造方法里抛出了空指针异常。
解决办法是,使用构造器注入,如下:
private User user;
private String school;
@Autowired
public UserAccountServiceImpl(User user){
this.user = user;
this.school = user.getSchool();
}
可以看出,使用构造器注入的方法,可以明确成员变量的加载顺序。
PS:Java变量的初始化顺序为:静态变量或静态语句块–>实例变量或初始化语句块–>构造方法–>@Autowired
@Autowired和构造方法执行的顺序解析
先看一段代码,下面的代码能运行成功吗?
@Autowired
private User user;
private String school;
public UserAccountServiceImpl(){
this.school = user.getSchool();
}
答案是不能。因为Java类会先执行构造方法,然后再给注解了@Autowired 的user注入值,所以在执行构造方法的时候,就会报错。
报错信息可能会像下面:
Exception in thread “main” org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘…’ defined in file [….class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate […]: Constructor threw exception; nested exception is java.lang.NullPointerException
报错信息说:创建Bean时出错,出错原因是实例化bean失败,因为bean时构造方法出错,在构造方法里抛出了空指针异常。
解决办法是,使用构造器注入,如下:
private User user;
private String school;
@Autowired
public UserAccountServiceImpl(User user){
this.user = user;
this.school = user.getSchool();
}
那么最开始Spring建议,为何要将成员变量加上final类型呢?
网上有解释如下:spring配置默认的bean的scope是singleton,也就是启动后一直有。通过设置bean的scope属性为prototype来声明该对象为动态创建。但是,如果你的service本身是singleton,注入只执行一次。
@Autowired本身就是单例模式,只会在程序启动时执行一次,即使不定义final也不会初始化第二次,所以这个final是没有意义的吧。
可能是为了防止,在程序运行的时候,又执行了一遍构造函数;
或者是更容易让人理解的意思,加上final只会在程序启动的时候初始化一次,并且在程序运行的时候不会再改变。
@Autowired
@Autowired 注释可以在 setter 方法中被用于自动连接 bean,就像 @Autowired 注释,容器,一个属性或者任意命名的可能带有多个参数的方法。
可以在属性中使用 @Autowired 注释来除去 setter 方法。当时使用 为自动连接属性传递的时候,Spring 会将这些传递过来的值或者引用自动分配给那些属性
@Component
踩到一个坑,有一个接口,在这个接口的实现类里,需要用到@Autowired注解,一时大意,没有在实现类上加上@Component注解,导致了Spring报错,找不到这个类
一旦使用关于Spring的注解出现在类里,例如我在实现类中用到了@Autowired注解,被注解的这个类是从Spring容器中取出来的,那调用的实现类也需要被Spring容器管理,加上@Component
1 |
|
介绍
开发中难免会遇到这个这个注解@Component
@Controller 控制器(注入服务)
用于标注控制层,相当于struts中的action层
@Service 服务(注入dao)
用于标注服务层,主要用来进行业务的逻辑处理
@Repository(实现dao访问)
用于标注数据访问层,也可以说用于标注数据访问组件,即DAO组件
@Component (把普通pojo实例化到spring容器中,相当于配置文件中的 )
泛指各种组件,就是说当我们的类不属于各种归类的时候(不属于@Controller、@Services等的时候),我们就可以使用@Component来标注这个类。
@ExceptionHandler
- 异常处理方式一. @ExceptionHandler
- 异常处理方式二. 实现HandlerExceptionResolver接口
- 异常处理方式三. @ControllerAdvice+@ExceptionHandler
spring的启动
Spring 是一个控制反转依赖管理的容器,作为 Java Web 的开发人员,基本没有不熟悉 Spring 技术栈的,尽管在依赖注入领域,Java Web 领域不乏其他优秀的框架,如 google 开源的依赖管理框架 guice,如 Jersey web 框架等。但 Spring 已经是 Java Web 领域使用最多,应用最广泛的 Java 框架。
此文将专注讲解如何在 Spring 容器启动时实现我们自己想要实现的逻辑。我们时常会遇到在 Spring 启动的时候必须完成一些初始化的操作,如创建定时任务,创建连接池等。
本文将介绍以下几种 Spring 启动监听方式:
- Bean 构造函数方式
- 使用 @PostConstruct 注解
- 实现 InitializingBean 接口
- 监听 ApplicationListener 事件
- 使用 Constructor 注入方式
- 实现 SpringBoot 的 CommandLineRunner 接口
- SmartLifecycle 机制
spring装填bean
我们知道如果我们要在一个类使用spring提供的bean对象,我们需要把这个类注入到spring容器中,交给spring容器进行管理,但是在实际当中,我们往往会碰到在一个普通的Java类中,想直接使用spring提供的其他对象或者说有一些不需要交给spring管理,但是需要用到spring里的一些对象。如果这是spring框架的独立应用程序,我们通过
ApplicationContext ac = new FileSystemXmlApplicationContext(“applicationContext.xml”);
ac.getBean(“beanId”);
这样的方式就可以很轻易的获取我们所需要的对象。
但是往往我们所做的都是Web Application,这时我们启动spring容器是通过在web.xml文件中配置,这样就不适合使用上面的方式在普通类去获取对象了,因为这样做就相当于加载了两次spring容器,而我们想是否可以通过在启动web服务器的时候,就把Application放在某一个类中,我们通过这个类在获取,这样就可以在普通类获取spring bean对象了,让我们接着往下看
普通类调用Spring bean对象:
可以参考:http://412887952-qq-com.iteye.com/blog/1479445
这里有更多这方面的介绍,比较详细
下面介绍在springboot中是如何使用的
1.在Spring Boot可以扫描的包下
写的工具类为SpringUtil,实现ApplicationContextAware接口,并加入Component注解,让spring扫描到该bean
springutil:
1 | package me.shijunjie.util; |
为了测试,我们再启动的时候先通过代码方式给spring容器中注入一个bean,入下所示
1 | package me.shijunjie.config; |
然后我们编写测试controller,并从刚才写的springutil中获取这个bean
1 | package me.shijunjie.controller; |
测试
启动web应用,打开浏览器输入http://localhost:8080/application/test1,测试成功
2 不在Spring Boot的扫描包下方式一
这种情况处理起来也很简单,先编写SpringUtil类,同样需要实现接口:ApplicationContextAware,具体编码如下:
1 | package me.shijunjie.util; |
使用@Bean注解,在App.java类中将SpringUtil注解进
1 | package me.shijunjie.controller; |
测试(使用热部署的需要重启服务器)
启动web应用,打开浏览器输入http://localhost:8080/application/test2,测试成功
除此以外,也可以在App.java中使用@Import进行导入。
1 | package me.shijunjie.controller; |
跑出结果和上面相同
原始构造函数
如果没有 Spring 容器,不依赖于 Spring 的实现,回归 Java 类实现本身,我们可以在静态代码块,在类构造函数中实现相应的逻辑,Java 类的初始化顺序依次是静态变量
> 静态代码块
> 全局变量
> 初始化代码块
> 构造器
。
比如,Log4j 的初始化,就是在 LogManager
的静态代码块中实现的:
1 | static { |
比如在构造函数中实现相应的逻辑:
1 | @Component |
这里考验一下各位,上面的代码是否可以正常运行。—— 不行,构造函数中的env
将会发生NullPointException
异常。这是因为在 Spring 中将先初始化 Bean,也就是会先调用类的构造函数,然后才注入成员变量依赖的 Bean(@Autowired
和@Resource
注解修饰的成员变量),注意@Value
等注解的配置的注入也是在构造函数之后。
@PostConstruct
在 Spring 中,我们可以使用@PostConstruct
在 Bean 初始化之后实现相应的初始化逻辑,@PostConstruct
修饰的方法将在 Bean 初始化完成之后执行,此时 Bean 的依赖也已经注入完成,因此可以在方法中调用注入的依赖 Bean。
1 | @Component |
与@PostConstruct
相对应的,如果想在 Bean 注销时完成一些清扫工作,如关闭线程池等,可以使用@PreDestroy
注解:
1 | @Component |
InitializingBean
实现 Spring 的InitializingBean
接口同样可以实现以上在 Bean 初始化完成之后执行相应逻辑的功能,实现InitializingBean
接口,在afterPropertiesSet
方法中实现逻辑:
1 | @Component |
ApplicationListener
我们可以在 Spring 容器初始化的时候实现我们想要的初始化逻辑。这时我们就可以使用到 Spring 的初始化事件。Spring 有一套完整的事件机制,在 Spring 启动的时候,Spring 容器本身预设了很多事件,在 Spring 初始化的整个过程中在相应的节点触发相应的事件,我们可以通过监听这些事件来实现我们的初始化逻辑。Spring 的事件实现如下:
- ApplicationEvent,事件对象,由 ApplicationContext 发布,不同的实现类代表不同的事件类型。
- ApplicationListener,监听对象,任何实现了此接口的 Bean 都会收到相应的事件通知。实现了 ApplicationListener 接口之后,需要实现方法 onApplicationEvent(),在容器将所有的 Bean 都初始化完成之后,就会执行该方法。
与 Spring Context 生命周期相关的几个事件有以下几个:
- ApplicationStartingEvent: 这个事件在 Spring Boot 应用运行开始时,且进行任何处理之前发送(除了监听器和初始化器注册之外)。
- ContextRefreshedEvent: ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法来发生。
- ContextStartedEvent: 当使用 ConfigurableApplicationContext 接口中的 start() 方法启动 ApplicationContext 时,该事件被触发。你可以查询你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。
- ApplicationReadyEvent: 这个事件在任何 application/ command-line runners 调用之后发送。
- ContextClosedEvent: 当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被触发。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启。
- ContextStoppedEvent: Spring 最后完成的事件。
因此,如果我们想在 Spring 启动的时候实现一些相应的逻辑,可以找到 Spring 启动过程中符合我们需要的事件,通过监听相应的事件来完成我们的逻辑:
1 | @Component |
除了通过实现ApplicationListener
接口来监听相应的事件,Spring 的事件机制也实现了通过@EventListener
注解来监听相对应事件:
1 | @Component |
Spring Event 是一套完善的进程内事件发布订阅机制,我们除了用来监听 Spring 内置的事件,也可以使用 Spring Event 实现自定义的事件发布订阅功能。
Constructor 注入
在学习 Spring 的注入机制的时候,我们都知道 Spring 可以通过构造函数、Setter 和反射成员变量注入等方式。上面我们在成员变量上通过@Autoware
注解注入依赖 Bean,但是在 Bean 的构造函数函数中却无法使用到注入的 Bean(因为 Bean 还未注入),其实我们也是使用 Spring 的构造函数注入方式, 这也是 Spring 推荐的注入机制(在我们使用 IDEA 的时候,如果没有关闭相应的代码 Warning 机制,会发现在成员变量上的@Autoware
是黄色的,也就是 idea 不建议的代码)。Spring 更推荐构造函数注入的方式:
1 | @Component |
CommandLineRunner
如果我们的项目使用的是 Spring Boot,那么可以使用 Spring Boot 提供的 CommandLineRunner
接口来实现初始化逻辑,Spring Boot 将在启动初始化完成之后调用实现了CommandLineRunner
的接口的run
方法:
1 | @Component |
并且,多个CommandLineRunner
实现,可以通过@Order
来控制它们的执行顺序。
SmartLifecycle
还有一种更高级的方法来实现我们的逻辑。这可以 Spring 高级开发必备技能哦。SmartLifecycle 不仅仅能在初始化后执行一个逻辑,还能再关闭前执行一个逻辑,并且也可以控制多个 SmartLifecycle
的执行顺序,就像这个类名表示的一样,这是一个智能的生命周期管理接口。
- start():bean 初始化完毕后,该方法会被执行。
- stop():容器关闭后,spring 容器发现当前对象实现了 SmartLifecycle,就调用 stop(Runnable), 如果只是实现了 Lifecycle,就调用 stop()。
- isRunning:当前状态,用来判你的断组件是否在运行。
- getPhase:控制多个 SmartLifecycle 的回调顺序的,返回值越小越靠前执行 start() 方法,越靠后执行 stop() 方法。
- isAutoStartup():start 方法被执行前先看此方法返回值,返回 false 就不执行 start 方法了。
- stop(Runnable):容器关闭后,spring 容器发现当前对象实现了 SmartLifecycle,就调用 stop(Runnable), 如果只是实现了 Lifecycle,就调用 stop()。
1 | @Component |
Spring @Aspect、@Before、@After 注解实现 AOP 切面功能
Spring AOP 注解概述
1、Spring 的 AOP 功能除了在配置文件中配置一大堆的配置,比如切入点、表达式、通知等等以外,使用注解的方式更为方便快捷,特别是 Spring boot 出现以后,基本不再使用原先的 beans.xml 等配置文件了,而都推荐注解编程。
@Aspect | 切面声明,标注在类、接口(包括注解类型)或枚举上。 |
---|---|
@Pointcut | 切入点声明,即切入到哪些目标类的目标方法。 value 属性指定切入点表达式,默认为 “”,用于被通知注解引用,这样通知注解只需要关联此切入点声明即可,无需再重复写切入点表达式 |
@Before | 前置通知, 在目标方法(切入点)执行之前执行。 value 属性绑定通知的切入点表达式,可以关联切入点声明,也可以直接设置切入点表达式 注意:如果在此回调方法中抛出异常,则目标方法不会再执行,会继续执行后置通知 -> 异常通知。 |
@After | 后置通知, 在目标方法(切入点)执行之后执行 |
@AfterReturning | 返回通知, 在目标方法(切入点)返回结果之后执行,在 @After 的后面执行 pointcut 属性绑定通知的切入点表达式,优先级高于 value,默认为 “” |
@AfterThrowing | 异常通知, 在方法抛出异常之后执行, 意味着跳过返回通知 pointcut 属性绑定通知的切入点表达式,优先级高于 value,默认为 “” 注意:如果目标方法自己 try-catch 了异常,而没有继续往外抛,则不会进入此回调函数 |
@Around | 环绕通知:目标方法执行前后分别执行一些代码,类似拦截器,可以控制目标方法是否继续执行。 通常用于统计方法耗时,参数校验等等操作。 环绕通知早于前置通知,晚于返回通知 |
2、上面这些 AOP 注解都是位于如下所示的 aspectjweaver 依赖中: |
3、对于习惯了 Spring 全家桶编程的人来说,并不是需要直接引入 aspectjweaver 依赖,因为 spring-boot-starter-aop 组件默认已经引用了 aspectjweaver 来实现 AOP 功能。换句话说 Spring 的 AOP 功能就是依赖的 aspectjweaver !
1 | <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop --> |
@Aspect 快速入门
1、@Aspect 常见用于记录日志、异常集中处理、权限验证、Web 参数校验、事务处理等等
2、要想把一个类变成切面类,只需3步:
1)在类上使用 @Aspect 注解使之成为切面类
2)切面类需要交由 Sprign 容器管理,所以类上还需要有 @Service、@Repository、@Controller、@Component 等注解
2)在切面类中自定义方法接收通知
3、AOP 的含义就不再累述了,下面直接上示例:
1 | import org.apache.commons.lang3.time.StopWatch; |
如上所示在不修改原来业务层代码的基础上,就可以使用 AOP 功能,在目标方法执行前后或者异常时都能捕获然后执行。
execution 切点表达式
1、@Pointcut 切入点声明注解,以及所有的通知注解都可以通过 value 属性或者 pointcut 属性指定切入点表达式。
2、切入点表达式通过 execution 函数匹配连接点,语法:execution([方法修饰符] 返回类型 方法路径(参数类型) [异常类型])
3、切入点表达式的写法比较灵活,比如:* 号表示任意一个,.. 表示任意多个,还可以使用 &&、||、! 进行逻辑运算.实际开发中常用:
execution(* com.wmx.aspect.EmpServiceImpl.findEmpById(Integer)) | 匹配 com.wmx.aspect.EmpService 类中的 findEmpById 方法,且带有一个 Integer 类型参数。 |
---|---|
execution(* com.wmx.aspect.EmpServiceImpl.findEmpById(*)) | 匹配 com.wmx.aspect.EmpService 类中的 findEmpById 方法,且带有一个任意类型参数。 |
execution(* com.wmx.aspect.EmpServiceImpl.findEmpById(..)) | 匹配 com.wmx.aspect.EmpService 类中的 findEmpById 方法,参数不限 |
execution(* grp.basic3.se.service.SEBasAgencyService3.editAgencyInfo(..)) || execution(* grp.basic3.se.service.SEBasAgencyService3.adjustAgencyInfo(..)) | 匹配 editAgencyInfo 方法或者 adjustAgencyInfo 方法 |
execution(* com.wmx.aspect.EmpService.*(..)) | 匹配 com.wmx.aspect.EmpService 类中的任意方法 |
execution(* com.wmx.aspect..(..)) | 匹配 com.wmx.aspect 包(不含子包)下任意类中的任意方法 |
execution(* com.wmx.aspect...(..)) | 匹配 com.wmx.aspect 包及其子包下任意类中的任意方法 |
execution(* grp.pm..Controller.(..)) | 匹配 grp.pm 包下任意子孙包中以 “Controller” 结尾的类中的所有方法 |
- execution:用于匹配方法执行的连接点;
- within:用于匹配指定类型内的方法执行;
- this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
- target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
- args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;
- @within:用于匹配所以持有指定注解类型内的方法;
- @target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
- @args:用于匹配当前执行的方法传入的参数持有指定注解的执行;
- @annotation:用于匹配当前执行方法持有指定注解的方法;
SpEL表达式
SpEL(Spring Expression Language),即Spring表达式语言,是比JSP的EL更强大的一种表达式语言
它可以在运行时查询和操作数据,尤其是数组列表型数据,因此可以缩减代码量,优化代码结构
SpEL有三种用法,一种是在注解@Value中;一种是XML配置;最后一种是在代码块中使用Expression
1.@Value
1 | //@Value能修饰成员变量和方法形参 |
2.
1 | <bean id="xxx" class="com.java.XXXXX.xx"> |
- 代码块中使用
1 | import org.springframework.expression.Expression; |
4.#{…}和${…}
- #{…} 用于执行SpEl表达式,并将内容赋值给属性
- ${…} 主要用于加载外部属性文件中的值
- #{…} 和${…} 可以混合使用,但是必须
#{}外面,${}在里面
1 | // 如果属性文件没有spelDefault.value,则会报错 |
1 | // SpEL:调用字符串Hello World的concat方法 |
1 | ${...}和#{...}可以混合使用,如下文代码执行顺序:通过${server.name}从属性文件中获取值并进行替换,然后就变成了 执行SpEL表达式{‘server1,server2,server3’.split(‘,’)} |