Spring6
概述
Spring Framework特点:
非侵入式:对结构影响小,功能组件只需注解,不破坏原有结构
控制反转-IOC :把自己创建的资源,向环境索取资源变成环境将资源准备好,只管资源注入
面向切面编程:AOP-再不修改源代码的基础上增强代码功能
容器:包含管理组件对象的生命周期,组件得到了容器化管理
组件化:实现使用简单的组件配置组合成一个复杂的应用
一站式:在IOC和AOP的基础上整合其他开源框架和第三方库
基础配置:
在porn.xml中添加配置文件和测试工具
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.2</version> </dependency>
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.8.2</version> </dependency>
|
创建spring-first模块,创建User对象
public class User { public void add(){ System.out.println("add....."); } }
|
在resources中创建Spring.Config , bean.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="user" class="com.jason.spring6.User"></bean> </beans>
|
创建UserTest测试类
public class UserTest { @Test public void testUserObject(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); User user = (User)context.getBean("user"); user.add(); } }
|
补充:无参数构造方法也会被执行, 也可以用反射来创建对象,
Class clazz = Class.forName("com.jason.spring6.User");
User user= (User)clazz.getDeclareConstructor().newInstance();
|
Log4j2日志
引入Log4j2的依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency>
|
log4j2.xml配置模板
<?xml version="1.0" encoding="UTF-8"?> <configuration> <loggers>
<root level="DEBUG"> <appender-ref ref="spring6log"/> <appender-ref ref="RollingFile"/> <appender-ref ref="log"/> </root> </loggers>
<appenders> <console name="spring6log" target="SYSTEM_OUT"> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/> </console>
<File name="log" fileName="d:/spring6_log/test.log" append="false"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/> </File>
<RollingFile name="RollingFile" fileName="d:/spring6_log/app.log" filePattern="log/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz"> <PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/> <SizeBasedTriggeringPolicy size="50MB"/>
<DefaultRolloverStrategy max="20"/> </RollingFile> </appenders> </configuration>
|
IoC容器
概念
控制反转,所有Java对象的实例化和初始化,控制对象与对象之间的依赖关系 ,通过ioc管理的对象叫做Spring Bean,使用map集合去存放bean对象
基本流程,在xml文件中配置Bean定义信息,经过BeanDefinitionReader进行读取加载,ioc通过BeanFactory工厂+反射来创建对象(实例化),再通过初始化和content.getBean(id)来获取最终对象
public void testUserObject(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); User user = (User)context.getBean("user"); user.add(); }
|
BeanFactory是ioc容器的基本实现,面向Spring本身,我们使用的是他的子接口 如 ApplicationContext的
ClassPathXMLApplicationContext :通过读取类路径下的XML格式的配置文件创建IOC容器对象,还有其他,FileSystem——,Configurable——等。
依赖注入(DI)的两种方法:set注入和构造注入
基于XML管理bean
获取bean
预先加入依赖, 创建User对象,创建bean.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="user" class="com.jason.test.User"></bean> </beans>
|
public class TestUser { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); User user3 = context.getBean("user", User.class);
} }
|
实验:1:组件实现了接口,可以根据接口获取bean吗 2:如果一个接口有多个实现类,这些实现类都配置了bean,根据接口可以实现bean吗
先验证第一个: 创建UserDao接口和UserDaoImpl实现类
bean.xml创建实现类对象
<bean id="userdao" class="com.jason.test.bean.UserDaoImpl"></bean>
|
测试是否调用成功
public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); UserDao userdao = context.getBean(UserDao.class); userdao.run();
}
|
接口可以获取bean,前提是bean是唯一的
同理可得,在创建一个personimpl的实现类继承Userdao接口,然后再配置了bean , 接口类型就不可以获取bean了,因为bean不唯一
依赖注入之setter注入
在bean中配置
<bean id="book" class="com.jason.test.setter.Book"> <property name="bname" value="三体1"></property> <property name="author" value="刘慈欣"></property> </bean>
|
@Test public void testSetter(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); Book book = context.getBean("book", Book.class); System.out.println(book); }
|
构造器注入
<!-- 构造器注入--> <bean id="book2" class="com.jason.test.setter.Book"> <constructor-arg name="bname" value="三体2"></constructor-arg> <constructor-arg name="author" value="刘慈欣"></constructor-arg> </bean>
|
对象类型属性注入
<bean id="dept" class"---略"> <property name="dname" value="技术部"></property> </bean> <bean id="emp" class="--略"> <property name="ename" value="lucy"></property> <property name="dept" ref="dept"></property> <property name="dept"> <bean id="dept2" class="---略"> <property name="dname" value="技术部"></property> </bean> </property> <property name="dept.dname" value="技术部"></property> </bean>
|
数组类型注入
<bean id="dept" class"---略"> <property name="dname" value="技术部"></property> </bean> <bean id="emp" class="--略"> <property name="ename" value="lucy"></property> <property name="loves"> <array> <value>1</value> <value>1</value> <value>1</value> </array> </property> </bean>
|
List类型和Map类型的注入注入
<property name="emplist"> <list> <ref bean="emp1"></ref> <ref bean="emp2"></ref> </list> </property>
<property name="empmap"> <map> <entry key="001" value-ref="dept1"></entry> <entry key="002" value-ref="dept2 "></entry> </map> </property>
|
p命名空间注入
... xmlns:p="http://www.springframework.org/schema/p" ... http://www.springframework.org/schema/p
<bean id="detp" class="...略" p:sid="100" p:sname="sont" p:emplist-ref="emplist" p:empmap-ref="empmap"></bean>
|
模拟
添加数据库依赖
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency>
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.31</version> </dependency>
|
创建外部属性文件
jdbc.user=root jdbc.password=atguigu jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC jdbc.driver=com.mysql.cj.jdbc.Driver
|
创建bean-jdbc.xm
<?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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<context:property-placeholder location="jdbc.properties"></context:property-placeholder>
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="${jdbc.url}"></property> <property name="name" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> </bean> </beans>
|
bean作用域
通过配置bean标签中的scope属性来指定bean的作用于范围
singletom(默认) :在IOC容器中,这个bean的对象始终为单实例,在IOC容器初始化时创建对象
prototype:在IOC容器中有多个实例,在获取bean时创建对象
bean的生命周期
- bean对象的创建(调用构造器)
- 给bean对象设置相关属性
- bean调用后置处理器BeanPostProcessor(初始化前方法) 记得放入ioc容器中
- bean对象初始化(调用指定初始化方法,init-method)
- bean调用后置处理器BeanPostProcessor(初始化后方法)
- bean对象创建完成,使用
- bean对象销毁(配置指定销毁方法 destroy-method)
- loC容器关闭
自动装配
根据制定策略,在ioc容器中匹配某一个bean,自动为指定的bean中所依赖的类型或接口属性赋值
准备工作:UserDao,UserService的接口和实现类里面添加自定义方法,添加UserController类,在UserController中添加 UserService属性,添加UserService的set方法,然后在方法中调用UserService中的方法,然后再UserServiceImpl中添加Userdao属性进行相同操作,调用Userdao的方法
原始操作要在UserService中创建UserImpl的实现类,然后再UserController创建UserServiceImpl的实现类,现在用spring来进行简化
创建bean-auto.xml (根据类型来自动装配) autowire=”byType”
<?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="userController" class="com.jason.test.auto.UserController" autowire="byType"></bean> <bean id="userService" class="com.jason.test.auto.UserServiceImpl" autowire="byType"></bean> <bean id="userDao" class="com.jason.test.auto.UserDaoImpl"></bean> </beans>
|
若在IOC中,没有任何一个兼容类型的bean能为属性赋值,默认为null。
byName方法通过属性的Set方法名去除Set后的方法名来判断的,非属性名
基于注解管理Bean
步骤
通过注解自动装配的步骤,简化xml配置
- 引入依赖
- 开启组件扫描
- 使用注解定义Bean
- 依赖注入
组件扫描
<context:component-scan base-package="com.jason.test">
<context:include-filter type="annotation" expression="com.jason.test.User"/>
|
使用注解定义Bean
注解 |
说明 |
@Compnent |
用于描述spring中的Bean,可用于任何层次 |
@Repository |
多用于数据访问层(Dao层) |
@Service |
多用于业务层(Service层) |
@Controller |
多用于控制层(Controller) |
在需要定义的类上添加注解即可
@Autowired注入
@Autowired默认用类型进行装配
@Controller public class UserController {
@Autowired @Qualifier(value = "userServiceImpl") private UserService userService;
public void addUser(){ userService.addUserService(); } }
|
@Resource注入
@Resource默认用名称进行装配byName,未指定名称时,使用属性名作为name,找不到name时自动启动通过类型byType装配
需要提前引入依赖
<dependency> <groupId>jakarta.annotation</groupId> <artifactId>jakarta.annotation-api</artifactId> <version>2.1.1</version> </dependency>
|
public class UserController { @Resource(name = "myuserService") private UserService userService;
|
Spring全注解开发
用配置类去替代配置文件(bean.xml)
创建SpringConfig配置类
package com.jason.test.bean.auto.config;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration;
@Configuration @ComponentScan("com.jason.test.bean.auto") public class SpringConfig { }
|
测试类
public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); UserController bean = context.getBean("userController", UserController.class); bean.addUser(); } }
|
手写SpringIoc
先新建子模块,然后创建service接口和dao接口以及他们的实现类,再UserServiceImpl中获取userdao中的方法,新建Bean注解和DI注解,Bean注解用于创建对象,DI注解用于进行属性注入
创建anno包,新建Bean注解和DI注解
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Bean { }
|
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface DI { }
|
创建bean包,新建ApplicationContext接口和AnnotationApplicationContext类
package com.jason.bean;
public interface ApplicationContext { Object getBean(Class clazz); }
|
package com.jason.bean;
import com.jason.anno.Bean; import com.jason.anno.DI;
import java.io.File; import java.lang.reflect.Field; import java.net.URL; import java.net.URLDecoder; import java.util.Enumeration; import java.util.HashMap; import java.util.Map;
public class AnnotationApplicationContext implements ApplicationContext{ private Map<Class,Object> beanFactory= new HashMap<>(); private static String rootPath;
@Override public Object getBean(Class clazz) { return beanFactory.get(clazz); } public AnnotationApplicationContext(String basePackage){ String packagePath = basePackage.replaceAll("\\.", "\\\\"); try { Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath); URL url = urls.nextElement(); String filePath = URLDecoder.decode(url.getFile(), "utf-8"); rootPath = filePath.substring(0, filePath.length() - packagePath.length()); loadBean(new File(filePath)); } catch (Exception e) { e.printStackTrace(); }
loadDi(); } private void loadBean(File file) throws Exception{ if(file.isDirectory()){ File[] childFiles = file.listFiles(); if(childFiles ==null || childFiles.length==0){ return; } for (File child : childFiles) { if(child.isDirectory()){ loadBean(child); }else{ String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1); if(pathWithClass.contains(".class")){ String allName = pathWithClass.replaceAll("\\\\", ".").replace(".class",""); Class<?> clazz = Class.forName(allName); if(!clazz.isInterface()){ Bean annotation = clazz.getAnnotation(Bean.class); if(annotation !=null){ Object instance = clazz.getConstructor().newInstance(); if(clazz.getInterfaces().length>0){ beanFactory.put(clazz.getInterfaces()[0],instance); }else { beanFactory.put(clazz,instance); } } } } } } } } private void loadDi() { for (Map.Entry<Class, Object> entries : beanFactory.entrySet()) { Object obj = entries.getValue(); Class<?> clazz = obj.getClass(); for (Field filed : clazz.getDeclaredFields()) { if(filed.getAnnotation(DI.class)!=null) { filed.setAccessible(true); try { filed.set(obj,beanFactory.get(filed.getType())); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } } }
|
测试类测试
package com.jason; public class TestUser { public static void main(String[] args) { ApplicationContext context = new AnnotationApplicationContext("com.jason"); UserService userService =(UserService) context.getBean(UserService.class); userService.add(); } }
|
面向切面:AOP
代理模式
调用目标方法时,先调用代理对象的方法,减少对目标方法的调用,同时让附加功能能集中在一起方便维护,把不属于核心逻辑的代码从方法中剥离出来,达到解耦的目的
静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。
动态代理
public interface Calcaulator { int add(int i,int j); int sub(int i,int j); int mul(int i,int j); int div(int i,int j); }
|
package com.jason.aop;
import org.aopalliance.intercept.Invocation;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays;
public class ProxyFactory { private Object target;
public ProxyFactory(Object target) { this.target = target; }
public Object getProxy(){
ClassLoader classLoader = target.getClass().getClassLoader(); Class<?>[] interfaces = target.getClass().getInterfaces(); InvocationHandler invocationHandler = new InvocationHandler(){
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args)); Object result = method.invoke(target, args); System.out.println("[动态代理][日志] "+method.getName()+",结果:"+ result); return result; } }; return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler); } }
|
package com.jason.aop;
public class Test1 { public static void main(String[] args) { ProxyFactory proxyFactory = new ProxyFactory(new CalcaulatorImpl()); Calcaulator proxy = (Calcaulator) proxyFactory.getProxy(); proxy.add(2,3); } }
|
基于注解的AOP
JDK动态代理:JDK原生的实现方式,代理对象和和目标对象实现同样的接口
cglib动态代理:没有接口,通过继承被代理的目标类 ,生成子类代理对象
AspectJ:本质上是静态代理,Spring只是借用了AspectJ中的注解。
引入依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>6.0.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>6.0.2</version> </dependency>
|
创建接口和实现类
package com.jason.aop.annoaop;
public interface Calcaulator { int add(int i,int j); int sub(int i,int j); int mul(int i,int j); int div(int i,int j); }
|
package com.jason.aop.annoaop;
@Component public class CalcaulatorImpl implements Calcaulator { @Override public int add(int i, int j) { int result =i+j; System.out.println("方法内部result="+result); return result; } @Override public int sub(int i, int j) { int result =i-j; System.out.println("方法内部result="+result); return result; } @Override public int mul(int i, int j) { int result =i*j; System.out.println("方法内部result="+result); return result; } @Override public int div(int i, int j) { int result =i/j; System.out.println("方法内部result="+result); return result; } }
|
添加前置通知 创建切面类
package com.jason.aop.annoaop;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect @Component public class LogAspect { @Before("execution(public int com.jason.aop.annoaop.CalcaulatorImpl.*(..))") public void beforeMethod(JoinPoint joinPoint){ String name = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); System.out.println("Logger-->前置通知,"+"增强方法的名字"+name+"参数"+ Arrays.toString(args)); } @AfterReturning("execution(public int com.jason.aop.annoaop.CalcaulatorImpl.*(..))", returning = "result") public void afterReturningMethod(JoinPoint joinPoint, Object result){ String methodName = joinPoint.getSignature().getName(); System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result); } @AfterThrowing("execution(public int com.jason.aop.annoaop.CalcaulatorImpl.*(..))", throwing = "ex") public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){ String methodName = joinPoint.getSignature().getName(); System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex); } @Around("execution(public int com.jason.aop.annoaop.CalcaulatorImpl.*(..))") public Object aroundMethod(ProceedingJoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); String args = Arrays.toString(joinPoint.getArgs()); Object result = null; try { System.out.println("环绕通知-->目标对象方法执行之前"); result = joinPoint.proceed(); System.out.println("环绕通知-->目标对象方法返回值之后"); } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println("环绕通知-->目标对象方法出现异常时"); } finally { System.out.println("环绕通知-->目标对象方法执行完毕"); } return result; } }
|
创建bean.xml 开启组件扫描和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: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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="com.jason.aop.annoaop"></context:component-scan> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
|
测试
public class Test1 { @Test public void add(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); Calcaulator calcaulator = context.getBean(Calcaulator.class); System.out.println(calcaulator.add(1, 2)); } }
|
优化
切入点表达式配置切入点
@Pointcut("execution(public int com.jason.aop.annoaop.*.*(..))") public void pointCut(){}
|
@Before("pointCut()") @Before("com.atguigu.aop.CommonPointCut.pointCut()")
|
使用@order来控制切面优先级 数越小,优先级越高
基于xml的AOP
删除切面类中的切面类注解和通知类型注解,只保留ioc容器注解 创建beanaop.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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="com.jason.aop.annoaop"></context:component-scan>
<aop:config> <aop:aspect ref="logAspect">
<aop:pointcut id="pointcut" expression="execution(public int com.jason.aop.annoaop.CalcaulatorImpl.*(..))"/>
<aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
<aop:after-returning method="afterReturningMethod" returning="result" pointcut-ref="pointcut"></aop:after-returning> </aop:aspect> </aop:config> </beans>
|
JUnit5 整合spring6
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>6.0.2</version> </dependency>
|
写一个User类,创建一个方法,创建bean.xml开启组件扫描
package com.jason.spring6;
import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig(locations = "classpath:bean.xml") public class JUit5Test { @Autowired private User user; @Test public void testUser(){ System.out.println(user); user.run(); } }
|
事务
使用 JdbcTemplate 方便实现对数据库操作
添加依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>6.0.2</version> </dependency>
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency>
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.31</version> </dependency>
|
创建jdbc.properties
jdbc.user=root jdbc.password=root jdbc.url=jdbc:mysql: jdbc.driver=com.mysql.cj.jdbc.Driver
|
创建数据表
CREATE DATABASE `spring`;
use `spring`;
CREATE TABLE `t_emp` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) DEFAULT NULL COMMENT '姓名', `age` int(11) DEFAULT NULL COMMENT '年龄', `sex` varchar(2) DEFAULT NULL COMMENT '性别', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
@SpringJUnitConfig(locations = "classpath:bens.xml") public class JdbcTemplateTest { @Autowired private JdbcTemplate jdbcTemplate;
@Test public void testUpdate(){ String sql ="insert into t_emp values(null,?,?,?)"; int update = jdbcTemplate.update(sql, "millet", 20, "女", 1); } }
|
@SpringJUnitConfig(locations = "classpath:bens.xml") public class JdbcTemplateTest { @Autowired private JdbcTemplate jdbcTemplate;
@Test public void testUpdate(){ String sql ="insert into t_emp values(null,?,?,?)"; int update = jdbcTemplate.update(sql, "millet", 20, "女", 1); } @Test public void testSelect(){
} @Test public void testSelectValue(){ String sql ="select count(*) from t_emp"; Integer count = jdbcTemplate.queryForObject(sql, Integer.class); System.out.println(count); }
|
基于注解的声明式事务
事务在代码里或者数据库中都可以配置。
其含义理解为 一系列的数据操作,要么全部执行完成、要么都不执行。归纳为
1、原子性:事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
2、一致性:一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败
3、隔离性:事务之间应该隔离开来。因为可能有许多事务会同时处理相同的数据,每个事务都应该与其他事务有隔离策略。
4、持久性:一旦事务完成,它的结果不会收到影响。通常情况下,事务的结果被写到持久化存储器中。
声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
模拟场景 买书流程
创建dao和service的接口和实现类,添加买书方法,添加Controller类
创建bens.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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.jason.spring6.tx"></context:component-scan>
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="druidDataSource"></property> </bean> </beans>
|
package com.jason.spring6.tx.dao; public interface BookDao { Integer getBookPriceByBookId(Integer bookid); void updateStock(Integer bookid); void updateUserBalance(Integer userid, Integer price); }
|
@Repository public class BookDaoImpl implements BookDao{ @Autowired private JdbcTemplate jdbcTemplate; @Override public Integer getBookPriceByBookId(Integer bookid) { String sql ="select price from t_book where book_id=?"; Integer price = jdbcTemplate.queryForObject(sql, Integer.class, bookid); return price; } @Override public void updateStock(Integer bookid) { String sql = "UPDATE t_book SET stock=stock-1 WHERE book_id =?"; jdbcTemplate.update(sql,bookid); } @Override public void updateUserBalance(Integer userid, Integer price) { String sql ="UPDATE t_user SET balance= balance -? WHERE user_id=?"; jdbcTemplate.update(sql,price,userid); } }
|
package com.jason.spring6.tx.service; public interface BookService { void buyBook(Integer bookid, Integer userid); }
|
@Service public class BookServiceImpl implements BookService{ @Autowired private BookDao bookDao; @Override public void buyBook(Integer bookid, Integer userid) { Integer price =bookDao.getBookPriceByBookId(bookid); bookDao.updateStock(bookid); bookDao.updateUserBalance(userid,price); } }
|
@Controller public class BookController { @Autowired private BookService bookService; public void buyBook(Integer bookid,Integer userid){ bookService.buyBook(bookid,userid); } }
|
@SpringJUnitConfig(locations = "classpath:bens.xml") public class TestBook { @Autowired private BookController bookController; @Test public void testBuyBook(){ bookController.buyBook(1,1); } }
|
但是当我们余额小于买书价格时,库存也减少了,我们希望这三个方法在执行时,只要一个不满足就会失败,方法回滚,声明式事务
配置文件中添加配置
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="druidDataSource"></property> </bean>
<tx:annotation-driven transaction-manager="transactionManager" />
|
在ServiceImpl类上写上 @Transactional 就可以实现声明式事务
事务属性
只读 : @Transactional(readOnly = true) 操作不涉及写操作。这样数据库就能够针对查询操作来进行优化,但是只能用在查询操作中,在增删改操作中不被允许
超时:在程序长时间占用资源或者出问题时,程序应该被回滚,释放资源
@Transactional(timeout = 3) public void buyBook(Integer bookId, Integer userId) { try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
}
|
回滚策略
声明式事务默认只针对运行时异常回滚,编译时异常不回滚。可以通过@Transactional中相关属性设置回滚策略
rollbackFor属性:需要设置一个Class类型的对象
rollbackForClassName属性:需要设置一个字符串类型的全类名
noRollbackFor属性:需要设置一个Class类型的对象
rollbackFor属性:需要设置一个字符串类型的全类名
@Transactional(noRollbackFor = ArithmeticException.class)
public void buyBook(Integer bookId, Integer userId) { Integer price = bookDao.getPriceByBookId(bookId); bookDao.updateStock(bookId); bookDao.updateBalance(userId, price); System.out.println(1/0); }
|
隔离行为
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
隔离级别一共有四种:
读未提交:READ UNCOMMITTED
允许Transaction01读取Transaction02未提交的修改。
读已提交:READ COMMITTED、
要求Transaction01只能读取Transaction02已提交的修改。
可重复读:REPEATABLE READ
确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。
串行化:SERIALIZABLE
确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。
各个隔离级别解决并发问题的能力见下表:
隔离级别 |
脏读 |
不可重复读 |
幻读 |
READ UNCOMMITTED |
有 |
有 |
有 |
READ COMMITTED |
无 |
有 |
有 |
REPEATABLE READ |
无 |
无 |
有 |
SERIALIZABLE |
无 |
无 |
无 |
各种数据库产品对事务隔离级别的支持程度:
隔离级别 |
Oracle |
MySQL |
READ UNCOMMITTED |
× |
√ |
READ COMMITTED |
√(默认) |
√ |
REPEATABLE READ |
× |
√(默认) |
SERIALIZABLE |
√ |
√ |
②使用方式
@Transactional(isolation = Isolation.DEFAULT) @Transactional(isolation = Isolation.READ_UNCOMMITTED) @Transactional(isolation = Isolation.READ_COMMITTED) @Transactional(isolation = Isolation.REPEATABLE_READ) @Transactional(isolation = Isolation.SERIALIZABLE)
|
全注解开发
用配置类去代替配置文件
@Configuration @ComponentScan("com.jason.spring6.tx") @EnableTransactionManagement public class SpringConfig { @Bean public DataSource getDataSource(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false"); dataSource.setUsername("root"); dataSource.setPassword("123456"); return dataSource; }
@Bean(name = "jdbcTemplate") public JdbcTemplate getJdbcTemplate(DataSource dataSource){ JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; }
@Bean public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){ DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dataSource); return dataSourceTransactionManager; } }
|
基于xml声明式事务
添加依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>6.0.2</version> </dependency>
|
修改配置类
<aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.atguigu.spring.tx.xml.service.impl.*.*(..))"></aop:advisor> </aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="get*" read-only="true"/> <tx:method name="query*" read-only="true"/> <tx:method name="find*" read-only="true"/> <tx:method name="save*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/> <tx:method name="update*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/> <tx:method name="delete*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/> </tx:attributes> </tx:advice>
|
Resource
Resource实现类
为了使用统一的方式访问资源,Spring 将资源抽象为 Resource,将资源的加载抽象为 ResourceLoader。Spring 配置文件的读取以及扫描包中的 bean 都会通过 Resource 访问资源。Resource接口继承了InputStreamSource接口,提供了很多InputStreamSource所没有的方法
UrlResource访问网络资源
public class UrlResourceDemo { public static void loadAndReadUrlResource(String path){ UrlResource url = null; try { url = new UrlResource(path); System.out.println(url.getFilename()); System.out.println(url.getURI()); System.out.println(url.getDescription()); System.out.println(url.getInputStream().read()); } catch (Exception e) { throw new RuntimeException(e); } } public static void main(String[] args) { loadAndReadUrlResource("http://www.baidu.com"); } }
|
ClassPathResource 访问类路径下资源
public class ClassPathResourceDemo {
public static void loadAndReadUrlResource(String path) throws Exception{ ClassPathResource resource = new ClassPathResource(path); System.out.println("resource.getFileName = " + resource.getFilename()); System.out.println("resource.getDescription = "+ resource.getDescription()); InputStream in = resource.getInputStream(); byte[] b = new byte[1024]; while(in.read(b)!=-1) { System.out.println(new String(b)); } } public static void main(String[] args) throws Exception { loadAndReadUrlResource("atguigu.txt"); } }
|
使用FileSystemResource 访问文件系统资源
public class FileSystemResourceDemo {
public static void loadAndReadUrlResource(String path) throws Exception{ FileSystemResource resource = new FileSystemResource("atguigu.txt"); System.out.println("resource.getFileName = " + resource.getFilename()); System.out.println("resource.getDescription = "+ resource.getDescription()); InputStream in = resource.getInputStream(); byte[] b = new byte[1024]; while(in.read(b)!=-1) { System.out.println(new String(b)); } } public static void main(String[] args) throws Exception { loadAndReadUrlResource("atguigu.txt"); } }
|
还有ServletContextResource,InputStreamResource,ByteArrayResource
ResourceLoader接口
Resource getResource(String location)** : 该接口仅有这个方法,用于返回一个Resource实例,我们可以分别用ClassPathXmlApplicationContext和FileSystemXmlApplicationContext来获取ApplicationContext对象 通过getResource来获取相对应的实例,实际上并不需要直接使用Resource实现类,而是调用ResourceLoader实例的getResource()方法来获得资源,ReosurceLoader将会负责选择Reosurce实现类,也就是确定具体的资源访问策略,从而将应用程序和具体的资源访问策略分离开来
数据校验Validation
在开发中,我们经常遇到参数校验的需求,比如用户注册的时候,要校验用户名不能为空、用户名长度不超过20个字符、手机号是合法的手机号格式等等。如果使用普通方式,我们会把校验的代码和真正的业务处理逻辑耦合在一起,而spring validation允许通过注解的方式来定义对象校验规则,把校验和业务逻辑分离开,让代码编写更加方便。Spring Validation其实就是对Hibernate Validator进一步的封装,方便在Spring中使用。
第一种是通过实现org.springframework.validation.Validator接口,然后在代码中调用这个类
创建Validator接口,实现接口方法指定校验规则
public class PersonValidator implements Validator { @Override public boolean supports(Class<?> clazz) { return Person.class.equals(clazz); }
@Override public void validate(Object object, Errors errors) { ValidationUtils.rejectIfEmpty(errors, "name", "name.empty","name is null"); Person p = (Person) object; if (p.getAge() < 0) { errors.rejectValue("age", "error value < 0"); } else if (p.getAge() > 150) { errors.rejectValue("age", "error value too old"); } } }
|
测试类
public class TestMethod1 { public static void main(String[] args) { Person person = new Person(); person.setName("lucy"); person.setAge(-1); DataBinder binder = new DataBinder(person); binder.setValidator(new PersonValidator()); binder.validate(); BindingResult results = binder.getBindingResult(); System.out.println(results.getAllErrors()); } }
|
第二种是按照Bean Validation方式来进行校验,即通过注解的方式。
spring默认有一个实现类LocalValidatorFactoryBean,它实现了上面Bean Validation中的接口,并且也实现了org.springframework.validation.Validator接口。
创建配置类
@Configuration @ComponentScan("com.atguigu.spring6.validation.method2") public class ValidationConfig {
@Bean public LocalValidatorFactoryBean validator() { return new LocalValidatorFactoryBean(); } }
|
再实体类上加入注解可实现校验
public class User { @NotNull private String name; @Min(0) @Max(120) private int age;
|
常用注解说明
@NotNull 限制必须不为null
@NotEmpty 只作用于字符串类型,字符串不为空,并且长度不为0
@NotBlank 只作用于字符串类型,字符串不为空,并且trim()后不为空串
@DecimalMax(value) 限制必须为一个不大于指定值的数字
@DecimalMin(value) 限制必须为一个不小于指定值的数字
@Max(value) 限制必须为一个不大于指定值的数字
@Min(value) 限制必须为一个不小于指定值的数字
@Pattern(value) 限制必须符合指定的正则表达式
@Size(max,min) 限制字符长度必须在min到max之间
@Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式
使用jakarta.validation.Validator校验或使用org.springframework.validation.Validator校验
@Service public class MyService1 {
@Autowired private Validator validator; public boolean validator(User user){ Set<ConstraintViolation<User>> sets = validator.validate(user); return sets.isEmpty(); } public boolean validaPersonByValidator(User user) { BindException bindException = new BindException(user, user.getName()); validator.validate(user, bindException); return bindException.hasErrors(); } }
|
测试
public class TestMethod2 {
@Test public void testMyService1() { ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class); MyService1 myService = context.getBean(MyService1.class); User user = new User(); user.setAge(-1); boolean validator = myService.validator(user); System.out.println(validator); }
@Test public void testMyService2() { ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class); MyService2 myService = context.getBean(MyService2.class); User user = new User(); user.setName("lucy"); user.setAge(130); user.setAge(-1); boolean validator = myService.validaPersonByValidator(user); System.out.println(validator); } }
|
第三种是基于方法实现校验
创建配置类,配置MethodValidationPostProcessor
@Configuration @ComponentScan("com.atguigu.spring6.validation.method3") public class ValidationConfig {
@Bean public MethodValidationPostProcessor validationPostProcessor() { return new MethodValidationPostProcessor(); } }
|
创建实体类,定义Service类,测试
public class User {
@NotNull private String name;
@Min(0) @Max(120) private int age;
@Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$",message = "手机号码格式错误") @NotBlank(message = "手机号码不能为空") private String phone;
|
@Service @Validated public class MyService { public String testParams(@NotNull @Valid User user) { return user.toString(); }
}
|
public class TestMethod3 {
@Test public void testMyService1() { ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class); MyService myService = context.getBean(MyService.class); User user = new User(); user.setAge(-1); myService.testParams(user); } }
|
AOP提前编译
就是提前把字节码转成机器码,在程序运行前编译,可以避免在运行时的编译性能消耗和内存消耗,可以在程序运行初期就达到最高性能,程序启动速度快
缺点:由于是静态提前编译,不能根据硬件情况或程序运行情况择优选择机器指令序列,理论峰值性能不如JIT,没有动态能力,同一份产物不能跨平台运行