java-spring
入门案例
通过bean创建对象
- 先通过spring的ClassPathXmlApplicationContext读取xml文件 ,然后通过getbean()函数获取对象,进行操作
- 通过反射机制,吸纳Class的函数forName(class属性)创建对象,然后clazz.getDeclaredConstructor().newinstance()返回对象
- 通过Bean创建的对象底层代码时一个Map,其中key值是对象的id(唯一标识),value值是类的定义(辅助信息)
Apache Log4j2(一个开源的的日志记录组件)
-
日志的优先级,从低到高为"TRACE<DEBUG<INFO<WARN<ERROR<FATAL"
- TRACE: 追踪,最低级别,相当于追踪程序的执行
- DEBUG:调试,一般开发都将其设置为最低的日志级别
- INFO:信息–>警告–>错误–>严重错误
指示重要程度,自动屏蔽级别低的日志
-
日志信息的输出目的地:制定了将日志打印到控制台还是文件
-
日志信息的输出格式:输出格式控制了日志信息的显示内容
添加日志:
-
添加依赖
<dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.19.0</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j18-impl</artifactId><version>2.18.0</version></dependency>
-
在类的根路径下提供log4j2.xml配置文件
-
使用日志
private final Logger logger = LoggerFactory.getLogger(testmain.class); //手写动态日志 logger.info("### 4.执行调用成功");
Ioc
IoC(inversion of control) ,控制反转
,它不是一门技术,而是一种设计思想,是一个重要的面向对象编程法则,通过IoC容器来管理所有对象的初始化和实例化,控制对象之间的依赖关系,我们将有IoC管理的java对象称为bean
,实际上与使用关键字new创建的java对象没有任何区别
- 控制反转是为了降低程序耦合度,提高程序扩展力
- 控制反转,反转的是
- 对象的创建权交给第三方容器负责
- 将对象之间的维护关系交出去,交给第三方负责
- 通过依赖**注入(DI)**实现
依赖注入(DI)
- 指spring创建过程中将对象依赖属性通过配置进行注入
两种常见的实现方式
- set注入
- 构造注入
- IoC是一种控制反转的思想,DI是对IoC的实现
- bean管理:bean对象的创建以及bean对象中属性的赋值
IoC容器在Spring中的实现
Spring的IoC容器就是IoC思想的一个落地的产品实现,IoC容器中管理的组件也叫做bean。在创建bean之前,首先需要创建IoC容器
创建IoC容器的两种实现方式
- BeanFactory : IoC容器的基本实现,spring的内部接口,但不提供给开发人员使用
- ApplicationContext:BeanFactory的子接口,提供了更多高级特性,一般都使用该方式创建IoC容器
基于xml管理bean
环境搭建
-
创建对应的子工程
-
将子工程中的所有依赖全部移到父工程的xml文件里面
搭建子模块
获取Bean
- 测试代码
import com.lele.User;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class testuser {public static void main(String[] args) throws Exception {ClassPathXmlApplicationContext context =new ClassPathXmlApplicationContext("bean.xml");//获取对象的方式// 根据id获取User user1 = (User)context.getBean("user");System.out.println("1 根据id获取:"+user1);//根据类型获取User user2 = context.getBean(User.class);System.out.println("2 根据类型获取:"+user2);//根据id和类型获取User user3 = context.getBean("user", User.class);System.out.println("3 根据id和类型获取:"+user1);//根据反射Class<?> aClass = Class.forName("com.lele.User");User o = (User)aClass.getDeclaredConstructor().newInstance();System.out.println("4 根据反射获取"+o);//根据接口获取类ClassPathXmlApplicationContext context1 = new ClassPathXmlApplicationContext("bean.xml");UserDao user4 = context1.getBean(UserDao.class);System.out.println("5.根据接口类型获取"+user4);user4.run();}
}
注意:根据类型获取bean的时候要求IoC容器中指定类型的bean有且只有一个,否则会报错No qualifying bean of type"…",expecter single matching bean but found 2
对应的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.lele.User"></bean><bean id="userDaoimpl" class="com.lele.UserDaoimpl"></bean>
</beans>
- 如果组件类实现了接口,根据接口类型可以获取bean,但前提是bean唯一
总结
- 根据类型获取bean是,在满足bean唯一性的前提下,其实只是看:【对象instanceof指定类型】返回结果,只要是返回true,就可以认定为和类型匹配,可以获得bean对象
- java中,instanceof 运算符用于判断前面的对象是否是后面的类,或其子类,实现类的示例,也就是说:用instanceof关键字做判断时,左右操作必须是有继承或实现关系
依赖注入
-
第一种方式:基于set方法完成
-
第一步:创建类,定义属性,生成属性set方法
-
第二步:在spring配置文件中配置
<bean id="book" class="com.lele.bl.Book"><property name="bname" value="spring全栈开发"></property><property name="bauthor" value="张佳乐"></property></bean>
-
-
第二种方式:基于构造器完成
-
第一步:创建类,定义属性,定义有参构造方法
-
第二步:在spring配置文件中配置
<bean id="bookcon" class="com.lele.bl.Book"><constructor-arg name="bname" value="c嘎嘎"></constructor-arg><constructor-arg name="bauthor" value="张佳乐"></constructor-arg><!--可以在name那里用index索引替换,但是要按顺序来,不推荐--></bean>
-
它们所在的测试代码
import com.lele.bl.Book;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class testbook {@Testpublic void testSetter(){ApplicationContext context = new ClassPathXmlApplicationContext("bean-dl.xml");Book book = context.getBean("book", Book.class);System.out.println(book);}@Testpublic void testConstructor(){ApplicationContext context = new ClassPathXmlApplicationContext("bean-dl.xml");Book bookcon = context.getBean("bookcon", Book.class);System.out.println(bookcon);}
}
特殊值处理
- 字面量赋值:普通的字面量,没有任何引申含义
- null值:
<property name="others"><null/></property>
- xml实体(使用转义字符)
<property name="others" value="<>"></property>
- CDATA节(里面可以添加特殊符号)
<property name="others"><value><![CDATA[a<b]]></value></property>
注意:上面的xml代码都是在里面的
为对象类型属性赋值
- 方式1:引用外部bean
<bean id="dept" class="http.Department"><property name="dname" value="人事部"></property></bean><bean id="emp" class="http.Employee"><!--普通类型--><property name="ename" value="张佳乐"></property><property name="age" value="10"></property><!--注入对象类型属性--><property name="dept" ref="dept"></property></bean>
- 方式2:内部bean注入
<bean id="emp2" class="http.Employee"><!--普通类型--><property name="ename" value="张佳乐"></property><property name="age" value="10"></property><!--注入对象类型属性--><property name="dept"><bean id="dept2" class="http.Department"><property name="dname" value="人事部"></property></bean></property></bean>
- 方式3:级联属性赋值
<bean id="emp3" class="http.Employee"><property name="ename" value="小帅哥"></property><property name="age" value="30"></property><property name="dept" ref="dept"></property><property name="dept.dname" value="保安部"></property></bean>
为数组属性进行赋值
<bean id="emp" class="http.Employee"><property name="ename" value="张小乐"></property><property name="age" value="100"></property><property name="dept" ref="dept"></property><property name="hobbies"><array><value>吃饭</value><value>睡觉</value><value>敲代码</value></array></property></bean>
为集合类型属性进行赋值
- list集合
<bean id="emp1" class="http.Employee"><property name="ename" value="张大乐"></property><property name="age" value="10"></property></bean><bean id="emp2" class="http.Employee"><property name="ename" value="张小乐"></property><property name="age" value="100"></property></bean><bean id="dept" class="http.Department"><property name="dname" value="财务部"></property><property name="emplist"><list><ref bean="emp1"></ref><ref bean="emp2"></ref></list></property></bean>
- map集合
<bean id="teacher1" class="school.Teacher"><property name="tname" value="张三"></property><property name="tid" value="11000"></property></bean><bean id="teacher2" class="school.Teacher"><property name="tname" value="王五"></property><property name="tid" value="14000"></property></bean><bean id="student" class="school.Student"><property name="sname" value="李四"></property><property name="sid" value="10086"></property><property name="teacherMap"><map><entry><key><value>10086</value></key><ref bean="teacher1"></ref></entry><entry><key><value>19000</value></key><ref bean="teacher2"></ref></entry></map></property></bean>
- 引入集合类型的bean
- 注意:要引入util的命令空间
<?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:util="http://www.springframework.org/schema/util"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util.xsd"><bean id="teacher1" class="school.Teacher"><property name="tname" value="张三"></property><property name="tid" value="11000"></property></bean><bean id="teacher2" class="school.Teacher"><property name="tname" value="王五"></property><property name="tid" value="14000"></property></bean><bean id="student" class="school.Student"><property name="sname" value="张三"></property><property name="sid" value="7839"></property><property name="lessons" ref="lessonlist"></property><property name="teacherMap" ref="teachermap"></property></bean><bean id="lesson1" class="school.Lesson"><property name="lessonName" value="java开发"></property></bean><bean id="lesson2" class="school.Lesson"><property name="lessonName" value="前端开发"></property></bean><util:list id="lessonlist"><ref bean="lesson1"></ref><ref bean="lesson2"></ref></util:list><util:map id="teachermap"><entry><key><value>10086</value></key><ref bean="teacher1"></ref></entry><entry><key><value>39020</value></key><ref bean="teacher2"></ref></entry></util:map>
</beans>
p命令空间
- 注意:要引入p命令空间
<?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:p="http://www.springframework.org/schema/p"xmlns:util="http://www.springframework.org/schema/util"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util.xsd"><bean id="teacher1" class="school.Teacher"><property name="tname" value="张三"></property><property name="tid" value="11000"></property></bean><bean id="teacher2" class="school.Teacher"><property name="tname" value="王五"></property><property name="tid" value="14000"></property></bean><bean id="lesson1" class="school.Lesson"><property name="lessonName" value="java开发"></property></bean><bean id="lesson2" class="school.Lesson"><property name="lessonName" value="前端开发"></property></bean><bean id="students" class="school.Student" p:sid="10083" p:sname="大哥"p:lessons-ref="lessonlist" p:teacherMap-ref="teachermap"></bean><util:list id="lessonlist"><ref bean="lesson1"></ref><ref bean="lesson2"></ref></util:list><util:map id="teachermap"><entry><key><value>10086</value></key><ref bean="teacher1"></ref></entry><entry><key><value>39020</value></key><ref bean="teacher2"></ref></entry></util:map>
</beans>
引入外部属性文件(以数据库为例)
- 引入数据库相关依赖
- 创建外部属性文件,properties格式,定义数据信息:用户名 密码 地址等
- 创建spring配置文件,引入context命名空间,引入属性相关文件,使用表达式完成注入
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!--引入外部属性文件--><context:property-placeholder location="classpath:jdbc.properties"/><!--完成信息库数据注入--><bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="url" value="jdbc::mysql://localhost:3306/ssm?serverTimezone=UTC"></property><property name="passworld" value="root"></property><property name="user" value="root"></property><property name="driverClassName" value="com.mysql.jdbc.Driver"></property></bean></beans>
jdbc.user = root
jdbc.password = root
jdbc.url = jdbc::mysql://localhost:3306/ssm?serverTimezone=UTC
jdbc.driver = com.mysql.jdbc.Driver
前提要在父工程里面导入两个依赖
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.20</version></dependency>
bean的作用域
在spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义如下:
取值 | 含义 | 创建对象的时机 |
---|---|---|
singleton | 在Ioc容器中,这个bean的对象始终为单实例 | IOC容器初始化时 |
prototype | 这个bean在IOC容器中有多个示例 | 获取bean时 |
如果在WebApplicationContext环境下可能还有其他几个(但不常用):
request | 在一个请求范围内有效 |
---|---|
session | 在一个会话范围内有效 |
测试:
<bean id="orders" class="scope.Orders" scope="singleton"></bean>-->
对应的测试代码
public class Testscope {public static void main(String[] args){ApplicationContext context = new ClassPathXmlApplicationContext("bean-scope.xml");Orders orders = (Orders)context.getBean("orders",Orders.class);System.out.println(orders);Orders orders1 = (Orders)context.getBean("orders", Orders.class);System.out.println(orders1);}
}
2025-05-09 21:58:01 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@2002fc1d
2025-05-09 21:58:01 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 2 bean definitions from class path resource [bean-scope.xml]
2025-05-09 21:58:01 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean ‘orders’
scope.Orders@2a7ed1f
scope.Orders@2a7ed1f
测试:
<bean id="orders1" class="scope.Orders" scope="prototype"/>
对应的测试代码
public class Testscope {public static void main(String[] args){ApplicationContext context = new ClassPathXmlApplicationContext("bean-scope.xml");Orders orders2 = (Orders)context.getBean("orders1",Orders.class);System.out.println(orders2);Orders orders3 = (Orders)context.getBean("orders1", Orders.class);System.out.println(orders3);}
}
2025-05-09 22:00:09 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@20d525
2025-05-09 22:00:09 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 1 bean definitions from class path resource [bean-scope.xml]
scope.Orders@560348e6
scope.Orders@1df8b5b8 (不相同,表示不同的bean)
bean的生命周期
- 具体的生命周期过程
- bean对象创建(调用无参构造器)
- 给bean对象设置属性
- bean的后置处理器(初始化之前)
- bean对象初始化(需在配置bean时指定初始化方法)
- bean的后置处理器(初始化之后)
- bean对象就绪可以使用
- bean对象销毁(需在配置bean时指定销毁方法)
- IOC容器关闭
具体源码:
package life;
//User类
public class User {private String name;public User(){System.out.println("1 对象创建,调用无参构造");}public String getName() {return name;}public void setName(String name) {System.out.println("2 给bean对象设置属性值");this.name = name;}//初始化方法public void initMethod(){System.out.println("4 bean对象初始化调用指定的初始化方法");}//销毁方法public void destroyMethod(){System.out.println("7 bean对象销毁调用指定的销毁方法");}
}
package post;
//MyPostProcess类
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
//后置处理器
public class MyPostProcess implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean,String beanName) throws BeansException {System.out.println("3 bean初始化之前调用后置处理器");return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean,String beanName) throws BeansException {System.out.println("5 bean初始化之后调用后置处理器");return bean;}
}
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="user" class="life.User" scope="singleton"init-method="initMethod" destroy-method="destroyMethod"><property name="name" value="lucy"></property></bean><bean id="mypostprocess" class="post.MyPostProcess" ></bean>
</beans>
import life.User;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class TestUsersww {public static void main(String[] args){ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean-life.xml");//这里必须用ClassPathXmlApplicationCOntext具体实现类作为对象,不能用ApplicationContext接口类作为返回对象类型,因为接口类没有close()函数进行销毁User user = (User)context.getBean("user");System.out.println("6 bean创建对象完成,可以使用了");System.out.println(user);context.close();}}
FactoryBean
FactoryBean是Spring提供的一种整合第三方框架的常用机制,配置一个该类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象而是其方法getObject()方法的返回值,通过这种方法,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用姐买你给我们
应用实例–整合Mybatics
FactoryBean的源码:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package org.springframework.beans.factory;import org.springframework.lang.Nullable;public interface FactoryBean<T> {String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";@NullableT getObject() throws Exception;@NullableClass<?> getObjectType();default boolean isSingleton() {return true;}
}
测试具体代码:
package Factory;
//MyFactoryBean类
import org.springframework.beans.factory.FactoryBean;public class MyFcatoryBean implements FactoryBean<User> {@Overridepublic User getObject() throws Exception{return new User();}@Overridepublic Class<?> getObjectType(){return User.class;}
}
<?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="myfactorybean" class="factory.MyFcatoryBean"></bean>
</beans>
import factory.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
//测试函数
public class TestFactory {public static void main(String[] args){ApplicationContext context =new ClassPathXmlApplicationContext("bean-factory.xml");User factory = (User)context.getBean("myfactorybean");System.out.println(factory);}
}
2025-05-09 23:07:05 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@ae13544
2025-05-09 23:07:05 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 1 bean definitions from class path resource [bean-factory.xml]
2025-05-09 23:07:05 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean ‘myfactorybean’
factory.User@4e928fbf
基本xml的自动装配
根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的类类型接口或接口类型属性赋值
UserController类
package auto.controller;import auto.servies.UserServices;public class UserController {private UserServices userServices;public void setUserServices(UserServices userServices) {this.userServices = userServices;}public void addUser(){System.out.println("addUser");//调用Services方法userServices.addUserServices();// UserDao userdao = new UserDaoimpl(); //原生写法
// userdao.addUserDao();}
}
UserDaoimpl类
package auto.dao;public class UserDaoimpl implements UserDao{@Overridepublic void addUserDao() {System.out.println("addUserDao....");}
}
UserServicesimple类
package auto.servies;import auto.dao.UserDao;public class UserServicesimpl implements UserServices {private UserDao userdao;public void setUserdao(UserDao userdao) {this.userdao = userdao;}@Overridepublic void addUserServices() {System.out.println("addUserServices....");userdao.addUserDao();}
}
对应的测试代码:
import auto.controller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class Testauto {public static void main(String[] args){ApplicationContext context =new ClassPathXmlApplicationContext("bean-auto.xml");UserController usercontroller = context.getBean("usercontroller", UserController.class);usercontroller.addUser();}
}
这里有两种自动装配方法: byType和byName
区别在于如果使用byName,那么它的id属性一定要和变量名相同
<?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="auto.controller.UserController" autowire="byType"></bean>
<!--按照之前的做法我们可以通过property一个个注入,但是这里我们需要实现自动装配--><bean id="usercervices" class="auto.servies.UserServicesimpl" autowire="byType"></bean>
<!--如果改成byName则要注意id的取值必须和变量名保持一致--><bean id="userdao" class="auto.dao.UserDaoimpl"></bean></beans>
2025-05-10 17:53:41 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean ‘usercontroller’
2025-05-10 17:53:41 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean ‘usercervices’
2025-05-10 17:53:41 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean ‘userdao’
addUser
addUserServices…
addUserDao…
基于注解管理bean
简介
**注解:**是代码中的一种特殊标记,可以在编译、类加载和运行时被读取、执行相应的效果。开发人员可以通过注解在不改变原有代码和逻辑的情况下,在源代码中嵌入补充信息,从而简化xml配置
spring通过注解实现自动装配的步骤如下
- 引入依赖
- 开启组件扫描
- 使用注解定义bean
- 依赖注入
搭建子模块
搭建子模块spring6-ioc-annotation
引入相关依赖(日志文件)
<?xml version="1.0" encoding="UTF-8"?>
<!--Log4j2 配置文件status="WARN" : 禁止 Log4j2 内部日志输出(生产环境建议)monitorInterval="30" : 每隔30秒检查配置文件变化(热更新)
-->
<Configuration status="info" monitorInterval="30"><!-- 定义所有输出器(Appenders) --><Appenders><!-- 控制台输出器 --><Console name="spring6log" target="SYSTEM_OUT"><PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-3level %logger{1024} - %msg%n"/></Console><!-- 普通文件输出器(每次运行覆盖文件) --><File name="log" fileName="d:/lele--test/java/test.log" append="false"><PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/></File><!-- 滚动文件输出器(补充缺失的 RollingFile 配置) --><RollingFile name="RollingFile"fileName="d:/lele--test/java/app.log"filePattern="d:/lele--test/java/app-%d{yyyy-MM-dd}-%i.log"><PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/><Policies><!-- 按时间滚动(每天) --><TimeBasedTriggeringPolicy interval="1" modulate="true"/><!-- 按文件大小滚动(10MB) --><SizeBasedTriggeringPolicy size="10 MB"/></Policies><!-- 最多保留10个文件 --><DefaultRolloverStrategy max="10"/></RollingFile></Appenders><!-- 定义日志记录器(Loggers) --><Loggers><!-- Root 记录器(全局默认) --><Root level="debug"><!-- 修正引用名称大小写问题 --><AppenderRef ref="spring6log"/><AppenderRef ref="log"/><AppenderRef ref="RollingFile"/></Root></Loggers>
</Configuration>
开启组件扫描
spring默认不使用注解装配Bean,因此我们需要在Spring的xml配置中,通过context:component-scan元素开启Spring Beans的自动扫描功能。开启此功能后,Spring会自动从扫描指定的包(base-package属性设置)及其子包下的所有类,如果类上使用了@Component注解,就将该类装配到容器中
- 最基本的组件扫描方式
<context:component-scan base-package="com"></context:component-scan>
- 指定要排除的组件
<context:component-scan base-package="com"><!-- context:exclude-filter标签:指定排除规则--><!--type:设置排除或包含的依据type="annotation",根据注解排除,expression中设置要排除的注解的全类名type="assignable",根据类型排除,expression中设置要排除的类型的全类名--><context:exclude-filter type="annotation"expression="org.springframework.stereotpe.Controller"/><!--表示Controller注解不会扫描--><context:exclude-filter type="assignable"expression="com.controller.UserController"/><!--表示UserController类及其内部的方法属性不会扫描-->
</context:component-scan>
- 仅扫描指定组件
<context:component-scan base-package="com" use-default-filters="false"><!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则--><!-- use-default-filters属性:取值false表示关闭默认规则即扫描多有只当包下的所有类--><!--type:设置排除或包含的依据type="annotation",根据注解排除,expression中设置要排除的注解的全类名type="assignable",根据类型排除,expression中设置要排除的类型的全类名--><context:include-filter type="annotation"expression="org.springframework.stereotpe.Controller"/><!--表示只扫描Controller注解--><context:exclude-filter type="assignable"expression="com.controller.UserController"/><!--表示只扫描UserController类-->
</context:component-scan>
使用注解定义Bean
Spring提供了以下多个注解,这些注解可以直接标注在java类上,将它们定义成Spring Bean
注解 | 说明 |
---|---|
@Component | 该注解用于描述Spring中的Bean,是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次 |
@Respository | 该注解一般用于数据访问层(Dao层)(功能同@Component) |
@Service | 该注解一般用于业务层(Service层)(功能同@Component) |
@Controller | 该注解一般用于控制层(Controller层)功能同@Component) |
目前如果我们创建bean用这四个效果一样
@Component(value="user") //相当于<bean id="user" class="..."></bean>,如果没有参数value值则默认类的首字母小写
class User{//...
}
实验一:@Autowired注入
单独使用@Autowired注解,默认更具类型装配(byType)(只支持单个)
该注解的源码:
package org.springframework.beans.factory.annotation;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {boolean required() default true;
}
- 第一处@Target:该注解可以标注在:
- 构造方法上
- 方法上
- 形参上
- 属性上
- 注解上
- 第二处@Retention:该注解有一个required属性,表示在注入的Bean必须是存在的,如果不存在则报错。如果required属性设置为false,表示注入的Bean存在不存在没关系,存在的话就注入,不存在也不报错
下面演示六种注入方法
UserController类
package com.autowired.controller;import com.autowired.service.UserService;
import org.springframework.stereotype.Controller;@Controller
public class UserController {//注入service//第一种方式 属性注入
// @Autowired //根据类型找到对应的对象,完成注入
// private UserService userService;// //第二种方式 set方法注入
//private UserService userService;
// @Autowired
// public void setUserService(UserService userService) {
// this.userService = userService;
// }//第三种方式 构造注入//private UserService userService;
// @Autowired
// public UserController(UserService userService) {
// this.userService = userService;
// }//第四种方式 形参上注入//private UserService userService;
// public UserController(@Autowired UserService userService) {
// this.userService = userService;
// }//第五种方式 只有一个有参构造时,无注解private UserService userService;public UserController(UserService userService) {this.userService = userService;}//第六种方式 @Autowired和@qualifier两个注解联合public void add(){System.out.println("Controller...");userService.add();}}
UserDao接口
package com.autowired.dao;public interface UserDao {public void add();
}
UserDaoImpl类
package com.autowired.dao;import org.springframework.stereotype.Repository;@Repository
public class UserDaoImpl implements UserDao {@Overridepublic void add() {System.out.println("Dao....");}
}
UserRedisDaoImpl类(仅第六种方式需要)
package com.autowired.dao;import org.springframework.stereotype.Repository;@Repository
public class UserRedisDaoImpl implements UserDao{@Overridepublic void add() {System.out.println("Dao Redis....");}
}
UserService类
package com.autowired.service;public interface UserService {public void add();
}
UserServiceImpl类
package com.autowired.service;import com.autowired.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl implements UserService{//第一种方式 属性注入
// @Autowired //根据类型找到对应的对象,完成注入
// private UserDao userDao;//第二种方式 set方法注入
//private UserDao userDao;
// @Autowired
// public void setUserDao(UserDao userDao) {
// this.userDao = userDao;
// }//第三种方式 构造注入// private UserDao userDao;
// @Autowired
// public UserServiceImpl(UserDao userDao) {
// this.userDao = userDao;
// }//第四中方式 形参上注入
// private UserDao userDao;
// public UserServiceImpl(@Autowired UserDao userDao) {
// this.userDao = userDao;
// }// //第五种方式 只有一个有参构造时,无注解
// private UserDao userDao;
//
// public UserServiceImpl(UserDao userDao) {
// this.userDao = userDao;
// }@Autowired@Qualifier(value = "userRedisDaoImpl")private UserDao userDao;@Overridepublic void add() {System.out.println("Service....");userDao.add();}
}
测试类
import com.autowired.controller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class TestUserController {public static void main(String[] args){ApplicationContext context =new ClassPathXmlApplicationContext("bean.xml");UserController usercontroller = context.getBean(UserController.class);usercontroller.add();}
}
实验二:@Resource注入
@Resource注解也可以完成属性注入,它和@Autowired注解的区别
- @Resource注解时JDK扩展包种的,也就是属于JDK的一部分,所以该注解是标准注解,更加具有通用性
- @Autowired注解是Spring框架自己的
- @Resouece注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配
- @Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@qualifier注解一起用
- @Resource用在属性上、setter方法上
- @Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上
@Resource注解属于JDK扩展包,所以不在JDK种,需要额外引入以下依赖:【如果是JDK8的话不需要引入依赖,高于JDK11或者低于JDK8需要引入以下依赖】
<dependency><groupID>jakarta.annotation</groupID><artifactID>jakarta.annotation-api</artifactID><version>2.1.1</version>
</dependency>
下面演示注入方法
UserController类
package com.resource.controller;import com.resource.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Controller;@Controller("myUserController") //如果不指定会创建两次,因为我其他包下也有UserCOntroller类
public class UserController {//根据bean名称注入
// @Resource(name = "myUserService")
// private UserService userService;//根据类型注入@Resource //即没有指定name,属性名也与bean名称不同,默认调用类型注入private UserService userService;public void add(){System.out.println("Controller...");userService.add();}}
UserDao接口
package com.resource.dao;public interface UserDao {public void add();
}
UserDaoImpl类
package com.resource.dao;import org.springframework.stereotype.Repository;@Repository("myUserDao") //与UserServiceImpl种的属性名保持一致
public class UserDaoImpl implements UserDao {@Overridepublic void add() {System.out.println("Dao....");}
}
UserRedisDaoImpl类
package com.resource.dao;import org.springframework.stereotype.Repository;@Repository("myUserRedisDao")
public class UserRedisDaoImpl implements UserDao {@Overridepublic void add() {System.out.println("Dao Redis....");}
}
UserService接口
package com.resource.service;public interface UserService {public void add();
}
UserServiceImpl类
package com.resource.service;import com.resource.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;@Service("myUserService") //value省略
public class UserServiceImpl implements UserService {//不指定name 直接使用属性名作为name@Resourceprivate UserDao myUserDao; //让属性名字和这个bean那个保持一致@Overridepublic void add() {System.out.println("Service....");myUserDao.add();}
}
测试类不变(省略)
Spring全注解开发
使用配置类代替配置文件xml
package com.config;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;@Configuration //配置类
@ComponentScan("com") //开启组件扫描
public class SpringConfig {}
测试类(因为要读取配置类,所以发生变化)
import com.autowired.controller.UserController;
import com.config.SpringConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class Testconfig {public static void main(String[] args) {ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);UserController usercontroller = context.getBean(UserController.class);usercontroller.add();}
}
基于此,我们彻底摆脱了xml文件的控制,进入注解时代
手写IoC
我们都知道Spring框架是基于java反射机制实现的
Java反射
java反射机制是在运行中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任一对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。简单来说,反射机制指的是程序在运行时能偶获取自身的信息
要想解剖一个类,必须先要获取该类的Class对象,而解剖一个类或用反射解决具体问题就是使用相关API(1)java.lang.Class(2)java.lang.reflect 所以,Class对象是反射的根源
Car类
package com.reflect;public class Car {private String name;private int age;private String color;//无参构造public Car(){}//有参构造public Car(String name, int age, String color) {this.name = name;this.age = age;this.color = color;}//普通方法private void run(){System.out.println("私有方法...run");}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;}public String getColor() {return color;}public void setColor(String color) {this.color = color;}@Overridepublic String toString() {return "Car{" +"name='" + name + '\'' +", age=" + age +", color='" + color + '\'' +'}';}
}
TestCar类
import com.reflect.Car;
import org.junit.jupiter.api.Test;import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;public class TestCar {//获取Class对象多种方式@Testpublic void test01() throws Exception {// 1 类名.classClass<?> clazz1 = Car.class;// 2 对象.getClassClass<?> clazz2 = new Car().getClass();// 3 Class.forName(“全路径”)Class<?> clazz3 = Class.forName("com.reflect.Car");//实例化Car car = (Car)clazz3.getDeclaredConstructor().newInstance();System.out.println(car);}//获取构造方法@Testpublic void test02() throws Exception{Class<?> clazz = Car.class;//获取所有构造//gerConstructors()获取所有的public的构造方法//getDeclaredConstructors()获取所有的构造方法Constructor<?>[] constructors = clazz.getConstructors(); //只能读取public方法for(Constructor<?> c:constructors){System.out.println(c.getName() + "参数:" + c.getParameterCount());}//指定有参数构造创建对象//1 构造publicConstructor<?> c1 = clazz.getConstructor(String.class, int.class, String.class);Car car1 = (Car)c1.newInstance("夏利", 10, "红色");System.out.println(car1);//2 构造privateConstructor<?> c2 = clazz.getDeclaredConstructor(String.class, int.class, String.class);c2.setAccessible(true); //表示你可以访问私有构造Car car2 = (Car)c2.newInstance("张三",100,"黑色");System.out.println(car2);}//获取属性@Testpublic void test03() throws Exception{Class<?> clazz = Car.class;Car o = (Car)clazz.getDeclaredConstructor().newInstance();//获取所有public属性//Field[] fields = clazz.getFields();//获取所有属性(包括private属性)Field[] Fields = clazz.getDeclaredFields();for(Field e:Fields){if(e.getName().equals("name")){e.setAccessible(true);e.set(o,"概率论");}System.out.println(e.getName());System.out.println(o);}}//获取方法@Testpublic void test04() throws Exception {Car car = new Car("泵吃",18,"黑色");Class<?> clazz = car.getClass();//public方法Method[] methods = clazz.getMethods();for(Method s : methods){//System.out.println(s.getName());//执行方法(以toString为例)if(s.getName().equals("toString")){String invoke = (String)s.invoke(car); //传对象和对应的参数System.out.println("toString方法执行:" + invoke);}}//private方法Method[] methodsAll = clazz.getDeclaredMethods();for(Method m : methodsAll){//System.out.println(s.getName());//执行方法(以toString为例)if(m.getName().equals("run")){m.setAccessible(true); //私有不可直接调用,必须先设置该参数m.invoke(car); //传对象和对应的参数System.out.println("run方法执行:" );}}}
}
实现Spring的IoC
我们知道,IoC(控制反转)和DI(依赖注入)是Spring里面核心的东西,那么,接下啦我们将实现手写出这样的代码
实现过程:
- 创建子模块
- 创建测试类service dao
- 创建两个注解**@Bean** (创建对象) @Di(属性注入)
- 创建bean容器接口ApplicationContext定义方法,返回对象
- 实现bean容器接口(1) 返回对象 (2)根据包规则加载bean
第5步举例:扫描com.lele这个包和它的子包里面的所有类,看类上面是否@Bean注解,如果有把这个类通过反射实例化
第一步 搭建子模块 and 第二步创建测试类
第三步 创建两个注解
Bean接口
package com.lele.anno;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.TYPE) //表示在类和接口上用
@Retention(RetentionPolicy.RUNTIME) //表示在运行时生效
public @interface Bean {
}
Di接口
package com.lele.anno;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.FIELD) //表示在属性上用
@Retention(RetentionPolicy.RUNTIME)
public @interface Di {
}
第四步 创建bean容器接口ApplicationContext定义方法,返回对象
package com.lele.bean;public interface ApplicationContext {public Object getBean(Class<?> clazz);
}
第五步 实现bean容器接口,返回对象并根据包规则加载bean
UserDao接口
package com.lele.dao;public interface UserDao {void add();
}
UserDaoImpl类
package com.lele.dao.impl;import com.lele.anno.Bean;
import com.lele.dao.UserDao;@Bean
public class UserDaoImpl implements UserDao{@Overridepublic void add() {System.out.println("Dao....");}
}
UserService接口
package com.lele.service;public interface UserService {void add();
}
UserServiceImpl类
package com.lele.dao.impl;import com.lele.anno.Bean;
import com.lele.dao.UserDao;@Bean
public class UserDaoImpl implements UserDao{@Overridepublic void add() {System.out.println("Dao....");}
}
ApplicationContext接口(里面包含getBean方法)
package com.lele.bean;public interface ApplicationContext {public Object getBean(Class<?> clazz);
}
AnnotationApplicationContext类(主要步骤实现对象IoC实例化)
package com.lele.bean;import com.lele.anno.Bean;import java.io.File;
import java.lang.reflect.InvocationTargetException;
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{//创建Map集合,放bean对象private static Map<Class<?>,Object> beanFactory = new HashMap<>();private static String rootPath;//创建有参数构造 传递包路径 设置包的扫描规则//当前包及其子包,拿个类有@Bean注解,把哪个类通过对象实例化public AnnotationApplicationContext(String basePackage) throws Exception {//com.lele//1. 把.替换成\String packagePath = basePackage.replaceAll("\\.", "\\\\");//获取包绝对路径final Enumeration<URL> urls =Thread.currentThread().getContextClassLoader().getResources(packagePath);while (urls.hasMoreElements()) {//提取文件路径并进行解码URL url = urls.nextElement();String filePath = URLDecoder.decode(url.getFile(),"utf-8");
// System.out.println(filePath);//字符串截取,获取包前面那部分rootPath = filePath.substring(0,filePath.length()-basePackage.length());//包扫描loadBean(new File(filePath));}}//包扫描过程,实例化Beanprivate static void loadBean(File file) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {// 1.判断是否是文件夹if(file.isDirectory()){// 2.获取文件夹里面的所有内容File[] childrenFiles = file.listFiles();// 3.文件夹为空,直接返回if(childrenFiles == null || childrenFiles.length == 0){return;}// 4.如果文件夹不为空,遍历所有内容for(File child : childrenFiles){// 4.1 遍历得到的每个File对象,继续判断,如果还是文件夹,递归if(child.isDirectory()){loadBean(child); //递归}else{// 4.2 遍历得到的File对象不是文件夹,是文件// 4.3 得到包路径+类名称部分String pathWithClass =child.getAbsolutePath().substring(rootPath.length() - 1);// 4.4 判断当前文件类型是否是.classif(pathWithClass.contains(".class")){// 4.5 如果是.class类型,把路径\替换成. , 把.class去掉// com.lele.service.UserServiceImplString allName = pathWithClass.replaceAll("\\\\", ".").replace(".class","");// 4.6 判断类上面是否有注解 @Bean,如果有就实例化// 4.6.1 获取类的ClassClass<?> clazz = Class.forName(allName);// 4.6.2 判断不是接口if(!clazz.isInterface()){// 4.6.3判断是否有注解Bean annotation = clazz.getAnnotation(Bean.class);if(annotation != null){Object instance = clazz.getConstructor().newInstance();// 4.7 把实例化对象放到map集合里面的beanFactory// 4.7.1 判断当前类如果有接口,让接口class作为map的keyif(clazz.getInterfaces().length>0){beanFactory.put(clazz.getInterfaces()[0],instance);}else{beanFactory.put(clazz,instance);}}}}}}}}// 返回对象@Overridepublic Object getBean(Class<?> clazz){return beanFactory.get(clazz);}// public static void main(String[] args) { //debug调试
// try{
// AnnotationApplicationContext annotationApplicationContext = new AnnotationApplicationContext("com.lele");
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
// }}
TestUser类(测试类)
import com.lele.bean.AnnotationApplicationContext;
import com.lele.bean.ApplicationContext;
import com.lele.service.UserService;public class TestUser {public static void main(String[] args) throws Exception {ApplicationContext context =new AnnotationApplicationContext("com.lele");UserService userService =(UserService)context.getBean(UserService.class);System.out.println(userService);userService.add();}
}
结果:
com.lele.service.impl.UserServiceImpl@1b4fb997
Service…进程已结束,退出代码为 0
实现属性注入
在上面的基础上只需需要修改AnnotationApplicationContext类和UserServiceImpl类
//UserService类
...
//构造函数出有修改,然后新增了一个loadDi函数
public AnnotationApplicationContext(String basePackage) throws Exception {//com.lele//1. 把.替换成\String packagePath = basePackage.replaceAll("\\.", "\\\\");//获取包绝对路径final Enumeration<URL> urls =Thread.currentThread().getContextClassLoader().getResources(packagePath);while (urls.hasMoreElements()) {//提取文件路径并进行解码URL url = urls.nextElement();String filePath = URLDecoder.decode(url.getFile(),"utf-8");
// System.out.println(filePath);//字符串截取,获取包前面那部分rootPath = filePath.substring(0,filePath.length()-basePackage.length());//包扫描loadBean(new File(filePath)); //对象实例化}loadDi(); //属性注入
}
private void loadDi() {// 实例化对象都在beanFactory的map集合里面// 1 遍历beanFactory的map集合Set<Map.Entry<Class<?>, Object>> entries = beanFactory.entrySet();for(Map.Entry<Class<?>, Object> entry : entries){// 2 获取map集合每个对象(value),获取每个对象的属性Object obj = entry.getValue();// 获取对象的classClass<?> clazz = obj.getClass();// 获取对应的属性Field[] declaredFields = clazz.getDeclaredFields();// 3 遍历得到的每个对象的数组,得到每个属性for(Field field : declaredFields){// 4 判断属性上面是否有这个注解@DiDi annotation = field.getAnnotation(Di.class);// 5 如果有,把对象进行设置(注入)if(annotation != null){field.setAccessible(true);try {field.set(obj,beanFactory.get(field.getType()));} catch (IllegalAccessException e) {throw new RuntimeException(e);}}}}
}
package com.lele.service.impl;import com.lele.anno.Bean;
import com.lele.anno.Di;
import com.lele.dao.UserDao;
import com.lele.service.UserService;@Bean
public class UserServiceImpl implements UserService {@Diprivate UserDao userDao;@Overridepublic void add() {System.out.println("Service...");userDao.add();}
}
执行结果
Service…
Dao…进程已结束,退出代码为 0
面向切面编程–AOP
场景模拟
我们先实现一个简单的带有日志功能的计算器
Calculator接口
package com.lele.aop.example;public interface Calculator {int add(int i,int j);int sub(int i,int j);int mul(int i,int j);int div(int i,int j);
}
CalculatorLagImpl类
package com.lele.aop.example;public class CaculatoeLagImpl implements Calculator{@Overridepublic int add(int i, int j) {System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);int result = i + j;System.out.println("add后的值为...." + result);System.out.println("[日志] add 方法结束了,结果是是:" + result);return result;}@Overridepublic int sub(int i, int j) {System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j);int result = i - j;System.out.println("sub后的值为...." + result);System.out.println("[日志] sub 方法结束了,结果是是:" + result);return result;}@Overridepublic int mul(int i, int j) {System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j);int result = i * j;System.out.println("mul后的值为...." + result);System.out.println("[日志] mul 方法结束了,结果是是:" + result);return result;}@Overridepublic int div(int i, int j) {System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j);int result = i / j;System.out.println("div后的值为...." + result);System.out.println("[日志] div 方法结束了,结果是是:" + result);return result;}
}
- 针对带有日志功能的实现类,我们发现有如下缺陷
- 对核心业务功能有干扰,导致程序员在开发核心业务时精力分散
- 不利于统一维护
- 解决方案
- 解耦,也就是把附加功能从业务代码中抽取出来
- 困难
- 要抽取的代码在方法内部,靠以前把总类中的重复代码抽取到父类的方式无法解决,游程引入了新的技术–代理模式
代理模式
概念
- 23中设计模式的一种,属于结构型模式
- 作用:通过提供一个代码类,让我们调用目标方法时不再直接对目标方法进行调用,而是通过代理类间接调用,让不属于目标方法核心逻辑的代码从目标方法中剥离出来
- 调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起利于统一维护
生活中常见的例子:经纪人、房产中介、秘书
相关术语:
- 代理:将非核心逻辑剥离出来以后,封装这些非核心的类、对象、方法
- 目标:被代理“套用”了非核心代码的类、对象、方法
静态代理
我们重新建立了一个实现类
CalculatorStaticProxy类
package com.lele.aop.example;public class CaculatorStaticProxy implements Calculator{// 将代理目标对象传递过来,通过构造方法private Calculator caculator;public CaculatorStaticProxy(Calculator caculator) {this.caculator = caculator;}@Overridepublic int add(int i, int j) {//输出日志System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);//调用目标对象实现核心业务int addresult = caculator.add(i, j);System.out.println("[日志] add 方法结束了,结果是" + addresult);return 0;}@Overridepublic int sub(int i, int j) {//输出日志System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j);//调用目标对象实现核心业务int subresult = caculator.sub(i, j);System.out.println("[日志] sub 方法结束了,结果是" + subresult);}@Overridepublic int mul(int i, int j) {//输出日志System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j);//调用目标对象实现核心业务int mulresult = caculator.add(i, j);System.out.println("[日志] mul 方法结束了,结果是" + mulresult);}@Overridepublic int div(int i, int j) {//输出日志System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j);//调用目标对象实现核心业务int divresult = caculator.div(i, j);System.out.println("[日志] div 方法结束了,结果是" + divresult);}
}
- 上述代码通过调用目标对象实现了解耦,但是代码都写死了,不具备任何的灵活性,就拿日志功能来说,其他地方也需要附加日志,还需要更多静态代理类,产生大量重复代码,而且日志功能分数,没有统一管理
- 提取进一步的需求,将日志功能几种到一个代理类中,将来 有任何日志需求,都能通过一个代理类来实现,这就需要使用动态代理技术
动态代理
- 相比于静态代理需要每个功能(比如计算器和闹钟的日志管理)都要重写一个代理类,动态代理类相对于某一功能只需要一个代理类就可以实现间接调用,从而解耦
ProxyFactory类
package com.lele.aop.example;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(){/*** Proxy.newProxyInstance() 方法有三个参数* 第一个参数 ClassLoader: 加载动态生成代理类的类加载器* 第二个参数 Class[] interfaces: 目录对象实现的所有接口的class类型数组* 第三个参数 InvocationHandler: 设置代理对象实现目标对象方法的过程*/// 第一个参数 ClassLoader: 加载动态生成代理类的类加载器ClassLoader classloader = target.getClass().getClassLoader();// 第二个参数 Class[] interfaces: 目录对象实现的所有接口的class类型数组Class<?>[] interfaces = target.getClass().getInterfaces();// 第三个参数 InvocationHandler: 设置代理对象实现目标对象方法的过程InvocationHandler invocationHandler = new InvocationHandler() {// 第一个参数: 代理对象// 第二个参数: 需要重写目标对象的方法// 第三个参数: method方法里面的参数@Overridepublic Object invoke(Object proxy,Method method,Object[] args) throws Throwable {//方法调用之前System.out.println("[动态代理][日志]"+method.getName()+"参数:"+ Arrays.toString(args));//调用目标的方法(和上面invoke所属类不同)Object result = method.invoke(target,args);//方法调用之后System.out.println("[动态代理][日志]"+method.getName()+",方法执行完毕");return result;}};return Proxy.newProxyInstance(classloader,interfaces,invocationHandler);}
}
TestCal测试类
import com.lele.aop.example.Calculator;
import com.lele.aop.example.CalculatorImpl;
import com.lele.aop.example.ProxyFactory;public class TestCal {public static void main(String[] args) {//创建代理对象(动态)ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());Calculator proxy = (Calculator) proxyFactory.getProxy();proxy.add(1,2);}
}
[动态代理][日志]add参数:[1, 2] //结果
add后的值为…3
[动态代理][日志]add,方法执行完毕进程已结束,退出代码为 0
AOP概念及相关术语
概述
- AOP是一种设计思想,是软件设计领域中的面向切面编程,是面向对象编程的一种补充和完善
- 它以通过预编译方式和运行期动态代理方法实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术
- 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑之间的耦合度降低,提高程序的可重用性,同时提高开发效率
相关术语
-
横切关注点:那些与核心业务逻辑无关,但又需要在多个模块中重复使用的功能,通常跨越多个类或模块,如日志记录、事务管理、安全控制等
-
通知(增强):你想要增强的功能,比如安全、事务、日志
-
通过方法:每一个横切关注点上要做的事情都要写一个方法来实现,这样的方法称为通过方法
-
前置通知:在被代理的目标方法前执行
-
返回通知:在被代理的目标方法成功结束后执行
-
异常通知:在被代理的目标方法异常运行后执行
-
后置通知:在被代理的目标方法中最终结束后执行
-
环绕通知: 使用try…catch…finally结构围绕整个被代理的目标方法,包括上面的四种通知对应的所有位置
-
-
切面:封装通知方法的类
-
目标:被代理的目标对象
-
代理:向目标对象应用统治之后的代理对象
-
连接点:(逻辑概念),将方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,xy轴的交叉点就是连接点(通俗说就是Spring允许你使用通知的地方)
- 切入点:定位连接点的方式,每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)。如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句。
Spring 的 AOP 技术可以通过切入点定位到特定的连接点。通俗说,要实际去增强的方法;切入点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。
作用
- 简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注与自己的功能,提高内聚性
- 代码增强:把特定功能封装到切面类中,看哪里有需要就往上套,被套用切莫逻辑的方法就被切面给增强了
基于注解的AOP
技术说明
-
动态代理分为JDK动态代理和cglib动态代理
-
当目标类有接口时使用JDK动态代理和cglib动态代理,没有接口时只能使用cglib动态代理
- JDK动态代理生成的代理类会在com.sun.proxy包下,类名为$proxy1,和目标类实现相同的接口
- cglib动态代理动态生成的代理类会和目标在相同的包下,会基础目标类
-
动态代理:JDK原生的实现方式,需要被代理的目标类必须实现接口,因为这个技术需求代理对象和目标对象实现同样的接口(两个兄弟拜把子模式)
-
cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口
-
Aspectj:是AOP思想的一种实现。本质上是静态代理,将代理逻辑”织入“被代理的目标类编译得到的字节码,所以最终效果是动态的。weaver就是织入器。Spring只是借用了Aspectj中的注解
实操
- 在IOC所需依赖基础上再添加下面依赖
<!-- Spring-AOP依赖 --><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>6.1.11</version></dependency><!-- Spring-aspect --><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>6.1.13</version></dependency>
Calculator接口
package com.lele.aop.annoaop;public interface Calculator {int add(int i,int j);int sub(int i,int j);int mul(int i,int j);int div(int i,int j);
}
CalculatorImpl类
package com.lele.aop.annoaop;import org.springframework.stereotype.Component;@Component
public class CalculatorImpl implements Calculator {@Overridepublic int add(int i, int j) {int result = i + j;System.out.println("add后的值为...." + result);return result;}@Overridepublic int sub(int i, int j) {int result = i - j;System.out.println("sub后的值为...." + result);//为了测试模拟一个异常int a = 1/0;return result;}@Overridepublic int mul(int i, int j) {int result = i * j;System.out.println("mul后的值为...." + result);return result;}@Overridepublic int div(int i, int j) {int result = i / j;System.out.println("div后的值为...." + result);return result;}
}
LogAspect类
package com.lele.aop.annoaop;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;import java.util.Arrays;@Aspect //切面类
@Component //Ioc容器中进行管理
public class LogAspect {//设置切面点和通知类型//切入点表达式:execution(访问修饰付 方法返回类型 方法所在全路径.方法名(参数类型)))//通知类型// 前置@Before(value="切入点表达式配置切入点")@Before(value="execution(public int com.lele.aop.annoaop.CalculatorImpl.*(..))")public void beforeMethod(JoinPoint joinPoint){String name = joinPoint.getSignature().getName();Object[] args = joinPoint.getArgs();System.out.println("before-->前置通知,名称:" + name + ", 参数:" + Arrays.toString(args));}// 后置@After()@After(value="pointcut()")public void afterMethod(JoinPoint joinPoint){String name = joinPoint.getSignature().getName();System.out.println("after-->后置通知,名称:" + name);}// 返回@AfterReturning@AfterReturning(value="execution(public int com.lele.aop.annoaop.CalculatorImpl.*(..))",returning = "result")public void afterReturningMethod(JoinPoint joinPoint,Object result){String name = joinPoint.getSignature().getName();Object[] args = joinPoint.getArgs();System.out.println("afterReturning-->返回通知,名称:" + name + ",返回结果" + result);}// 异常@AfterThrowing 获取到目标方法的异常信息// 目标方法异常时执行@AfterThrowing(value="execution(public int com.lele.aop.annoaop.CalculatorImpl.*(..))",throwing = "ex")public void afterThrowingMethod(JoinPoint joinPoint,Throwable ex){String name = joinPoint.getSignature().getName();System.out.println("afterThrowing-->异常通知,名称:" + name + ",异常信息:" + ex);}// 环绕@Around()@Around("execution(public int com.lele.aop.annoaop.CalculatorImpl.*(..))")public Object aroundMethod(ProceedingJoinPoint joinPoint){String name = joinPoint.getSignature().getName();Object[] args = joinPoint.getArgs();String argString = Arrays.toString(args);Object result = null;try{System.out.println("环绕通知--目标方法之前执行");//调用目标方法result = joinPoint.proceed();System.out.println("环绕通知--目标方法之后执行");}catch(Throwable e) {e.printStackTrace();System.out.println("环绕通知--出现异常执行");}finally{System.out.println("环绕通知--目标方法执行完毕后执行");}return result;}//重用切入点表达式@Pointcut(value = "execution(public int com.lele.aop.annoaop.CalculatorImpl.*(..))")public void pointcut(){}
}
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"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!--开启组件扫描--><context:component-scan base-package="com.lele.aop.annoaop"></context:component-scan><!--开启aspectj自动代理,,为目标对象生成代理--><aop:aspectj-autoproxy></aop:aspectj-autoproxy></beans>
TestAop类
import com.lele.aop.annoaop.Calculator;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class TestAop {@Testpublic void testadd(){ApplicationContext context =new ClassPathXmlApplicationContext("bean.xml");Calculator calculator = context.getBean(Calculator.class);calculator.add(1,2);}
}
重用切入点表达式
- 声明
@Pointcut("execution(* com.atguigu.aop.annotation..*(..))")
public void pointcut(){}
- 在同一个切面中使用
@Before("pointCut()")
public void beforeMethod(JoinPoint joinPoint){String methodName = joinPoint.getSignature().getName();String args = Arrays.toString(joinPoint.getArgs());System.out.println("Logger-->前置通知, 方法名: "+methodName+", 参数: "+args);
}
- 在不同切面使用
@Before("com.atguigu.aop.CommonPointcut.pointCut()")
public void beforeMethod(JoinPoint joinPoint){String methodName = joinPoint.getSignature().getName();String args = Arrays.toString(joinPoint.getArgs());System.out.println("Logger-->前置通知, 方法名: "+methodName+", 参数: "+args);
}
切面的优先级
相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序
- 优先级高的切面:外面
- 优先级低的切面:里面
使用@Order注解可以控制切面的优先级
- @Order(较小的数):优先级高
- @Order(较高的数):优先级低
基于XML的AOP
Calculator接口和CalculatorImpl类已知
LogAspect类
package com.lele.aop.xmlaop;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;import java.util.Arrays;@Component //Ioc容器中进行管理
public class LogAspect {//前置通知public void beforeMethod(JoinPoint joinPoint){String name = joinPoint.getSignature().getName();Object[] args = joinPoint.getArgs();System.out.println("before-->前置通知,名称:" + name + ", 参数:" + Arrays.toString(args));}//后置通知public void afterMethod(JoinPoint joinPoint){String name = joinPoint.getSignature().getName();System.out.println("after-->后置通知,名称:" + name);}//返回通知public void afterReturningMethod(JoinPoint joinPoint,Object result){String name = joinPoint.getSignature().getName();Object[] args = joinPoint.getArgs();System.out.println("afterReturning-->返回通知,名称:" + name + ",返回结果" + result);}//异常通知public void afterThrowingMethod(JoinPoint joinPoint,Throwable ex){String name = joinPoint.getSignature().getName();System.out.println("afterThrowing-->异常通知,名称:" + name + ",异常信息:" + ex);}//环绕通知public Object aroundMethod(ProceedingJoinPoint joinPoint){String name = joinPoint.getSignature().getName();Object[] args = joinPoint.getArgs();String argString = Arrays.toString(args);Object result = null;try{System.out.println("环绕通知--目标方法之前执行");//调用目标方法result = joinPoint.proceed();System.out.println("环绕通知--目标方法之后执行");}catch(Throwable e) {e.printStackTrace();System.out.println("环绕通知--出现异常执行");}finally{System.out.println("环绕通知--目标方法执行完毕后执行");}return result;}//重用切入点表达式@Pointcut(value = "execution(public int com.lele.aop.xmlaop.CalculatorImpl.*(..))")public void pointcut(){}
}
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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启组件扫描--><context:component-scan base-package="com.lele.aop.xmlaop"></context:component-scan><!-- 配置aop五种通知类型--><aop:config><!--配置切面类--><aop:aspect ref="logAspect"><!--配置切入点--><aop:pointcut id="pointcut" expression="execution(* com.lele.aop.xmlaop.CalculatorImpl.*(..))"/><!--配置五种通知类型--><!--前置通知--><aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before><!--后置通知--><aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after><!--返回通知--><aop:after-returning method="afterReturningMethod" pointcut-ref="pointcut" returning="result"></aop:after-returning><!--异常通知--><aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointcut" throwing="ex"></aop:after-throwing><!--环绕通知--><aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around></aop:aspect></aop:config>
</beans>
Testaopaa类
import com.lele.aop.xmlaop.Calculator;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class TestAopaa {@Testpublic void testadd(){ApplicationContext context =new ClassPathXmlApplicationContext("beanaop.xml");Calculator calculator = context.getBean(Calculator.class);calculator.add(4,9);}
}
单元测试:JUnit
在之前的测试方法中,几乎都能看到以下的两行代码
ApplicationContext context =ClassPathXmlApplicationContext("XXX.xml");
xxxx xxx = context.getBean(XXX.class);
- 这两行代码的作用时创建Spring容器,最终获取到对象,但是每次测试都需要重复编写,针对上述问题,我们需要的是容器自动帮我们创建容器
- 我们都知道JUnit无法知晓我们是否使用了JUnit框架,更不用说帮我们创建容器了。但Spring中整合了一个运行器,可以读取配置文件(或注解)来创建容器,我们只需要告诉它配置文件位置即可。这样一来我们就可以通过Spring整合JUnit,可以是程度创建Spring容器了
整合Junit5
搭建子模块
引入相关依赖
<!--Spring整合Junit相关依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>6.0.11</version></dependency><dependency><groupId>jakarta.annotation</groupId><artifactId>jakarta.annotation-api</artifactId><version>2.1.1</version></dependency>
添加配置文件
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!--开启组件扫描--><context:component-scan base-package="com.lele.junit"></context:component-scan></beans>
添加Java类
User类
package com.lele.junit.junit5;import org.springframework.stereotype.Component;@Component
public class User {public void run(){System.out.println("user....");}}
测试
package com.lele.junit.junit5;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 SpringTestJunit5 {@Autowiredprivate User user;@Testpublic void testUser(){System.out.println(user);user.run();}}
整合Junit4
添加依赖
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version>
</dependency>
SpringTestJunit4类
package com.lele.junit.junit4;import com.lele.junit.junit5.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:bean.xml")
public class SpringTestJunit4 {@Autowiredprivate User user;@Testpublic void testUser4(){System.out.println(user);user.run();}
}
事务
jdbcTemplate
Spring框架堆JDBC进行封装,使用jdbcTemplate方便实现对数据库操作
创建数据库
CREATE DATABASE `t_emp`;
USE `t_emp`;
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;
Emp类
package com.lele.spring.jdbc;public class Emp {private Integer id;private String name;private Integer age;private String sex;@Overridepublic String toString() {return "Emp{" +"id=" + id +", name='" + name + '\'' +", age=" + age +", sex='" + sex + '\'' +'}';}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}
}
jdbcTemplate类
package com.lele.spring.jdbc;import org.junit.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;import java.util.List;@ExtendWith(SpringExtension.class)
@SpringJUnitConfig(locations = "classpath:beans.xml")
public class JdbcTemplateTest {@Autowiredprivate JdbcTemplate jdbcTemplate;//查询:返回对象@Testpublic void testSelectObject(){/*//方法一String sql = "select * from t_emp where id = ?";Emp empresult = jdbcTemplate.queryForObject(sql,(rs, rowNum) -> {Emp emp = new Emp();emp.setId(rs.getInt("id"));emp.setAge(rs.getInt("age"));emp.setName(rs.getString("name"));emp.setSex(rs.getString("sex"));return emp;});System.out.println(empresult);*///方法二 使用了RowMapper接口的实现类BeanPropertyPewMapper,但是底层代码相同String sql = "select * from t_emp where id = ?";Emp emp = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Emp.class),1);System.out.println(emp);}//查询:返回list集合@Testpublic void testSelectList(){String sql = "select * from t_emp";List<Emp> list = jdbcTemplate.query(sql,new BeanPropertyRowMapper<>(Emp.class));System.out.println(list);}//查询:返回单个属性@Testpublic void testSelectValue(){String sql = "select count(*) from t_emp "; //查询一共多少条记录Integer count = jdbcTemplate.queryForObject(sql,Integer.class);System.out.println(count);}//添加、修改、删除操作@Testpublic void testUpdata() {/*//1.添加操作//第一步 编写SQl语句String sql = "INSERT INTO `t_emp` values (null,?,?,?)";//第二步 调用jdbctemplate中的方法,传入相关参数Object[] params = {"张佳乐",19,"男"};int rows = jdbcTemplate.update(sql,params);System.out.println(rows);*//*//2.修改操作String sql = "update t_emp set name=? where id=?";int rows = jdbcTemplate.update(sql,"张三",3); //第三行System.out.println(rows);*///3.删除操作String sql = "delete from t_emp where id=?";int rows = jdbcTemplate.update(sql,3); //第三行System.out.println(rows);}
}
beans.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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!-- 引入外部属性文件,创建数据源对象--><context:property-placeholder location="classpath:jdbc.properties"/><bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="url" value="${jdbc.url}"/><property name="driverClassName" value="${jdbc.driver}"/><property name="username" value="${jdbc.user}"/><property name="password" value="${jdbc.password}"/></bean><!-- 创建jdbcTemplate对象,注入数据源对象--><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="druidDataSource"/></bean></beans>
jdbc.properties文件
jdbc.user =
jdbc.password =
jdbc.url = jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&&useSSL=false
jdbc.driver = com.mysql.cj.jdbc.Driver
声明式事务概念
介绍
- 事务
- 数据库事务(transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成
- 特性
- 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
- 一致性:事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。
- 隔离性:指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
- 持久性:指的是只要事务成功结束,它对数据库所做的更新就必须保存下来。即使发生系统崩溃,重新启动数据库引擎,数据库还能恢复到事务成功结束时的状态。
编程式事务
事务功能的相关操作全部通过自己编写代码来实现:
Connection conn = ...;try {// 开启事务:关闭事务的自动提交conn.setAutoCommit(false);// 核心操作// 提交事务conn.commit();
} catch(Exception e) {// 回滚事务conn.rollback();
} finally {// 释放数据库连接conn.close();
}
编程式的实现方式存在缺陷:
- 细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐
- 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码没有得到复用
声明式事务
既然事务控制的代码有规律可循,代码的结构基本式确定的,所以框架可以将固定模式的代码抽取出来,进行相关的封装 封装起来之后,我们只需要在配置文件中进行简单的配置即可完成操作
- 好处1:提高开发效率
- 好处2:消除了冗余的代码
- 好处3:框架会综合考虑相关领域中在时机开发环境下有可能遇到的各种问题,进行了健壮性、性能等各方面的优化
所以我们总结下面两个概念
- 编程式:自己写代码实现功能
- 声明式:通过配置让框架实现功能
基于注解的声明式事务
案例功能实现
创建数据库
CREATE DATABASE `t_book`;
USE `t_book`;
CREATE TABLE `t_book` (`book_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',`book_name` varchar(20) DEFAULT NULL COMMENT '图书名称',`price` int(11) DEFAULT NULL COMMENT '价格',`stock` int(10) unsigned DEFAULT NULL COMMENT '库存(无符号)',PRIMARY KEY (`book_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;insert into `t_book`(`book_id`, `book_name`, `price`, `stock`) values (1,'斗破苍穹',80,100),
(2,'斗罗大陆',50,100);CREATE DATABASE `t_user`;
USE `t_user`;
CREATE TABLE `t_user` (`user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',`username` varchar(20) DEFAULT NULL COMMENT '用户名',`balance` int(10) unsigned DEFAULT NULL COMMENT '余额(无符号)',PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;insert into `t_user`(`user_id`, `username`, `balance`) values (1,'admin',50);
BookController类
package com.lele.spring.tx.controller;import com.lele.spring.tx.services.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;@Controller
public class BookController {@Autowiredprivate BookService bookService;//买书的方法(图书Id,用户Id)public void buyBook(Integer bookId, Integer userId) {//调用Service方法bookService.buyBook(bookId,userId);}
}
BookDao接口
package com.lele.spring.tx.dao;public interface BookDao {int getBookPriceBooId(Integer bookId);void updatStock(Integer bookId);void updateUserBalance(Integer bookId,Integer userBalance);
}
BookDaoImpl类
package com.lele.spring.tx.dao;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;@Repository
public class BookDaoImpl implements BookDao {@Autowiredprivate JdbcTemplate jdbcTemplate;@Overridepublic int getBookPriceBooId(Integer bookId) {String sql = "select price from t_book where id = ?";Integer price = jdbcTemplate.queryForObject(sql, Integer.class, bookId);return price;}@Overridepublic void updatStock(Integer bookId) {String sql = "update t_book set stock = stock - 1 where id = ?";jdbcTemplate.update(sql, bookId);}@Overridepublic void updateUserBalance(Integer bookId, Integer userBalance) {String sql = "update t_book set balance = balance - ? where user_id = ?";jdbcTemplate.update(sql, userBalance, bookId);}
}
BookService接口
package com.lele.spring.tx.services;public interface BookService {void buyBook(Integer bookId, Integer userId);
}
BooKServiceImpl类
package com.lele.spring.tx.services;import com.lele.spring.tx.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class BookServiceImpl implements BookService {@Autowiredprivate BookDao bookDao;//买书的方法(图书Id,用户Id)@Overridepublic void buyBook(Integer bookId, Integer userId) {//根据图书id查询图书价格Integer price = bookDao.getBookPriceBooId(bookId);//更新图书库存量-1bookDao.updatStock(bookId);//更新用户余额 -图书价格bookDao.updateUserBalance(userId,price);}
}
TestBook类
package com.lele.spring.tx;import com.lele.spring.tx.controller.BookController;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;@SpringJUnitConfig(locations = "classpath:beans.xml")
public class TestBook {@Autowiredprivate BookController bookController;@Testpublic void mybook(){bookController.buyBook(1,1);}
}
beans.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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!--开启组件扫描--><context:component-scan base-package="com.lele.spring.tx"/><!-- 引入外部属性文件,创建数据源对象--><context:property-placeholder location="classpath:jdbc.properties"/><bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="url" value="${jdbc.url}"/><property name="driverClassName" value="${jdbc.driver}"/><property name="username" value="${jdbc.user}"/><property name="password" value="${jdbc.password}"/></bean><!-- 创建jdbcTemplate对象,注入数据源对象--><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="druidDataSource"/></bean></beans>
- 模拟场景
- 用户购买图书,先查询图书的价格,再更新图书的库存和用户的余额
假设用户id为1的用户,购买id为1的图书
用户余额为50,而图书价格为80
购买图书之后,用户的余额为-30,数据库中余额字段设置了无符号,因此无法将-30插入到余额字段,此时执行sql语句会抛出SQLException
- 用户购买图书,先查询图书的价格,再更新图书的库存和用户的余额
- 观察结果
- 因为没有添加事务,图书的库存更新了,但是用户的余额没有更新
显然这样的结果是错误的,购买图书是一个完整的功能,更新库存和更新余额要么都成功要么都失败
- 因为没有添加事务,图书的库存更新了,但是用户的余额没有更新
案例添加事务
-
添加事务配置(添加事务管理,引入tx空间)
<?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:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsd"><!--开启组件扫描--><context:component-scan base-package="com.lele.spring.tx"/><!-- 引入外部属性文件,创建数据源对象--><context:property-placeholder location="classpath:jdbc.properties"/><bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="url" value="${jdbc.url}"/><property name="driverClassName" value="${jdbc.driver}"/><property name="username" value="${jdbc.user}"/><property name="password" value="${jdbc.password}"/></bean><!-- 创建jdbcTemplate对象,注入数据源对象--><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="druidDataSource"/></bean><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="druidDataSource"></property></bean><!--开启事务的注解驱动通过注解@Transactional所标识的方法活表示的类中索云的方法,都会被事务管理器所管理--><!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好是这个默认值,可以省略这个属性--><tx:annotation-driven transaction-manager="transactionManager"/> </beans>
-
添加事务注释
- 因为service层表示业务逻辑层,一个方法表示完成一个功能,因此处理事务一一般在service层处理
- 在BookServiceImpl中的buyBook()添加注解@Transactional
- 该注解标识在方法上,只会影响该方法;如果标识在类上,则会影响类中所有的方法
BookServiceImpl类
package com.lele.spring.tx.services;import com.lele.spring.tx.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
public class BookServiceImpl implements BookService {@Autowiredprivate BookDao bookDao;//买书的方法(图书Id,用户Id)@Transactional@Overridepublic void buyBook(Integer bookId, Integer userId) {//根据图书id查询图书价格Integer price = bookDao.getBookPriceBooId(bookId);//更新图书库存量-1bookDao.updatStock(bookId);//更新用户余额 -图书价格bookDao.updateUserBalance(userId,price);}
}
事务属性
属性1:只读
设置只读,只能查询,不能修改添加删除
- 介绍:对一个查询操作来说,如果我们把它设置为只读,就能明确告诉数据库,这个操作不涉及写操作。这样数据库就能针对查询操作来进行优化。
- 使用方式:
@Transactional(readOnly = true)
@Service
public class BookServiceImpl implements BookService {@Autowiredprivate BookDao bookDao;//...
}
属性2:超时
在设置超时时候之内没有完成,抛出异常回滚
- 事务在执行过程中可能会因为各种问题导致程序卡住,而长时间占用数据库资源
- 超时回滚,释放资源
//超时时间单位秒 -1表示永不超时
@Transactional(timeout = 3)
属性3:回滚策略
设置哪些异常不回滚
- 声明式事务默认只针对运行时异常回滚,编译时异常不回滚
- 可以通过@Transactional中相关属性设置回滚策略
- rollbackFor属性:需要设置一个Class类型的对象
- rollbackForClassName属性:需要设置一个字符类型的全类名
- noRollbackFor属性:需要设置一个Class类型的对象
- rollbackFor属性:需要设置一个字符串类型的全类名
@Transactional(noRollbackFor = ArithmeticException.class)
//@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
public void buyBook(Integer bookId, Integer userId) {// 查询图书的价格Integer price = bookDao.getPriceByBookId(bookId);// 更新图书的库存bookDao.updateStock(bookId);// 更新用户的余额bookDao.updateBalance(userId, price);System.out.println(1/0);
}
- 总的来说就是运行有问题但是当出现对象异常时不回滚,其他代码正常执行
属性4:隔离级别
读问题
-
数据库系统必须具体隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题,一个事务与其他事务隔离的程度称为隔离级别
-
隔离级别:
- 读未提交:READ_UNCOMMITED 运行事务1读取事务2未提交的修改
- 读已提交:READ_COMMITTED 要求事务1读取事务2已提交的修改
- 可重复读:REPEATABLE READ 事务1执行期间进制其他事务更新这个数据
- 串行化 : SERIALIZABLE 事务1执行期间禁止其他事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下
-
MYSQL默认可重复读级别
//使用方式
@Transactional(isolation = Isolation.DEFAULT)
@Transactional(isolation = Isolation.READ_COMMITTED)
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Transactional(isolation = Isolation.SERIALIZABLE)
属性5:传播行为
事务方法之间调用,事务如何使用
- 介绍:在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。
- 一共有七种传播行为:
- REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
- SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行【有就加入,没有就不管了】
- MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常【有就加入,没有就抛异常】
- REQUIRES_NEW:开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】
- NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务【不支持事务,存在就挂起】
- NEVER:以非事务方式运行,如果有事务存在,抛出异常【不支持事务,存在就抛异常】
- NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】
//使用方式如
@Transactional(propagation = Propagation.MANDATORY)
使用全注解配置事务
SpringConfig类
package com.lele.spring.tx.config;import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.sql.DataSource;@Configuration //表示这是一个配置类
@ComponentScan("com.lele.spring.tx") //开启组件扫描
@EnableTransactionManagement //开启事务管理
public class SpringConfig {@Beanpublic DataSource getDataSource() {DruidDataSource dataSource = new DruidDataSource() ;dataSource.setDriverClassName("com.mysql.jdbc.Driver");dataSource.setUrl("jdbc:mysql://localhost:3306");dataSource.setUsername("zhangjiale");dataSource.setPassword("159357258zx");return dataSource;}@Bean(name = "jdbcTemplate")public JdbcTemplate getJdbcTemplate(DataSource dataSource) {JdbcTemplate jdbcTemplate = new JdbcTemplate();jdbcTemplate.setDataSource(dataSource);return jdbcTemplate;}@Beanpublic DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource);return dataSourceTransactionManager;}
}
TestAnno类
package com.lele.spring.tx;import com.lele.spring.tx.config.SpringConfig;
import com.lele.spring.tx.controller.BookController;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class TestAnno {@Testpublic void testAnnotation() {AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(SpringConfig.class);BookController bookController = context.getBean(BookController.class);Integer bookIds = 1;bookController.buyBook(bookIds,1);}
}
- 使用配置类时就不需要xml文件了,不要同时存在,不然自动注入多个无法进行
基于XML文件的声明式事务
beans-xml.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:tx="http://www.springframework.org/schema/tx"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!-- 开启组件扫描--><context:component-scan base-package="com.lele.spring.xmltx"/><!-- 引入外部属性文件,创建数据源对象--><context:property-placeholder location="classpath:jdbc.properties"/><bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="url" value="${jdbc.url}"/><property name="driverClassName" value="${jdbc.driver}"/><property name="username" value="${jdbc.user}"/><property name="password" value="${jdbc.password}"/></bean><!-- 创建jdbcTemplate对象,注入数据源对象--><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="druidDataSource"/></bean><!-- 事务管理器--><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="druidDataSource"/></bean>
<!-- 配置事务通知--><tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes><tx:method name="buyBook" read-only="true"/><tx:method name="update*" isolation="READ_COMMITTED"/><!--...--></tx:attributes></tx:advice>
<!-- 配置切入点和通知使用的方法--><aop:config><aop:pointcut id="pt" expression="execution(* com.lele.spring.xmltx.services.*.*(..))"/><aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/></aop:config>
</beans>
资源操作:Resource
- java标准java…net.URL类和各种URL前缀的标准处理程序无法满足所有对low-level资源的访问,比如:没有标准化的URL试下可用于访问需要从类路径或相对于ServiceContext获取的资源,并且缺少某些Spring所需要的功能
- 而Spring的Resource声明了访问low-level资源的能力‘
Resource接口
Spring的Resource接口位于org.springframewoek.core.io中,旨在成为一个更强大的接口,用于抽象对低级资源的访问
public interface Resource extends InputStreamSource {boolean exists();boolean isReadable();boolean isOpen();boolean isFile();URL getURL() throws IOException;URI getURI() throws IOException;File getFile() throws IOException;ReadableByteChannel readableChannel() throws IOException;long contentLength() throws IOException;long lastModified() throws IOException;Resource createRelative(String relativePath) throws IOException;String getFilename();String getDescription();
}
方法描述
getInputStream()
: 找到并打开资源,返回一个InputStream
以从资源中读取。预计每次调用都会返回一个新的InputStream
,调用者有责任关闭每个流。exists()
: 返回一个布尔值,表明某个资源是否以物理形式存在。isOpen()
: 返回一个布尔值,指示此资源是否具有开放流的句柄。如果为 true,InputStream
就不能够多次读取,只能够读取一次并且及时关闭以避免内存泄漏。对于所有常规资源实现,返回 false,但是InputStreamResource
除外。getDescription()
: 返回资源的描述,用来输出错误的日志。这通常是完全限定的文件名或资源的实际 URLisReadable()
: 表示资源的目录是否通过getInputStream
进行读取isFile()
: 表明这个资源是否代表了一个文件系统的文件。getURL()
: 返回一个 URL 句柄,如果资源不能够被解析为 URL,将抛出IOException
。getURI()
: 返回一个资源的 URI 句柄。getFile()
: 返回某个文件,如果资源不能够被解析称为绝对路径,将会抛出FileNotFoundException
。lastModified()
: 返回资源最后一次修改的时间戳。createRelative(String relativePath)
: 创建此资源的相关资源。getFilename()
: 返回资源的文件名是什么,例如:最后一部分的文件名myfile.txt
。
Resource的实现类
- Resource 接口是 Spring 资源访问策略的抽象,它本身并不提供任何资源访问实现,具体的资源访问由该接口的实现类完成——每个实现类代表一种资源访问策略。Resource一般包括这些实现类:UrlResource、ClassPathResource、FileSystemResource、ServletContextResource、InputStreamResource、ByteArrayResource
UrlResource访问网络资源
- Resource的一个实现类,用来访问网络资源,它支持URL的绝对路径。
http:-----该前缀用于访问基于HTTP协议的网络资源。
ftp:-----该前缀用于访问基于FTP协议的网络资源
file:----该前缀用于从文件系统中读取资源
package com.lele.spring.resource;import org.springframework.core.io.ClassPathResource;import java.io.IOException;
import java.io.InputStream;public class ClassPathResourceDemo {public static void loadClassPathResource(String path){//创建Resource实现类对象 ClassPathReource类ClassPathResource resource = new ClassPathResource(path);//获取资源信息System.out.println(resource);System.out.println(resource.getFilename());System.out.println(resource.getPath());System.out.println(resource.getDescription());try {//获取文件内容InputStream in = resource.getInputStream();byte[] bytes = new byte[1024];while(in.read(bytes) != -1){System.out.println(new String(bytes));}} catch (IOException e) {throw new RuntimeException(e);}}public static void main(String[] args) {loadClassPathResource("lele.txt");}
}
ClassPathResource访问类路径下资源
ClassPathResource访问类路径下资源,但对于其他的Resource实现类,其主要优势是方便房屋内类加载路径的资源,尤其是对于Web应用,ClassPathResource可自动搜索位于classes下的资源文件,无需使用绝对路径访问
package com.lele.spring.resource;import org.springframework.core.io.UrlResource;//演示UrlResource访问网络资源
public class UrlResourceDemo {public static void main(String[] args) {//http前缀loadUrlResource("http://www.baidu.com");//file前缀(根路径下)loadUrlResource("file:lele.txt");}//访问前缀http、filepublic static void loadUrlResource(String path){try {//创建Resource接口的实现类UrlResourceUrlResource 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);}}
}
FileSystemResource访问文件资源系统
Spring提供的FileSystemResource类用于访问文件系统资源,使用并没有太大优势,因为java提供的File类也可用于访问文件系统资源
package com.lele.spring.resource;import org.springframework.core.io.FileSystemResource;import java.io.IOException;
import java.io.InputStream;//访问文件系统资源
public class FileSystemResourceDemo {public static void main(String[] args) {loadFileSystemResource("d:\\aaa.txt");}public static void loadFileSystemResource(String path) {//获取对象FileSystemResource resource = new FileSystemResource(path);//获取资源信息System.out.println(resource.getFilename());System.out.println(resource.getDescription());try {InputStream in = resource.getInputStream();byte[] b = new byte[1024];while(in.read(b) != -1){System.out.println(new String(b));}} catch (IOException e) {throw new RuntimeException(e);}}
}
其他
-
ServletContextResource
- 它是一个用于表示存储在 ServletContext 中的资源的类。它能够通过 ServletContext 来访问 Web 应用程序中的资源,例如可以获取存储在 Web 应用程序根目录下的文件等资源,常用于基于 Servlet 的 Web 应用程序中,方便对应用内的资源进行操作。
-
InputStreamResource
- 这个类是对 Java 中的 InputStream 进行封装的资源类。它把一个 InputStream 对象包装成一个资源对象,使得可以像操作其他资源一样操作 InputStream 中的数据,方便在需要资源操作的场景中使用 InputStream 来提供数据源。
-
ByteArrayResource
- 它是一个基于字节数组的资源类。它将一个字节数组作为资源内容,可以方便地将字节数组中的数据作为资源进行读取等操作。这在处理内存中的数据或者将字节数组数据作为资源传递时非常有用,例如在处理一些临时的二进制数据或者从其他地方获取到字节数组形式的数据需要作为资源使用时。
其他Resource接口
Spring还提供如下两个接口
ResourceLoader
:该科偶的实现类的实例可以获得一个Resource实例ResourceLooaderAware
:该接口的实现类的实例可以获得一个ResourceLoader接口的引用
在ResourceLoader接口中有如下方法
- Resource getResource(String location) 该接口仅有该方法,用于返回一个Resource实例
ApplicationContext实现类都i实现ResourceLoader接口,因此ApplicaionContext都可直接获取Resource实例
ResourceLoader接口
package com.lele.spring.resourceloader;import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.core.io.Resource;public class ResourceLoaderDemo {@Testpublic void demo1(){ApplicationContext context = new ClassPathXmlApplicationContext();Resource resource = context.getResource("lele.txt");System.out.println(resource.getFilename());}@Testpublic void demo2(){ApplicationContext context = new FileSystemXmlApplicationContext();Resource resource = context.getResource("lele.txt");System.out.println(resource.getFilename());}
}
Spring将采用ApplicationContext相同的策略来访问资源,也就是说,如果ApplicationContext是FileSystemApplicationContext,res就是FileSystemResource实例;如果ApplicationContext是ClassPathXmlApplicationContext,res就是ClassPathResource实例
当Spring应用需要进行资源访问时,实际上并不需要直接使用Resource实现类,而是调用Resource实例的getResource方法来获得资源,ResourceLoader将会负责选择Resource实现类,也就是确定的具体的资源访问策略,从而将应用程序和具体的访问策略分离开来
另外,使用ApplicationContext访问资源时,可通过不同前缀指定强制使用指定的ClassPathResource,FileSystemResource等实现类
Resource res = ctx.getResource("calsspath:bean.xml);
Resource res = ctx.getResource("file:bean.xml);
Resource res = ctx.getResource("http://localhost:8080/bean.xml);
ResourceLoaderAware接口
ResourceLoaderAware
接口:- 实现该接口的类可以获取一个
ResourceLoader
的引用。 - 接口提供了
setResourceLoader()
方法,由 Spring 容器调用,传入ResourceLoader
对象。
- 实现该接口的类可以获取一个
- 资源加载:
- 如果一个 Bean 类实现了
ResourceLoaderAware
接口,Spring 容器会将自身作为ResourceLoader
传入setResourceLoader()
方法。 ApplicationContext
的实现类都实现了ResourceLoader
接口,因此 Spring 容器自身可以作为ResourceLoader
使用。
- 如果一个 Bean 类实现了
TestBean类
package com.lele.spring.resourceloaderaware;import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;public class TestBean implements ResourceLoaderAware {private ResourceLoader resourceLoader;@Overridepublic void setResourceLoader(ResourceLoader resourceLoader) {this.resourceLoader = resourceLoader;}public ResourceLoader getResourceLoader() {return this.resourceLoader;}
}
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="testBean" class="com.lele.spring.resourceloaderaware.TestBean"></bean>
</beans>
Testbean类
import com.lele.spring.resourceloaderaware.TestBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ResourceLoader;public class Testbean {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");TestBean testBean = context.getBean("testBean", TestBean.class);ResourceLoader resourceLoader = testBean.getResourceLoader();System.out.println(resourceLoader == context);}
}
Resource作为属性
- Spring 资源访问策略:
- 需要使用
Resource
实现类或ApplicationContext
来获取资源。 - Spring 提供了更好的解决方案:直接利用依赖注入。
- 需要使用
- Spring 框架的优势:
- 利用策略模式简化资源访问。
- 结合策略模式和 IoC(控制反转),简化 Spring 资源访问。
- Bean 实例访问资源的两种解决方案:
- 代码中获取
Resource
实例。 - 使用依赖注入。
- 代码中获取
- 代码中获取 Resource 实例:
- 需要提供
Resource
所在的位置。 - 通过
FileSystemResource
、ClassPathResource
或ApplicationContext
的getResource()
方法获取实例。 - 资源位置被耦合到代码中,位置改变需要修改程序。
- 需要提供
- 依赖注入:
- 建议采用的方法,让 Spring 为 Bean 实例依赖注入资源。
ResourceBean类
package com.lele.spring.di;import org.springframework.core.io.Resource;public class ResourceBean {private Resource resource;public Resource getResource() {return resource;}public void setResource(Resource resource) {this.resource = resource;}public void print() {System.out.println(resource.getFilename());System.out.println(resource.getDescription());}
}
beans.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="resourceBean" class="com.lele.spring.di.ResourceBean"><property name="resource" value="classpath:lele.txt"></property></bean></beans>
TestBeanDi类
import com.lele.spring.di.ResourceBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class TestBeanDi {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");ResourceBean resourceBean = context.getBean(ResourceBean.class);resourceBean.print();}
}
指定访问策略
- 不管以怎样的方式创建ApplicationContext实例,都需要为ApplicationContext指定配置文件,Spring允许使用一份或多分XML配置文件。当程序创建ApplicationContext实例时,通常也是以Resource的方式来访问配置文件的,所以ApplicationContext完全支持ClassPathResource、FileSystemResource、ServletContextResource等资源访问方式。
- ApplicationContext确定资源访问策略通常有两种方法:
(1)使用ApplicationContext实现类指定访问策略。
(2)使用前缀指定访问策略。
前缀访问策略
classpath通配符使用
classpath *:前缀提供了加载多个XML配置文件的能力,当使用时前缀来指定XML配置文件时,系统将搜索类加载路径,找到所有与文件名匹配的文件,分别加载文件中的配置定义,最后合并成一个ApplicationContext。
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:bean.xml");
System.out.println(ctx);
- 当使用classpath *:前缀时,Spring将会搜索类加载路径下所有满足该规则的配置文件。
如果不是采用classpath *:前缀,而是改为使用classpath:前缀,Spring则只加载第一个符合条件的XML文件 - 注意:classpath *:前缀仅对ApplicationContext有效。实际情况是,创建ApplicationContext时,分别访问多个配置文件(通过ClassLoader的getResource方法实现)。因此,classpath *:前缀不可用于Resource。
通配符其他使用
一次性加载多个配置文件的方式:指定配置文件时使用通配符
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:bean*.xml");
Spring允许将classpath*:前缀和通配符结合使用:
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:bean*.xml");
国际化i18n
java国际化
java自身是支持国际化的,java.util.Locale用于指定当前用户所属的语言环境等信息,java.util.ResourceBundle用于查找绑定对应的资源文件。Locale包含language信息和country信息,Locale创建默认Locale对象时使用的静态方法
- 配置文件命名规则
basename_language_country.properties
,所有配置文件一般放在classpth中,一般放在resource目录下
package com.lele.spring.i18n;import java.util.Locale;
import java.util.ResourceBundle;public class Resourcei18n {public static void main(String[] args) {// 使用 Locale.of 方法创建 Locale 实例Locale locale1 = Locale.of("zh", "CN");ResourceBundle rb1 = ResourceBundle.getBundle("messages", locale1);String value1 = rb1.getString("test");System.out.println(value1);Locale locale2 = Locale.of("en", "GB");ResourceBundle rb2 = ResourceBundle.getBundle("messages", locale2);String value2 = rb2.getString("test");System.out.println(value2);}
}
Spring国际化
spring中国际化是通过MessageSource这个接口来支持的
常见实现类
ResourceBundleMessageSource
- 这个是基于java的ResourceBundle基础类实现的,鱼需仅通过资源名加载国际化资源
ReloadableResourceBundleMessageSource
- 这个功能和第一个类的功能类似,多了定时刷新功能,运行不重启相同的情况下,更新资源的信息
StaticMessageSource
它允许通过编程的方式提供国际化信息,一会我们可以通过这个来实现db中存储国际化信息的功能
Resourcei18n类
package springi18n;import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;import java.util.Date;
import java.util.Locale;public class Resourcei18n {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");//使用数组形式传递动态参数Object[] objs = new Object[]{"lele",new Date().toString()};String str = context.getMessage("lele.com",objs, Locale.ENGLISH);System.out.println(str);}
}
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="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"><property name="basenames"><list><value>lele</value></list></property><property name="defaultEncoding" value="utf-8"/></bean>
</beans>
数据校验:Validation
-
使用场景:如用户注册时用户名不能为空,用户名长度不超过20,手机号合法等等
-
我们可以使用普通方式来实现,但是该校验代码就和业务代码混合,耦合度高,难以修改,因此spring里面允许通过注解等方式定义校验规则,把校验和业务逻辑分开
在spring中有多种校验的方法:
- 通过Validator接口实现
- Bean Validator注解实现
- 基于方法实现校验
- 实现自定义校验
通过Validator接口实现
- 引入依赖
<dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>9.0.0.Final</version></dependency><dependency><groupId>org.glassfish</groupId><artifactId>jakarta.el</artifactId><version>4.0.2</version><scope>compile</scope></dependency>
- 创建实体类,定义属性,创建对应set和get方法
package com.lele.spring.validator.one;public class Person {private String name;private int 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;}
}
- 创建类,实现接口,实现接口的方法,编写校验逻辑
package com.lele.spring.validator.one;import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;public class PersonValidator implements Validator {@Overridepublic boolean supports(Class<?> clazz) { //表示此校验用在哪个类型上return Person.class.equals(clazz);}@Overridepublic void validate(Object target, Errors errors) { //校验规则//name不能为空ValidationUtils.rejectIfEmpty(errors,"name","name.empty");//age>0并且小于200Person person = (Person) target;if(person.getAge() < 0){errors.rejectValue("age","age.value.invalid","age<0");}else if(person.getAge() > 200){errors.rejectValue("age","age.value.invalid","age>200");}}
}
- 最终测试
package com.lele.spring.validator.one;import org.springframework.validation.BindingResult;
import org.springframework.validation.DataBinder;public class TestPerson {public static void main(String[] args) {//创建person对象Person person = new Person();person.setAge(-20);//创建person对应databinderDataBinder binder = new DataBinder(person);//设置校验器binder.setValidator(new PersonValidator());//调用方法执行检测binder.validate();//输出校验结果BindingResult result = binder.getBindingResult();System.out.println(result.getAllErrors());}
}
Bean Validation注解实现
步骤:
- 创建配置类,配置LocalValidatorFactoryBean
- 创建实体类,定义属性,生成get和set方法,在属性上使用注解设置校验规则
- 创建校验器(原生的/spring框架下的)
- 最终测试
常用注解说明:
@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格式
配置类ValidationConfig
package com.lele.spring.validator.two;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;@Component
@ComponentScan("com.lele.spring.validator.two")
public class ValidationConfig {@Beanpublic LocalValidatorFactoryBean validator() {return new LocalValidatorFactoryBean();}
}
实体类User
package com.lele.spring.validator.two;import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;public class User {@NotNullprivate String name;@Min(0)@Max(150)private int 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;}
}
MyValidation1类
package com.lele.spring.validator.two;import jakarta.validation.ConstraintViolation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import jakarta.validation.Validator; //原生的import java.util.Set;@Service
public class MyValidation1 {@Autowiredprivate Validator validator;public boolean validatorByUser1(User user){Set<ConstraintViolation<User>> validate = validator.validate(user);return validate.isEmpty();}
}
MyValidation2类
package com.lele.spring.validator.two;import jakarta.validation.ConstraintViolation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import jakarta.validation.Validator; //原生的import java.util.Set;@Service
public class MyValidation1 {@Autowiredprivate Validator validator;public boolean validatorByUser1(User user){Set<ConstraintViolation<User>> validate = validator.validate(user);return validate.isEmpty();}
}
测试类
package com.lele.spring.validator.two;import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.validation.BindException;public class TestUser {@Testpublic void testValidationOne() {ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);MyValidation1 validation1 = context.getBean(MyValidation1.class);User user = new User();user.setName("test");user.setAge(-10);boolean message = validation1.validatorByUser1(user);System.out.println(message);}@Testpublic void testValidationTwo() throws BindException {ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);MyValidation2 validation2 = context.getBean(MyValidation2.class);User user = new User();user.setName("test");user.setAge(-10);boolean message = validation2.validateByUser2(user);System.out.println(message);}
}
基于方法实现校验
创建配置类ValidationConfig
package com.lele.spring.validator.three;import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;@Component
@ComponentScan("com.lele.spring.validator.three")
public class ValidationConfig {@Beanpublic MethodValidationPostProcessor validationPostProcessor() {return new MethodValidationPostProcessor();}
}
User类
package com.lele.spring.validator.three;import jakarta.validation.constraints.*;
import org.springframework.stereotype.Service;@Service
public class User {@NotNullprivate String name;@Min(0)@Max(100)private int age;@Pattern(regexp= "^1(3|4|5|7|8)\\d{9}$",message = "手机号码格式错误")@NotBlank(message="手机号码不能为空")private String phone;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;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}
}
MyService类
package com.lele.spring.validator.three;import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;@Service
@Validated
public class MyService {public String testMethod(@NotNull @Valid User user) {return user.toString();}}
TestUser类
package com.lele.spring.validator.three;import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class TestUser {@Testpublic void testUser(){ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);MyService service = context.getBean(MyService.class);User user = new User();user.setName("lucy");user.setAge(20);user.setPhone("13789");service.testMethod(user);}}
自定义校验
定义并创建一个注解,这个格式是固定的,所以直接照着一个的格式修改即可
CannotBlank注解
package com.lele.spring.validator.four;import jakarta.validation.Constraint;
import jakarta.validation.Payload;import java.lang.annotation.*;@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy={CannotBlankValidator.class})
public @interface CannotBlank {//默认提示错误信息String message() default "不能包含空格";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface List{CannotBlank[] value();}
}
CannotBlankValidator类
package com.lele.spring.validator.four;import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;public class CannotBlankValidator implements ConstraintValidator<CannotBlank,String> {@Overridepublic boolean isValid(String value, ConstraintValidatorContext context) {if(value != null && value.contains(" ")){//获取默认提示信息String defaultConstraintMessageTemplate =context.getDefaultConstraintMessageTemplate();System.out.println("default message:" + defaultConstraintMessageTemplate);//禁用默认提示信息context.disableDefaultConstraintViolation();//设置提示语context.buildConstraintViolationWithTemplate("cannot contains blank spaces").addConstraintViolation();return false;}return true;}
}
MyService类
package com.lele.spring.validator.four;import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;@Service
@Validated
public class MyService {public String testMethod(@NotNull @Valid User user) {return user.toString();}}
testUser类
package com.lele.spring.validator.four;import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class TestUser {@Testpublic void testUser(){ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);MyService service = context.getBean(MyService.class);User user = new User();user.setName("lucy");user.setAge(20);user.setPhone("134682957");user.setMessage("he llo zhan gj i ale ");System.out.println(service.testMethod(user));}
}
User类
package com.lele.spring.validator.four;import jakarta.validation.constraints.*;
import org.springframework.stereotype.Service;@Service
public class User {@NotNullprivate String name;@Min(0)@Max(100)private int age;@Pattern(regexp= "^1(3|4|5|7|8)\\d{9}$",message = "手机号码格式错误")@NotBlank(message="手机号码不能为空")private String phone;@CannotBlankprivate String message;public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}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;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}
}
ValidationConfig类
package com.lele.spring.validator.four;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;@Component
@ComponentScan("com.lele.spring.validator.four")
public class ValidationConfig {@Beanpublic MethodValidationPostProcessor validationPostProcessor() {return new MethodValidationPostProcessor();}
}
可
CannotBlank注解
package com.lele.spring.validator.four;import jakarta.validation.Constraint;
import jakarta.validation.Payload;import java.lang.annotation.*;@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy={CannotBlankValidator.class})
public @interface CannotBlank {//默认提示错误信息String message() default "不能包含空格";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface List{CannotBlank[] value();}
}
CannotBlankValidator类
package com.lele.spring.validator.four;import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;public class CannotBlankValidator implements ConstraintValidator<CannotBlank,String> {@Overridepublic boolean isValid(String value, ConstraintValidatorContext context) {if(value != null && value.contains(" ")){//获取默认提示信息String defaultConstraintMessageTemplate =context.getDefaultConstraintMessageTemplate();System.out.println("default message:" + defaultConstraintMessageTemplate);//禁用默认提示信息context.disableDefaultConstraintViolation();//设置提示语context.buildConstraintViolationWithTemplate("cannot contains blank spaces").addConstraintViolation();return false;}return true;}
}
MyService类
package com.lele.spring.validator.four;import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;@Service
@Validated
public class MyService {public String testMethod(@NotNull @Valid User user) {return user.toString();}}
testUser类
package com.lele.spring.validator.four;import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class TestUser {@Testpublic void testUser(){ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);MyService service = context.getBean(MyService.class);User user = new User();user.setName("lucy");user.setAge(20);user.setPhone("134682957");user.setMessage("he llo zhan gj i ale ");System.out.println(service.testMethod(user));}
}
User类
package com.lele.spring.validator.four;import jakarta.validation.constraints.*;
import org.springframework.stereotype.Service;@Service
public class User {@NotNullprivate String name;@Min(0)@Max(100)private int age;@Pattern(regexp= "^1(3|4|5|7|8)\\d{9}$",message = "手机号码格式错误")@NotBlank(message="手机号码不能为空")private String phone;@CannotBlankprivate String message;public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}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;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}
}
ValidationConfig类
package com.lele.spring.validator.four;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;@Component
@ComponentScan("com.lele.spring.validator.four")
public class ValidationConfig {@Beanpublic MethodValidationPostProcessor validationPostProcessor() {return new MethodValidationPostProcessor();}
}