Spring
简介
Spring框架由Rod Johnson开发,2004年发布了Spring框架的第一版。Spring是一个从实际开发中抽取出来的框架,因此它完成了大量开发中的通用步骤,留给开发者的仅仅是与特定应用相关的部分,从而大大提高了企业应用的开发效率。
Spring是什么?
Spring是一个轻量级的,IoC和AOP的一站式java开发框架,是为了简化企业级应用开发而生。
名词解释:
轻量级:Spring框架所使用的jar都比较小,Spring核心功能的所需的jar总共在3M左右。Spring框架运行占用的资源少。
IoC(Inverse of Control):控制反转。当我们需要使用某一个对象的方法时,在传统模式下有两种方式:①原始做法:主动创建该对象;②简单工厂模式:找到该对象所在工厂,主动通过工厂获取该对象。
传统的方法描述中都有“主动”,也就是在编写代码时,开发者必须要显现的获取该对象,这样做有两大缺点,高耦合度,并且在后期对项目的升级维护。而使用了Spring框架后,IoC很巧妙的解决了这一问题,可以将所需要的对象交由Spring框架创建管理,我们在哪里使用,在哪里定义一个接收的容器即可。由于对象由开发者管理变更为由Spring框架管理,所以称之为控制反转。
AOP(Aspect Orient Programming):AOP也就是面向切面编程(一种编程思想),作为面向对象编程的一种补充,已经成为一种比较成熟的编程方式。其实AOP问世的时间并不太长,AOP和OOP互为补充,面向切面编程将程序运行过程分解成各个切面。AOP专门用于处理系统中分布于各个模块(不同方法)中的交叉关注点的问题,在JavaEE应用中,常常通过AOP来处理一些具有横切性质的系统级服务,如事务管理、安全检查、缓存、对象池管理等,AOP已经成为一种非常常用的解决方案。 简单讲就是可以为程序动态的添加某种功能,而不需要修改原来的代码。如对数据库修改需要提交事务、打印日志文件等。
此处并不需要担心对IoC和AOP不理解。下面对IoC和AOP进一步解释
案例演示
IoC:
IoC(控制反转):IoC不是一项技术,而是一种设计思想,将原来在程序中手动创建对象的控制权交由Spring框架来管理。IoC容器负责对象的实例化、对象的初始化、对象和对象之间的依赖关系、对象的销毁,对象提供对象的查找等操作,对象的整个生命周期都是由容器来控制的。当我们需要使用的时候直接从IoC容器中获取即可,我们只需要定义一个对象容器。
Demo前提,创建一个maven项目
导入spring框架所需依赖:
<!-- spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
Spring Bean管理:
基于xml文件配置方式
在resources目录下创建spring配置文件spring.xml
创建方式如图所示:
spring.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:配置需要Spring管理的类
id:生成的对象名称
class:类全名
name:对象别名, 可以定义多个
scope:singleton(默认值):在Spring中只存在一个bean实例, 即单例模式
prototype:原型, getBean()时每次都会创建一个新的对象, 即多例模式
-->
<bean id="user" name="user2,user3" class="com.flash.demo.model.User" scope="prototype"/>
</beans>
创建用户类:
package com.flash.demo.model;
/**
* @author flash
* @date 2024/07/11 10:14
* 功能描述:用户类
*/
public class User {
private Integer id;
private String name;
private Integer age;
// 无参构造器
public User() {
System.out.println("User无参构造器");
}
public User(Integer id, String name, Integer age) {
System.out.println("User三参构造器");
this.id = id;
this.name = name;
this.age = age;
}
// 由于框架要使用类反射, 所以在创建类时, 将对应的属性的getter和seter方法生成, 类反射会用到
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
System.out.println("setId被调用");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
Test测试类:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author flash
* @date 2024/07/11 10:16
* 功能描述:获取Spring框架生成的User对象
*/
public class Test1 {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Object user = applicationContext.getBean("user");
System.out.println(user);
System.out.println("user的hash值 = " + user.hashCode());
Object user2 = applicationContext.getBean("user2");
System.out.println("user2的hash值 = " + user2.hashCode());
}
}
当spring.xml文件中User类scope = singleton时运行结果:
当spring.xml文件中User类scope = prototype时运行结果:
对象初始化:
以下演示scope均使用默认的单例模式
方式1(setter方法):
spring.xml更改:
<bean id="user" name="user2,user3" class="com.flash.demo.model.User">
<property name="id" value="1"/>
<property name="name" value="张三"/>
<property name="age" value="21"/>
</bean>
该方式会调用无参构造器创建一个对象实例,然后调用该对象的setter方法完成初始化
运行结果如果所示:
方式2(构造器):
spring.xml更改:
<bean id="user" name="user2,user3" class="com.flash.demo.model.User" scope="prototype">
<constructor-arg name="id" value="1"/>
<constructor-arg name="name" value="张三"/>
<constructor-arg name="age" value="21"/>
</bean>
该方式会调用此处定义的三个参数的构造器创建一个对象实例
运行结果如果所示:
基于注解方式
开启注解扫描
采用注解的方式实现IoC需要开启注解扫描,spring.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解扫描, 检测添加有 spring 注解标签的类-->
<context:component-scan base-package="com.flash.demo"/>
</beans>
为User类添加注解
测试类:
import com.flash.demo.model.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author flash
* @date 2024/07/11 10:16
* 功能描述:获取Spring框架生成的User对象, 注解方式
*/
public class Test2 {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User user = applicationContext.getBean("user", User.class);
System.out.println(user);
}
}
运行结果:
数据库交互
Spring实现与数据库(mysql)交互,使用阿里数据源提供的druid(德鲁伊)
与数据库交互所需依赖:
<!--数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!-- spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<!-- 阿里数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
spring.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解扫描, 检测添加有 spring 注解标签的类-->
<context:component-scan base-package="com.flash.demo"/>
<!--导入属性文件-->
<context:property-placeholder location="config.properties"/>
<!--spring 管理阿里巴巴数据库链接对象-->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${uname}"/>
<property name="password" value="${pwd}"/>
<property name="maxActive" value="${maxActive}"/>
<property name="initialSize" value="${initialSize}"/>
</bean>
<!--创建 spring 对 jdbc 进行封装的一个 jdbcTemplate,spring框架创建JdbcTemplate实例对象并将druidDataSource数据库连接对象注入到该对象中-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="druidDataSource"/>
</bean>
</beans>
创建config.properties配置文件
config.properties配置文件内容:
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/demo?serverTimezone=Asia/Shanghai
uname=root
pwd=root
maxActive=20 # 最大连接数
initialSize=5 # 初始连接数
数据库交互层类:
package com.flash.demo.dao;
import com.flash.demo.model.User;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
/**
* @author flash
* @date 2024/07/11 13:36
* 功能描述:数据库交互
*/
// @Repository(value = "userDao") // 指定对象名, 该对象被注入的地方可以通过对象名寻找此对象
@Repository
public class UserDao {
@Resource
public JdbcTemplate jdbcTemplate;
public void insertUser(User user) {
System.out.println("添加user");
jdbcTemplate.execute("INSERT INTO `user`(id,`name`,age) VALUES ('1','张三','21')");
}
}
Service层类:
package com.flash.demo.service;
import com.flash.demo.dao.UserDao;
import com.flash.demo.model.User;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @author flash
* @date 2024/07/11 13:51
* 功能描述:
*/
@Service(value = "userService")
public class UserService {
// @Resource(name = "userDao") 通过对象名查找
@Resource // 注解中没有指定对象名, 按照对象的类型寻找, 即UserDao
/*
也可以使用@Autowired注解标签, 可以添加在要注入的属性上, 也可以是属性的set方法上, 如果直接添加在属性上, 那么set方法可以需要
默认情况加, 要注入的属性对象不能为空. @Autowired(required = true), false则可以为空, 允许为空时在值为空时, 不会报spring的错,
而是因为调用它的方法出现空指针异常.
@Qualifier与@Autowired搭配使用, 指定对象名, 可以不要, 会根据属性类型自动寻找
@Qualifier和@Autowired使用示例:
@Autowired
@Qualifier(value = "userDao")// 这个可以不要, 自动会找
需要注意的是:在jdk8中可以使用@Resource注解, 在其他版本中可能无法使用. 就必须使用@Autowired注解标签, @Resource注解是由jdk提供的, 而@Autowired注解是由spring提供的
*/
UserDao userDao;
public void save(User user) {
userDao.insertUser(user);
}
}
Test类:
import com.flash.demo.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author flash
* @date 2024/07/11 13:49
* 功能描述:使用注入方式通过阿里的druid将User对象添加到数据库
*/
public class Test3 {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
System.out.println("userService = " + userService);
userService.save(null);
}
}
运行结果:
在数据库中我们可以看到数据已经成功添加:
AOP
AOP(Aspect Oriented Programming):意为面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,是java开发中的一个重要内容。利用AOP可以对业务逻辑和非业务逻辑进行隔离,从而使得各部分的耦合度降低,提交代码的复用性,也提高开发效率。
OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。
AOP则是对业务处理过程中的切面进行提取,面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间的低耦合性的隔离效果。OOP和AOP这两种设计思想在目标上有着本质行的差异。使用案例:修改数据库数据事务的提交;打印日志,在程序运行期间将对应的操作通过日志的形式记录下来;权限判断,在执行方法前,判断是否有权限。
AOP的基本概念:
连接点(Joinpoint):类中可以被增强的方法,这个方法就被称为连接点
切入点(pointcut):类中有很多方法可以被增强,但实际中只有 add 和 update
被增了,那么 add 和 update 方法就被称为切入点(实际实现的连接点)
通知(Advice): 通知是指一个切面在特定的连接点要做的事情(增强的功能)。通
知分为方法执行前通知,方法执行后通知,环绕通知等. 目标(Target): 代理的目标对象(连接点,切入点所在类)
代理(Proxy): 向目标对象应用通知时创建的代理对象
代码示例:
为了不和IoC的内容混淆,所以重新创建一个项目为读者演示AOP的功能
AOP所需依赖:
<!-- AOP相关jar-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<!-- spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
spring.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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启注解扫描, 检测添加有 spring 注解标签的类-->
<context:component-scan base-package="com.flash.springPro2"/>
<!--启动 AspectJ 支持-->
<aop:aspectj-autoproxy/>
</beans>
更改UserDao类的内容
此处示例不需要与数据库交互,打印输出一句话即可。
package com.flash.demo.dao;
import com.flash.demo.model.User;
import org.springframework.stereotype.Repository;
/**
* @author flash
* @date 2024/07/11 13:36
* 功能描述:
*/
@Repository
public class UserDao {
public void insertUser(User user) {
System.out.println("添加user");
}
}
前置通知
编写CommonUtil类:
为UserDao类的insertUser(User user)方法功能增强,编写CommonUtil类:
package com.flash.demo.common;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @author flash
* @date 2024/07/11 15:32
* 功能描述:
*/
@Component // spring 管理生成 CommonUtil 类的对象
@Aspect // 添加此标签类中的方法就是代理对象要调用的方法
public class CommonUtil {
// 第一个 * 表示任意返回值,第二个 * 表示UserDao类下的任意方法,括号中的“..”表示该方法的所有重载方法
@Before("execution(* com.flash.demo.dao.UserDao.*(..))") // ..是因为java中有重载, 表示对该方法名的所有方法均使用
public void printLog() {
System.out.println("方法执行成功");
}
}
Test测试类:
import com.flash.demo.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author flash
* @date 2024/07/11 15:38
* 功能描述:AOP测试类
*/
public class Test4 {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.save(null);
}
}
运行结果:
可见在程序运行过程,先执行的是CommonUtil类下我们定义的方法,后执行我们所执行的业务逻辑代码,即添加User。这样的方法称之为前置通知。
以下测试所使用的测试类均相同,即Test4
后置通知
编写CommonUtil类
package com.flash.demo.common;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @author flash
* @date 2024/07/11 15:32
* 功能描述:
*/
@Component // spring 管理生成 CommonUtil 类的对象
@Aspect // 添加此标签类中的方法就是代理对象要调用的方法
public class CommonUtil {
@AfterReturning("execution(* com.flash.demo.dao.UserDao.*(..))")
public void commit() {
System.out.println("提交事物");
}
}
运行结果:
使用@AfterReturning定义的后置通知在我们执行的业务逻辑中如果出现异常则不执行,
如果使用@After定义后置通知,则不管是否出现异常都会执行该通知
异常通知
编写CommonUtil类
package com.flash.demo.common;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @author flash
* @date 2024/07/11 15:32
* 功能描述:
*/
@Component // spring 管理生成 CommonUtil 类的对象
@Aspect // 添加此标签类中的方法就是代理对象要调用的方法
public class CommonUtil {
@AfterThrowing(value = "execution(* com.flash.demo.dao.UserDao.*(..))", throwing = "e")
public void printException(Throwable e) {
System.out.println("出异常了" + e.getMessage());
}
}
更改UserDao类
为其添加一个异常
package com.flash.demo.dao;
import com.flash.demo.model.User;
import org.springframework.stereotype.Repository;
/**
* @author flash
* @date 2024/07/11 13:36
* 功能描述:
*/
@Repository
public class UserDao {
public void insertUser(User user) {
System.out.println("添加user");
System.out.println(1 / 0);
}
}
运行结果:
@AfterThrowing异常通知,当业务逻辑代码出现异常时通知
环绕通知
编写CommonUtil类
package com.flash.demo.common;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @author flash
* @date 2024/07/11 15:32
* 功能描述:
*/
@Component // spring 管理生成 CommonUtil 类的对象
@Aspect // 添加此标签类中的方法就是代理对象要调用的方法
public class CommonUtil {
@Around("execution(* com.flash.demo.dao.UserDao.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object proceed = null;
try {
System.out.println("保存日志");
for (Object arg : joinPoint.getArgs()) {
System.out.println(arg);
}
joinPoint.proceed();// 调用自己的方法, 有参数也可以接收参数
System.out.println("提交事务");
} catch (Throwable e) {
System.out.println("异常" + e.getMessage());
} finally {
System.out.println("后置通知");
}
System.out.println("无论是否异常均执行");
// 返回方法调用后的结果
return null;
}
}