为什么用Spring
什么是Spring
Spring 是一款开源的轻量级 Java 开发框架,旨在提高开发人员的开发服从以及体系的可维护性。
Spring的一个最大的目标就是使JAVA EE开发更加容易。同时,Spring之以是与Struts、Hibernate等单层框架不同,是因为Spring致力于提供一个以统一的、高效的方式构造整个应用,而且可以将单层框架以最佳的组合揉和在一起建立一个连贯的体系。可以说Spring是一个提供了更完善开发环境的一个框架,可以为POJO(Plain Ordinary Java Object)对象提供企业级的服务。
Spring的特性和上风
从Spring 框架的特性来看:
- 非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API
- 控制反转:Inversion of Control(IOC),指的是将对象的创建权交给 Spring 去创建,是一个轻量级的IOC容器。使用 Spring 之前,对象的创建都是由我们本身在代码中new创建。而使用 Spring 之后。对象的创建都是给了 Spring 框架,实现松耦合。
- 依赖注入:Dependency Injection(DI),是指依赖的对象不需要手动调用 setXX 方法去设置,而是通过设置赋值。
- 面向切面编程:Aspect Oriented Programming(AOP),把应用业务逻辑和体系服务分开,通过切面和模板减少样板式代码。
- 容器:Spring 是一个容器,它包含而且管理应用对象的生命周期
- 组件化:Spring 实现了使用简单的组件设置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。
- 声明式事务的支持:可以从单调繁冗的事务管理代码中解脱出来,通过声明式方式机动地进行事务的管理,可向下扩展到(例如使用一个单一的数据库)本地事务并扩展到全局事务(例如,使用 JTA),提高开发服从和质量。
- 一站式:在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(现实上 Spring 自身也提供了体现层的 SpringMVC 和持久层的 Spring JDBC)
从使用Spring 框架的上风看:
- Spring 可以使开发人员使用 POJOs 开发企业级的应用程序。只使用 POJOs 的好处是你不需要一个应用程序服务器,但是你可以选择使用一个健壮的 servlet 容器,好比 Tomcat 或者一些商业产品。
- Spring 在一个单元模式中是有组织的。纵然包和类的数量非常大,你只要担心你需要的,而其它的就可以忽略了。
- Spring 不会让你白费力气做重复工作,它可以整合一些现有的技术,像 ORM 框架、日记框架、JEE、Quartz 和 JDK 计时器,其他视图技术。
- 测试一个用 Spring 编写的应用程序很容易,因为环境相干的代码被移动到这个框架中。别的,通过使用 JavaBean-style POJOs,它在使用依赖注入测试数据时变得更容易。
- Spring 的 web 框架是一个设计良好的 web MVC 框架,它为好比 Structs 或者其他工程上的或者不怎么受欢迎的 web 框架提供了一个很好的供替代的选择。MVC 模式导致应用程序的不同方面(输入逻辑,业务逻辑和UI逻辑)分离,同时提供这些元素之间的松散耦合。模型(Model)封装了应用程序数据,通常它们将由 POJO 类组成。视图(View)负责渲染模型数据,一般来说它天生客户端浏览器可以表明 HTML 输出。控制器(Controller)负责处理用户请求并构建得当的模型,并将其传递给视图进行渲染。
- Spring 对 JavaEE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。
相干资料
Spring的组件
Spring5.x 版本中 Web 模块的 Portlet 组件已经被废弃掉,同时增长了用于异步响应式处理的 WebFlux 组件。
从最下层往上介绍
Spring Test
Spring 团队提倡测试驱动开发(TDD)。有了控制反转 (IoC)的资助,单元测试和集成测试变得更简单。
Spring 的测试模块对 JUnit(单元测试框架)、TestNG(类似 JUnit)、Mockito(主要用来 Mock 对象)、PowerMock(解决 Mockito 的问题好比无法模拟 final, static, private 方法)等等常用的测试框架支持的都比较好。而且还额外提供了一些基于 Spring 的测试功能,好比在测试 Web 框架时,模拟 Http 请求的功能。
包含Mock Objects, TestContext Framework, Spring MVC Test, WebTestClient。
源码对应模块如下:
Core Container
Spring 框架的核心模块,也可以说是基础模块,主要提供 IoC 依赖注入功能的支持。由 Beans 模块、Core 核心模块、Context 上下文模块和 SpEL 表达式语言模块组成,没有这些核心容器,也不可能有 AOP、Web 等上层的功能。
- spring-core:封装了 Spring 框架的底层部分,包罗资源访问、范例转换及一些常用工具类。
- spring-beans:提供对 bean 的创建、设置和管理等功能的支持,包罗控制反转和依赖注入。
- spring-context:建立在 Core 和 Beans 模块的基础之上,集成 Beans 模块功能并添加资源绑定、数据验证、国际化、Java EE 支持、容器生命周期、事故流传等。ApplicationContext 接口是上下文模块的核心。
- spring-expression:提供对表达式语言(Spring Expression Language) SpEL 的支持,只依赖于 core 模块,不依赖于其他模块,可以单独使用。支持访问和修改属性值,方法调用,支持访问及修改数组、容器和索引器,命名变量,支持算数和逻辑运算,支持从 Spring 容器获取 Bean,它也支持列表投影、选择和一般的列表聚合等。
对应源码模块如下:
AOP、Aspects、Instrumentation和Messaging
- spring-aspects:该模块为与 AspectJ 的集成提供支持,是一个功能强盛且成熟的面向切面编程(AOP)框架。
- spring-aop:提供了面向切面的编程实现。提供好比日记纪录、权限控制、性能统计等通用功能和业务逻辑分离的技术,而且能动态的把这些功能添加到需要的代码中,这样各司其职,降低业务逻辑和通用功能的耦合。
- spring-instrument:提供了为 JVM 添加代理(agent)的功能。 具体来讲,它为 Tomcat 提供了一个织入代理,可以或许为 Tomcat 传递类文 件,就像这些文件是被类加载器加载的一样。没有理解也没关系,这个模块的使用场景非常有限。
- spring-messaging:是从 Spring4.0 开始新参加的一个模块,主要职责是为 Spring 框架集成一些基础的报文传送应用。
- spring-jcl 模块: Spring 5.x中新增了日记框架集成的模块。
对应源码模块如下:
Data Access/Integration
- spring-jdbc:提供了对数据库访问的抽象 JDBC。不同的数据库都有本身独立的 API 用于操作数据库,而 Java 程序只需要和 JDBC API 交互,这样就屏蔽了数据库的影响。
- spring-tx:支持编程和声明式事务管理。
- spring-orm:提供对 Hibernate、JPA、iBatis 和 MyBatis 等 ORM 框架的支持。而且还可以使用 Spring 事务管理,无需额外控制事务。
- spring-oxm:提供一个抽象层支撑 OXM(Object-to-XML-Mapping),例如:JAXB、Castor、XMLBeans、JiBX 和 XStream 等。将 Java 对象映射成 XML 数据,或者将XML 数据映射成 Java 对象。
- spring-jms : 指 Java 消息服务,提供一套 “消息生产者、消息消费者”模板用于更加简单的使用 JMS,JMS 用于用于在两个应用程序之间,或分布式体系中发送消息,进行异步通讯。自 Spring Framework 4.1 以后,它还提供了对 spring-messaging 模块的继承。
对应源码模块:
Spring Web
- spring-web:提供了基本的 Web 开发集成特性,例如多文件上传功能、使用的 Servlet 监听器的 IOC 容器初始化以及 Web 应用上下文。
- spring-webmvc:提供了一个 Spring MVC Web 框架实现。Spring MVC 框架提供了基于注解的请求资源注入、更简单的数据绑定、数据验证等及一套非常易用的 JSP 标签,完全无缝与 Spring 其他技术协作。
- spring-websocket:提供了对 WebSocket 的支持,WebSocket 可以让客户端和服务端进行双向通讯。
- spring-webflux:提供对 WebFlux 的支持。WebFlux 是 Spring Framework 5.0 中引入的新的响应式框架。与 Spring MVC 不同,它不需要 Servlet API,是完全异步,而且通过Reactor项目实现了Reactive Streams规范。Spring WebFlux 用于创建基于事故循环执行模型的完全异步且非壅闭的应用程序。
对应源码模块如下:
Spring、SpringMVC、SpringBoot之间的关系
Spring 包含了多个功能模块(上面刚刚提到过),其中最紧张的是 Spring-Core(主要提供 IoC 依赖注入功能的支持) 模块, Spring 中的其他模块(好比 Spring MVC)的功能实现基本都需要依赖于该模块。
Spring MVC 是 Spring 中的一个很紧张的模块,主要赋予 Spring 快速构建 MVC 架构的 Web 程序的本领。MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、体现分离来组织代码。
使用 Spring 进行开发各种设置过于贫苦好比开启某些 Spring 特性时,需要用 XML 或 Java 进行显式设置。于是,Spring Boot 诞生了!
Spring 旨在简化 J2EE 企业应用程序开发。Spring Boot 旨在简化 Spring 开发(减少设置文件,开箱即用!)。
Spring Boot 只是简化了设置,如果你需要构建 MVC 架构的 Web 程序,你还是需要使用 Spring MVC 作为 MVC 框架,只是说 Spring Boot 帮你简化了 Spring MVC 的许多设置,真正做到开箱即用!
HelloWorld-xml
这里只是体现这是Spring第一个项目,以HelloWorld作为标注。现实需求是获取 用户列表信息,并打印执行日记
案例
案例源码点击这里
- <properties>
- <maven.compiler.source>8</maven.compiler.source>
- <maven.compiler.target>8</maven.compiler.target>
- <spring.version>5.3.37</spring.version>
- <aspectjweaver.version>1.9.6</aspectjweaver.version>
- </properties>
- <dependencies>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-context</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-core</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-beans</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <dependency>
- <groupId>org.aspectj</groupId>
- <artifactId>aspectjweaver</artifactId>
- <version>${aspectjweaver.version}</version>
- </dependency>
- </dependencies>
复制代码- public class User {
- private String name;
- private int age;
- public User(String name, int age) {
- this.name = name;
- this.age = age;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- }
复制代码
- DAO 获取 POJO, UserDaoServiceImpl (mock 数据)
- public class UserDaoImpl{
- public List<User> findUserList() {
- return Collections.singletonList(new User("seven", 18));
- }
- }
复制代码
- 业务层 UserServiceImpl(调用DAO层)
- public class UserServiceImpl {
- private UserDaoImpl userDao;
- public void setUserDao(UserDaoImpl userDao) {
- this.userDao = userDao;
- }
- public List<User> findUserList() {
- return userDao.findUserList();
- }
- }
复制代码- @Aspect
- public class LogAspect {
- @Around("execution(* com.seven.springhelloworldxml.service.*.*(..))")
- public Object businessService(ProceedingJoinPoint pjp) throws Throwable {
- // get attribute through annotation
- Method method = ((MethodSignature) pjp.getSignature()).getMethod();
- System.out.println("execute method: " + method.getName());
- // continue to process
- return pjp.proceed();
- }
- }
复制代码
- 添加并增长spring.xml和aspects.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
- <bean id="userDao" >
-
- </bean>
- <bean id="userService" >
- <property name="userDao" ref="userDao"/>
-
- </bean>
-
- </beans>
复制代码- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xmlns:context="http://www.springframework.org/schema/context"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/aop/spring-aop.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context.xsd
- ">
- <context:component-scan base-package="com.seven.springhelloworldxml" />
- <aop:aspectj-autoproxy/>
- <bean id="logAspect" >
-
- </bean>
-
- </beans>
复制代码- public class APP {
- public static void main(String[] args) {
- // create and configure beans
- ApplicationContext context = new ClassPathXmlApplicationContext("aspects.xml", "spring.xml");
- // retrieve configured instance
- UserServiceImpl service = context.getBean("userService", UserServiceImpl.class);
- // use configured instance
- List<User> userList = service.findUserList();
- // print info from beans
- userList.forEach(a -> System.out.println(a.getName() + "," + a.getAge()));
- }
- }
复制代码 运行结果:
怎样体现的Spring上风
控制反转 - IOC
查询用户(service通过调用dao查询pojo),本质上就是怎样创建User/Dao/Service?
- 如果没有Spring框架,需要本身创建User/Dao/Service等,好比:
- UserDaoImpl userDao = new UserDaoImpl();
- UserSericeImpl userService = new UserServiceImpl();
- userService.setUserDao(userDao);
- List<User> userList = userService.findUserList();
复制代码
- 有了Spring框架,可以将原有Bean的创建工作转给框架, 需要用时从Bean的容器中获取即可,这样便简化了开发工作
Bean的创建和使用分离了。- // create and configure beans
- ApplicationContext context = new ClassPathXmlApplicationContext("aspects.xml", "spring.xml");
- // retrieve configured instance
- UserServiceImpl service = context.getBean("userService", UserServiceImpl.class);
- // use configured instance
- List<User> userList = service.findUserList();
复制代码 更进一步,便能理解为何会有如下的知识点了:
- Spring框架管理这些Bean的创建工作,即由用户管理Bean变革为框架管理Bean,这个就叫控制反转 - Inversion of Control (IoC)
- Spring 框架托管创建的Bean放在哪里呢? 这便是IoC Container;
- Spring 框架为了更好让用户设置Bean,必然会引入不同方式来设置Bean? 这便是xml设置,Java设置,注解设置等支持
- Spring 框架既然接管了Bean的天生,必然需要管理整个Bean的生命周期等;
- 应用程序代码从Ioc Container中获取依赖的Bean,注入到应用程序中,这个过程叫 依赖注入(Dependency Injection,DI) ; 以是说控制反转是通过依赖注入实现的,其实它们是同一个概念的不同角度描述。通俗来说就是IoC是设计思想,DI是实现方式
- 在依赖注入时,有哪些方式呢?这就是构造器方式,@Autowired, @Resource, @Qualifier... 同时Bean之间存在依赖(可能存在先后顺序问题,以及循环依赖问题等)
面向切面 - AOP
第二个需求:给Service所有方法调用添加日记(调用方法时),本质上是解耦问题;
- 如果没有Spring框架,需要在每个service的方法中都添加纪录日记的方法,好比:
- public List<User> findUserList() {
- System.out.println("execute method findUserList");
- return this.userDao.findUserList();
- }
复制代码
- 有了Spring框架,通过@Aspect注解 定义了切面,这个切面中定义了拦截所有service中的方法,并纪录日记; 可以明显看到,框架将日记纪录和业务需求的代码解耦了,不再是侵入式的了
- /**
- * aspect for every methods under service package.
- */
- @Around("execution(* com.seven.springhelloworldxml.service.*.*(..))")
- public Object businessService(ProceedingJoinPoint pjp) throws Throwable {
- // get attribute through annotation
- Method method = ((MethodSignature) pjp.getSignature()).getMethod();
- System.out.println("execute method: " + method.getName());
- // continue to process
- return pjp.proceed();
- }
复制代码 更进一步,便能理解为何会有如下的知识点了:
- Spring 框架通过定义切面, 通过拦截切点实现了不同业务模块的解耦,这个就叫面向切面编程 - Aspect Oriented Programming (AOP)
- 为什么@Aspect注解使用的是aspectj的jar包呢?这就引出了Aspect4J和Spring AOP的历史渊源,只有理解了Aspect4J和Spring的渊源才能理解有些注解上的兼容设计
- 怎样支持更多拦截方式来实现解耦, 以满足更多场景需求呢? 这就是@Around, @Pointcut... 等的设计
- 那么Spring框架又是怎样实现AOP的呢? 这就引入代理技术,分静态代理和动态代理,动态代理又包含JDK代理和CGLIB代理
Spring框架逐步简化开发
Java 设置方式改造
案例源码点击这里
在前文的例子中, 通过xml设置方式实现的,这种方式现实上比较贫苦; 我通过Java设置进行改造:
- User,UserDaoImpl, UserServiceImpl,LogAspect不用改
- 将原通过.xml设置转换为Java设置
- @EnableAspectJAutoProxy
- @Configuration
- public class BeansConfig {
- /**
- * @return user dao
- */
- @Bean("userDao")
- public UserDaoImpl userDao() {
- return new UserDaoImpl();
- }
- /**
- * @return user service
- */
- @Bean("userService")
- public UserServiceImpl userService() {
- UserServiceImpl userService = new UserServiceImpl();
- userService.setUserDao(userDao());
- return userService;
- }
- /**
- * @return log aspect
- */
- @Bean("logAspect")
- public LogAspect logAspect() {
- return new LogAspect();
- }
- }
复制代码- public class APP {
- public static void main(String[] args) {
- // create and configure beans
- ApplicationContext context = new AnnotationConfigApplicationContext(BeansConfig.class);
- // retrieve configured instance
- UserServiceImpl service = context.getBean("userService", UserServiceImpl.class);
- // use configured instance
- List<User> userList = service.findUserList();
- // print info from beans
- userList.forEach(a -> System.out.println(a.getName() + "," + a.getAge()));
- }
- }
复制代码 这里简单提一下实现原理:
- 当应用启动时,Spring框架会使用Java的反射API来查抄所有带有@Configuration 注解的类(这里不懂的可以看下注解实现的原理)。Spring框架内置了一个注解处理器ConfigurationClassPostProcessor,它是BeanFactoryPostProcessor 的一个实现。
- 接着ConfigurationClassPostProcessor 会在容器初始化时被调用,它会查找所有带有@Configuration 注解的类,并剖析这些类中定义的@Bean 方法。
- BeanDefinition:对于每个@Configuration 类,Spring会为其中的每个@Bean 方法天生一个BeanDefinition 对象。这些BeanDefinition 对象会包含创建和设置Bean所需的所有信息。
- 处理嵌套设置:如果一个@Configuration 类中包含了另一个@Configuration 类的引用,ConfigurationClassPostProcessor 会递归地处理这些嵌套的设置类。
- 注册BeanDefinition: 一旦所有的BeanDefinition 被创建,它们会被注册到Spring容器的BeanFactory 中。这样,Spring容器就可以在需要时创建和注入这些Bean。
- 代理设置类: 为了支持嵌套设置类和循环依赖等特性,Spring会为每个@Configuration 类创建一个代理,这个代剖析在运行时处理相干的逻辑。
注解设置方式改造
案例源码点击这里
更进一步,Java 5开始提供注解支持,Spring 2.5 开始完全支持基于注解的设置而且也支持JSR250 注解。在Spring后续的版本发展倾向于通过注解和Java设置联合使用.
- @EnableAspectJAutoProxy
- @Configuration
- public class BeansConfig {
- }
复制代码
- UserDaoImpl 增长了 @Repository注解
- @Repositorypublic class UserDaoImpl{
- public List<User> findUserList() {
- return Collections.singletonList(new User("seven", 18));
- }
- }
复制代码
- UserServiceImpl 增长了@Service 注解,并通过@Autowired注入userDao
- @Service
- public class UserServiceImpl {
- @Autowired
- private UserDaoImpl userDao;
- public List<User> findUserList() {
- return userDao.findUserList();
- }
- }
复制代码- @Component
- @Aspect
- public class LogAspect {
- @Around("execution(* com.seven.springhelloworldanno.service.*.*(..))")
- public Object businessService(ProceedingJoinPoint pjp) throws Throwable {
- // get attribute through annotation
- Method method = ((MethodSignature) pjp.getSignature()).getMethod();
- System.out.println("execute method: " + method.getName());
- // continue to process
- return pjp.proceed();
- }
- }
复制代码
- 在App中扫描com.seven.springhelloworldanno包
- public static void main(String[] args) {
- // create and configure beans
- ApplicationContext context = new AnnotationConfigApplicationContext("com.seven.springhelloworldanno");
- // retrieve configured instance
- UserServiceImpl service = context.getBean(UserServiceImpl.class);
- // use configured instance
- List<User> userList = service.findUserList();
- // print info from beans
- userList.forEach(a -> System.out.println(a.getName() + "," + a.getAge()));
- }
复制代码这里要提一嘴的是,现在大多数的Spring项目,基本都是这种方式。主要步骤就是:
1、对类添加@Component相干的注解,好比@Controller,@Service,@Repository
2、设置ComponentScan的basePackage, 好比在xml文件里设置, 或者在设置类中设置@ComponentScan("com.seven.springframework")注解,或者 直接在APP类中 new AnnotationConfigApplicationContext("com.seven.springframework")指定扫描的basePackage.
SpringBoot托管设置
Springboot现实上通过约定大于设置的方式,使用xx-starter统一的对Bean进行默认初始化,用户只需要很少的设置就可以进行开发了。
面试题专栏
Java面试题专栏已上线,欢迎访问。
- 如果你不知道简历怎么写,简历项目不知道怎么包装;
- 如果简历中有些内容你不知道该不该写上去;
- 如果有些综合性问题你不知道怎么答;
那么可以私信我,我会尽我所能资助你。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |