Spring 基础
持续更新…
何为Spring ?
Spring 是目前主流的 Java Web 开发框架。是针对bean的生命周期进行管理的轻量级开源框架。 Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。
Spring 由 Rod Johnson 创立,2004 年发布了 Spring 框架的第一版,其目的是用于简化企业级应用程序开发的难度和周期。
IOC
何为IOC?
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。简单地说,OC就是一个可以操作Bean对象的容器。
Spring实现IOC容器的两种方式(底层通过工厂模式创建对象):
- BeanFactory (IOC容器的基本实现,是Spring内部使用接口)
- ApplicationContext (BeanFactory的子接口)
AOP
何为AOP?
面向切面编程利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可复用性,同时提高了开发的效率。
通俗地说,就是不通过修改源代码方式,在主干功能里面添加新功能。
下图以登录页面操作为例:
假设我们需要添加一个权限管理模块,按照原始的方式,就是修改源代码的方式(比如添加if判断语句)来达到目的,但是,这势必会增加耦合度,而且也违背了开闭原则。因此可以通过AOP实现。
具体操作为:在成功进入主页面的位置添加权限判断模块。
AspectJ注解实现
例如:
<?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="org.wu.*"></context:component-scan>
<!-- 开启AspectJ生成代理对象 -->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
</beans>
package org.wu.proxy;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LoginProxy {
// 前置通知
@Before(value="execution(* org.wu.service.LoginService.login())")
public void loginBefore(){
System.out.println("登录操作之前… ");
}
@After(value ="execution(* org.wu.service.LoginService.login())")
public void loginAfter() {
System.out.println("最终通知… ");
}
@AfterReturning(value ="execution(* org.wu.service.LoginService.login())")
public void loginAfterReturning() {
System.out.println("登录操作之后… ");
}
@AfterThrowing(value ="execution(* org.wu.service.LoginService.login())")
public void loginAfterThrowing() {
System.out.println("抛出异常… ");
}
@Around(value ="execution(* org.wu.service.LoginService.login())")
public void loginAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕之前… ");
proceedingJoinPoint.proceed();
System.out.println("环绕之后… ");
}
}
package org.wu.service;
public interface ILoginService{
void login() ;
}
package org.wu.service;
import org.springframework.stereotype.Service;
@Service(value = "loginService")
public class LoginService implements ILoginService{
// 定义切入点
public void login() {
System.out.println("登录操作… ");
}
}
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.wu.service.ILoginService;
import org.wu.service.LoginService;
public class Test {
@org.junit.Test
public void test(){
// 1.加载spring的配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:Bean.xml");
// 2.获取LoginService对象
ILoginService loginService = (LoginService)context.getBean("loginService");
loginService.login();
}
}
结果:
读者应该发现了,对于多个重复的切入点,会显得比较繁琐,因此这里通过添加==@Pointcut==注解改善了代码,结果同上:
package org.wu.proxy;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LoginProxy {
// 抽取相同的切入点
@Pointcut(value = "execution(* org.wu.service.LoginService.login())")
public void pointcut(){}
// 前置通知
@Before(value="pointcut()")
public void loginBefore(){
System.out.println("登录操作之前… ");
}
@After(value ="pointcut()")
public void loginAfter() {
System.out.println("最终通知… ");
}
@AfterReturning(value ="pointcut()")
public void loginAfterReturning() {
System.out.println("登录操作之后… ");
}
@AfterThrowing(value ="pointcut()")
public void loginAfterThrowing() {
System.out.println("抛出异常… ");
}
@Around(value ="pointcut()")
public void loginAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕之前… ");
proceedingJoinPoint.proceed();
System.out.println("环绕之后… ");
}
}
同时,对于多个增强类对同一个方法进行增强,通过==@Order==设置增强类的优先级,这样就可以保证只有唯一的一个增强类可以实现,如下:
添加一个新的增强类(设置优先等级为1),并将原先的增强类的优先级设置为2
package org.wu.proxy;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Aspect
@Order(1)
public class RepeatLoginProxy {
@Pointcut(value = "execution(* org.wu.service.LoginService.login())")
public void pointcut(){}
@AfterReturning(value = "pointcut()")
public void loginAfterReturning(){
System.out.println("再次登陆之后… ");
}
@Before(value = "pointcut()")
public void loginBefore(){
System.out.println("再次登陆之前… ");
}
@AfterThrowing(value = "pointcut()")
public void loginAfterThrowing(){
System.out.println("再次登陆跑出异常… ");
}
@After(value = "pointcut()")
public void loginAfter(){
System.out.println("再次登陆最终通知… ");
}
@Around(value = "pointcut()")
public void loginAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("再次登陆环绕通知之前… ");
proceedingJoinPoint.proceed();
System.out.println("再次登陆环绕通知之后… ");
}
}
结果:
SpringBoot中使用AOP:
添加依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.0</version>
</dependency>
其余操作同上
结果:
AspectJ配置文件实现
SpringMVC
一个最基本的SpringMVC框架搭建流程如下:
首先在Maven Web项目引入坐标:
<!-- 版本锁定 -->
<properties>
<springVersion>5.2.0.RELEASE</springVersion>
</properties>
<!-- 坐标导入 -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${springVersion}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${springVersion}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
</dependencies>
web.xml文件内容:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" id="WebApp_ID" version="4.0">
<display-name>SpringMVCDemo1</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- DispatcherServlet绑定Spring的配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!-- 启动级别为1 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
在src目录下新建spring-mvc.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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 开启注解 -->
<mvc:annotation-driven/>
<!-- 扫描注解类 -->
<context:component-scan base-package="com.wu.controller" />
<!-- 资源过滤 -->
<mvc:default-servlet-handler/>
<!-- 视图解析器 -->
<bean class = "org.springframework.web.servlet.view.InternalResourceViewResolver" id = "internalResourceViewResolver">
<property name = "prefix" value = "/WEB-INF/jsp/"></property>
<property name = "suffix" value = ".jsp"></property>
</bean>
</beans>
MyController.java:
package com.wu.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/springmvc")
public class MyController {
@RequestMapping("/hello")
public String sayHello(Model model) {
// 封装数据
model.addAttribute("hello", "Hello SpringMVC!");
// 返回的值会被视图解析器处理
return "Hello";
}
}
Hello.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>SpringMVC</title>
</head>
<body>
<h1>${requestScope.hello}</h1>
</body>
</html>
结果:
RestFul风格
对于原始方式,则按照如下代码
@RequestMapping("/count")
public String count(@RequestParam("num1") int a ,@RequestParam("num2") int b , Model model) {
model.addAttribute("sum","和为:"+(a+b));
return "Count";
}
比如此时用户请求的URL: http://localhost:8058/springmvc/count?num1=0&num2=1
RestFul方式
结合@PathVariable使用
@RequestMapping("/info/{name}/{age}")
public String showInfo(@PathVariable String name, @PathVariable int age,Model model) {
model.addAttribute("info","你的姓名是"+name+",年龄为"+age+"岁");
return "Info";
}
此时用户请求的URL为:http://localhost:8058/springmvc/info/张三/20
结果为:
以下三个语句等价
@RequestMapping("/info/{name}/{age}") // 默认情况
@RequestMapping(path = "/info/{name}/{age}",method = RequestMethod.GET)
@GetMapping("/info/{name}/{age}")
前端传递对象
需要建立一个pojo,Student.java:
package com.wu.pojo;
public class Student {
private String name;
private Long id;
private Integer age;
public void setName(String name) {
this.name = name;
}
public void setId(Long id) {
this.id = id;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", id=" + id + ", age=" + age + "]";
}
}
注:需要提供setter方法
新建一个controller方法,showStudent方法:
@GetMapping("/student")
public String showStudent(Student student,Model model) {
model.addAttribute("info",student.toString());
return "Info";
}
用户提交的URL为:http://localhost:8058/springmvc/student?name=张三&id=20210114&age=20,其显示结果:
对于乱码现象,可以在web.xml文件中添加以下的代码:
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Jackson的使用
引入坐标:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.0</version>
</dependency>
对Student.java添加getter和setter方法
增加Controller的方法:
@RequestMapping(value = "/json")
public String jsonTest(Student student,Model model) {
ObjectMapper mapper = new ObjectMapper();
String jsonStr = "";
try {
jsonStr = mapper.writeValueAsString(student);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
model.addAttribute("info","Json值为:"+jsonStr);
return "Info";
}
用户提交的URL为:http://localhost:8058/springmvc/json?name=张三&id=20210114&age=20,其结果为:
注:如果出现乱码问题,可以在spring-mvc.xml中添加如下代码
<!-- JSON乱码问题配置 -->
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class = "org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value = "utf-8"></constructor-arg>
</bean>
<bean class = "org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class = "org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="failOnEmptyBeans" value = "false" />
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
webflux响应式编程: