本节是 Spring Boot基础,主要介绍了Spring的核心技术。主要包括了Spring历史、Spring容器和Spring AOP技术,如果您对此部分已有了解可越过本节的学习。
一、Spring 的历史
Java EE 是企业应用需求的体现,而Spring 则基于企业应用井非全是分布式这个前提,进一步简化了Java EE 的开发,Spring 奠基作者Rod Johnson 在2003 年出版的《Expert One-On-One Java EE Design and Development 》一书中首次提到Spring 框架,它并不像当时的Java EE 一样给了开发人员诸多限制。
- 2004 年, Spring 又推出了IoC (反向控制) 和AOP (面向切面编程),使得Spring 成为非常受欢迎的框架,开发企业应用的另外一种轻量级选择。
- 2005 年, Spring 成立了独立的公司,公司最早的名字是interface21 ,现在经过一系列并购,称为Pivotal ,全心全意维护Spring 框架,同时,大量的社区人员参与进来,商业公司也参与了Spring 的开发。
Pivotal 是EMC 、VMware 、GE 共同成立的公司,目前估值为28 亿美元, Spring的开发人员基本上都是这个公司雇佣人员,一些知名的开源产品的顶级开发者也成为该公司的雇佣人员。知名的Red 怡、RabbitMQ 也属于这个公司。
如今Spring 框架己经发展成为一个庞大的开源帝国,包含了多款开源软件,主要有:
-
Spring Framework
-
Spring Boot
-
Spring Data 系列
-
Spring Cloud 系列
还有很多未在这里一一列出,有兴趣的读者可以进一步参考Spring 文档。
Spring Cloud 是现在炙手可热的技术,是一个基于Spring Boot 实现的云应用开发工具,它为基于JVM 的云应用开发中的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。可以说Spring Boot 和Spring Cloud 是一对黄金搭档,也是Spring 团队重点的开发方向。市场上实现的微服务系统架构,主要是联合这两种技术。
二、Spring 容器介绍
1、Spring loC
是指容器控制程序对象之间的关系,而不是传统实现中,由程序代码直接操控。控制权由应用代码中转到了外部容器,控制权的转移是所谓反转。 对于Spring而言,就是由Spring来控制对象的生命周期和对象之间的关系;IoC还有另外一个名字——“依赖注入(Dependency Injection)”。从名字上理解,所谓依赖注入,即组件之间的依赖关系由容器在运行期决定,即由容器动态地将某种依赖关系注入到组件之中。
相对于Bean 直接构造依赖的对象, Spring 框架则根据Bean之间的依赖关系创建对象,并注入到Bean 中。对于传统的企业应用代码,以下是一种常见的写法:
public UserServiceimpl implements UserService {
private CreditUserService creditUserService =new CreditUserService();
public void order( .... ) {
creditUserService.addCredit(S);
}
}
如果creditUserService 本身构造方式比较复杂,而且是个单例对象,那么会使用工厂类来实现进一步优化,比如:
private CreditUserService creditUserService = CreditUserServiceFactory.getService();
或者更先进一点的做法:
private CreditUserService creditUserService = ServiceFactory.getService(CreditUserService.class);
ServiceFactory 是一个工厂类,根据传入的类名,返回一个己经初始化好的实例。
在Spring 框架中,如果你的Bean 是通过Spring 来管理的, Spring 容器则会帮你完成这些事情,开发人员需要做的仅仅是声明依赖关系,因此代码如下所示:
@Service
public CreditUserServiceimpl implements CreditUserService {
public void addCredit(int score) {
}
}
@Service
public UserService {
@Autowired
private CreditUserService creditUserService
public void order( .... ) {
creditUserService.addCredit(S);
}
}
- Bean 通过注解@ Service 声明为一个Spring 管理的Bean, Spring 容器会扫描classpath 下的所有类,找到带有@ Service 注解的UserService 类,并根据Spring 注解对其进行初始化和增强,如果发现此类属性creditUserService 也有注解@Autowired ,则会从Spring 容器里查找一个己经初始化好的CreditUserService ,如果没有,则先初始化CreditUserService 。
- Spring 的容器构造和管理Bean 远远比这个例子复杂,但作为Spring Boot 应用开发者,不需要了解这么深入,Spring 容器把简单留给了开发者,作为一个Spring 新手,只需要对这些概念有所了解即可。
如何成功吸引Spring 容器的注意而“有幸”成为容器管理的Bean ?在Spring Boot 中就是依靠注解,如在类上的注解@Controller 、@Service 、@Configuration 等, 方法上的注解@Bean 。
还有一种吸引Spring 容器注意的办法是实现Spring 的某些接口类, Spring 会在容器的生命周期里调用这些接口类,比如BeanPostProcessor 接口就是在所有容器管理Bean 初始化后,会调用此接口实现,对Bean 进行进一步的配置,比如Spring 的AutowiredAnnotationBeanPostProcessor 会寻找Bean 里的@Autowired 注解来注入依赖Bean 。
2、Spring 常用注解
Spring 提供了多个注解声明Bean 为Spring 管理的Bean ,注解不同代表的含义不一样,但对于Spring 容器来说,都是Spring 管理的Bean 。
-
Controller :声明此类是一个MVC 类,通常与@RequestMapping 一起使用。
@Controller @RequestMapping ("/user") public class UserController { @RequestMapping ("/get/{id}") public String getUser(@PathVariable String id) { } }
-
Service : 声明此类是一个业务处理类,通常与@Transactional 一起配合使用。
@Service
@Transactional
public Class UserServiceimpl implements UserService{
public void order( .. ) {
}
}
- Repository : 声明此类是一个数据库或者其他NoSQL 出问类。
@Repository
public class UserDao implements CrudDao<User , String> {
}
- RestController : 同Controller,用于REST 服务。
- Component :声明此类是一个Spring 管理的类,通常用于无法用上述注解描述的Spring管理类。
- Configuration : 声明此类是一个配置类,通常与注解@ Bean 配合使用。
@Configuration
public class DataSourceConfig {
@Bean(name = "dataSource")
public DataSource datasource(Environment env) {
HikariDataSource ds =new HikariDataSource();
ds.setJdbcUrl(env.getProperty("spring.daatasource.url")) ;
ds.setUsername(env.getProperty ("spring.datasource.username"));
...
return ds ;
}
}
如上示例, DataSourceConfig 是一个Spring 容器配置类,配置了HikariDataSource。
- Bean : 作用在方法上,声明该方法执行的返回结果是一个Spring 容器管理的Bean , 参考@Configuration 示例。
Spring 负责实例化Bean ,开发者可以提供一系列回调函数,用于进一步配置Bean ,包括@PostConstruct 注解和@PreDestory 注解。
当Bean 被容器初始化后,会调用@PostConstruct 的注解方法:
@Component
public class ExampleBean {
@PostConstruct
public void init() {}
}
@PreDestory :在容器被销毁之前,调用被@PreDestory 注解的方法。
@Service
public class ExampleBean {
@PreDestory
public void cleanup() {}
}
由于时传统原因Spring 还提供了其他Bean生命周期的回调方式,指定初始化和销毁的方法,以及可以用实现InitializingBean 接口的afterPropertiesSet ( )来初始化Bean 和实现DisposableBean 的destroy( )方法来销毁Bean 。
Spring 有两种方式来引用容器管理的Bean ,一种是根据名字,为每个管理的Bean 指定一个名字,随后可以通过名字引用此Bean。
@Service
@Qualifier ("exampleBean")
public class ExampleBean {
}
这样在其他Bean 中,可以使用注解@Qualifier 来引用,比如:
@Service
public AnotherExampleBean{
@Qualifier ("exampleBean") ExampleBean bean;
}
另外一种是根据类型,使用起来更加简单。上面的例子可以改写成:
@Service
public class ExampleBean {
}
@Service
public AnotherExampleBean{
@Autowried ExampleBean bean;
}
在一个Spring 管理的Bean 中,开发人员可以通过@Autowired 声明对其他Bean 的引用,作用于属性或者构造函数参数,甚至是方法调用参数上。
三、Spring AOP介绍
1、初步理解
AOP (Aspect-Oriented P rogramming ,面向切面编程)提供了另外一种思路来实现应用系统的公共服务。AOP通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP 之所以能得到广泛认可,主要是因为它将应用系统拆分分了2个部分:核心业务逻辑(Core business concerns)及横向的通用逻辑,也就是所谓的切面Crosscutting enterprise concerns。例如,所有大中
型应用都要涉及到的持久化管理(Persistent)、事务管理(TransactionManagement)、权限管理(Privilege Management)、日志管理(Logging)和调试管理(Debugging)等。使用AOP 技术,可以让开发人员只专注核心业务,而通用逻辑则使用AOP 技术进行横向切入,由专人去处理这些通用逻辑,会使得任务简单明了,提高开发和调试的效率。
举个具体例子理解:
我们一般做活动的时候,一般对每一个接口都会做活动的有效性校验(是否开始、是否结束等等)、以及这个接口是不是需要用户登录。
按照正常的逻辑,我们可以这么做。
第1版
这有个问题就是,有多少接口,就要多少次代码copy。对于一个“懒人”,这是不可容忍的。好,提出一个公共方法,每个接口都来调用这个接口。这里有点切面的味道了。
第2版
同样有个问题,我虽然不用每次都copy代码了,但是,每个接口总得要调用这个方法吧。于是就有了切面的概念,我将方法注入到接口调用的某个地方(切点)。
这样接口只需要关心具体的业务,而不需要关注其他非该接口关注的逻辑或处理。
红框处,就是面向切面编程
2、几个概念
[外链图片转存失败(img-DALqWRkL-1568603857649)(C:\Users\wangxf\AppData\Roaming\Typora\typora-user-images\1568272032621.png)]
-
切面(Aspect):
Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
-
连接点(Join Point)
所有可能的需要注入切面的地方。如方法前后、类初始化、属性初始化前后等等。典型的包括方法调用、对类成员的访问, 以及异常处理程序块的执行等。Spring 中的Joint point 只支持方法调用。
-
Pointcut(切点):
表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
-
Advice(增强):
Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
o before : 在执行方法前调用Advice ,比如cache 功能可以在执行方法前先判断是否有缓存。
o around :在执行方法前后调用Advice , 这是Spring 框架和应用系统一种最为常用的方法, 本书第1 章用户权限的实现就是采用的around 。
o after : 在执行方法后调用Advice, after return 是方法正常返回后调用, after throw 是方法抛出异常后调 用。
o finally :方法调用后执行Advice , 无论是否抛出异常还是正常返回。
-
Target(目标对象):
织入 Advice 的目标对象.。
-
Weaving(织入):
将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程。
AOP 代理(AOP Proxy):
AOP 框架创建的对象,包含通知。在Spring中,AOP 代理可以是JDK 动态代理或CGLIB 代理。
3、Spring Boot 中使用AOP
首先,创建一个Spring Boot 简单应用。
其次,引入AOP 依赖, 在porm 文件中添加spring-boot-starter-aop
<dependency> <groupid>org.springframework.boot</groupid> <artifactid>srping-boot-starter-aop</artifactid> </dependency>
再次, 编写一个AOP 切面类:
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Aspect; import org.springframework.context.annotation.Configuration; @Configuration @Aspect public class AOPConfig { @Around ("@within(org.springframework.stereotype.Controller)" ) public Object simpleAop(final ProceedingJoinPoint pjp) throws Throwable { try { Object[] args = pjp.getArgs(); System.out.println ("args:"+Arrays.asList(args)) ; //调用原有的方法 Object o = pjp.proceed(); System.out.println ("return:" +o) ; return o ;
对于这段代码,可以按照以下顺序理解:
( 1 ) @Configuration ,用于声明这是一个Spring 管理配置Bean ,这里是引起Spring 的注意,好比一群人里有一个人带了一个很高的帽子,更能吸引人注意。
(2) @Aspect ,声明了这是一个切面类。
(3) @Around ,声明了一个表达式,描述要织入的目标的特性,比如@within 表示目标类型带有注解,其注解类型参数为org. spring framework.stereotype . Control !e r ,这意味着SpringController 方法在被调用的时候,都会执行@ Around 注解的方法,也就是simpleAop 。
( 4 ) simpleAop 是用来织入的代码, 其参数称为Proceeding JoinPoint , 如上述代码实例,将调用的方法的参数取出来,打印到控制台。
(5 ) 创pjp.proceed(),通常情况下,执行完切面代码,还需要继续执行应用代码, proceed()方法则会继续调用原有的业务逻辅,并将返回对象正常返回。
继续执行应用代码,有可能抛出异常,在切面里,我们不会处理这个异常, 直接抛出给调用者。
我们访问http://127.0.0.1:8080 /sayhello.html ?name=a 的时候,查看控制台,会发现有如下输出:
args: [a]
return :hello world
说明织入的代码被执行了。
Sping AOP 支持多种表达式及表达式的组合,这里列出一些简单的表达式以供参考,更多的表达式需要参考Spring 官网文档。
execution( public (..)): 所有publie 方法,后面的星号代表类路径和方法名。
execution( set*(…)):所有set 开头的方法。
execution( publie set( … )):所有set 开头的publie 方法。
execution( publie com.xyz.service.set(..)):所有set 开头的public 方法,且位于
com.xyz.坦问ice 包下。
target(com.xyz.service.CommonService):所有实现了CommonService 接口的类的方
法。
• @target (org.springframework. transaction.annotation.Transactional):所有用
@Transactional 注解的方法。
@within(org.springframework. stereotype.Controlle ): 类型声明了@Controller的所
有方法。
第一节 Spring Boot 2 精髓学习笔记(一)
第二节 Spring Boot 2 精髓学习笔记(二)—Maven技术
第三节 Spring Boot 2 精髓学习笔记(三)—Spring 核心技术