1. 入门
1.创建spring6
工程,创建spring6-first
子模块
2.在父工程pom.xml中引入Sping的基础依赖,子工程也会继承该依赖
<dependencies>
<!--spring context依赖-->
<!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.3</version>
</dependency>
<!--junit5测试-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.3.1</version>
</dependency>
<!--log4j2的依赖-->
<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-slf4j2-impl</artifactId>
<version>2.19.0</version>
</dependency>
</dependencies>
3.创建类HelloWorld
,包名为package com.atguigu.spring6.bean;
4.在resources
目录创建一个 Spring
配置文件 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标签表示将 class指定包下的指定一个类 与id设定的名绑定-->
<bean id="helloWorld" class="com.atguigu.spring6.bean.HelloWorld"></bean>
</beans>
5.在其他类中使用
@Test
public void testHelloWorld(){
//生成 beans.xml 配置文件的对象
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
//getbean 方法找到指定id对应的类,返回该类
HelloWorld helloworld = (HelloWorld) ac.getBean("helloWorld");
//实例化成功,可以使用该类的方法
helloworld.sayHello();
}
2. Log4j2日志
在类的根路径下创建log4j2.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<loggers>
<!--
level指定日志级别,从低到高的优先级:
TRACE < DEBUG < INFO < WARN < ERROR < FATAL
trace:追踪,是最低的日志级别,相当于追踪程序的执行
debug:调试,一般在开发中,都将其设置为最低的日志级别
info:信息,输出重要的信息,使用较多
warn:警告,输出警告的信息
error:错误,输出错误信息
fatal:严重错误
-->
<root level="DEBUG">
<appender-ref ref="spring6log"/>
<appender-ref ref="RollingFile"/>
<appender-ref ref="log"/>
</root>
</loggers>
<appenders>
<!--输出日志信息到控制台-->
<console name="spring6log" target="SYSTEM_OUT">
<!--控制日志输出的格式-->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/>
</console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用
fileName指定存储地址-->
<File name="log" fileName="d:/spring6_log/test.log" append="false">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</File>
<!-- 这个会打印出所有的信息,
每次大小超过size,
则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,
作为存档-->
<RollingFile name="RollingFile" fileName="d:/spring6_log/app.log"
filePattern="log/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
<SizeBasedTriggeringPolicy size="50MB"/>
<!-- DefaultRolloverStrategy属性如不设置,
则默认为最多同一文件夹下7个文件,这里设置了20 -->
<DefaultRolloverStrategy max="20"/>
</RollingFile>
</appenders>
</configuration>
3. 容器:IoC
3.1 基于XML管理Bean
3.1.1 获取Bean
方法一:根据id获取
HelloWorld helloworld = (HelloWorld) ac.getBean("helloWorld");
方法二:根据类型获取
HelloWorld bean = ac.getBean(HelloWorld.class);
方法三:根据id和类型获取
HelloWorld bean = ac.getBean("helloworld", HelloWorld.class);
注意:可以使用接口类型获取Bean
3.1.2 依赖注入
指Spring创建对象的过程中,将对象的属性通过配置进行赋值
setter方法注入:
1.首先,指定类中必须有set
方法
2.然后在配置中声明,property
标签来给该类的属性赋值,使用set
方法
<bean id="studentOne" class="com.atguigu.spring6.bean.Student">
<!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->
<!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关) -->
<!-- value属性:指定属性值 -->
<property name="id" value="1001"></property>
<property name="name" value="张三"></property>
<property name="age" value="23"></property>
<property name="sex" value="男"></property>
</bean>
3.创建实例:会自动根据配置创建对象,并根据配置来给对象赋值
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-di.xml");
Student studentOne = ac.getBean("studentOne", Student.class);
System.out.println(studentOne);
构造器注入:
同理,原类中需有构造方法,在配置中使用constructor-arg
标签,该标签会调用构造方法来赋值
:constructor-arg标签有name属性来指定参数名
<bean id="studentTwo" class="com.atguigu.spring6.bean.Student">
<constructor-arg value="1002"></constructor-arg>
<constructor-arg value="李四"></constructor-arg>
<constructor-arg value="33"></constructor-arg>
<constructor-arg value="女"></constructor-arg>
</bean>
3.1.3 特殊值处理
null值:
<property name="name"><null /></property>
转义字符:输入< >
<property name="expression" value="a < b"/>
CDATA节:
<property name="expression">
<value><![CDATA[ 任意字符 ]]></value>
</property>
3.1.4 对象类型赋值
:给一个类中的一个对象属性赋值private Student stu
(如类中的这个属性为学生对象)
:使用bean
标签里的ref
属性
例子:Person类中有Student类的属性,注入:
方法一:引用外部bean
<!--
1.先给Student类配置
2.给Person类注入时,使用ref标签,里面填Student的配置的id
-->
<bean id="student" class="com.bean.Student"></bean>
<bean id="studentFour" class="com.bean.Person">
<property name="stu" ref="student"></property>
</bean>
方法二:内部bean:property内部写bean来引入
<bean id="studentFour" class="com.atguigu.spring6.bean.Student">
<property name="clazz">
<!-- 在一个bean中再声明一个bean就是内部bean -->
<!-- 内部bean只能用于给属性赋值,不能在外部通过IOC容器获取,因此可以省略id属性 -->
<bean id="clazzInner" class="com.atguigu.spring6.bean.Clazz">
</bean>
</property>
</bean>
方法三:级联赋值
:ref指定对象属性,用id.属性方式,注入
<bean id="studentFour" class="com.atguigu.spring6.bean.Student">
<property name="clazz" ref="clazzOne"></property>
<property name="clazz.clazzId" value="3333"></property>
<property name="clazz.clazzName" value="最强王者班"></property>
</bean>
3.1.5 数组类型赋值
private String[] hobbies;
array
标签
<property name="hobbies">
<array>
<value>抽烟</value>
<value>喝酒</value>
<value>烫头</value>
</array>
</property>
3.1.6 集合类型赋值
List:
private List<Student> students;
list
标签
<property name="students">
<list>
<ref bean="studentOne"></ref>
<ref bean="studentTwo"></ref>
<ref bean="studentThree"></ref>
</list>
</property>
Map:
private Map<String, Teacher> teacherMap;
<bean id="teacherOne" class="com.atguigu.spring6.bean.Teacher"></bean>
<bean id="teacherTwo" class="com.atguigu.spring6.bean.Teacher"></bean>
<bean id="studentFour" class="com.atguigu.spring6.bean.Student">
<property name="teacherMap">
<map>
<entry>
<key>
<value>10010</value>
</key>
<ref bean="teacherOne"></ref>
</entry>
<entry>
<key>
<value>10086</value>
</key>
<ref bean="teacherTwo"></ref>
</entry>
</map>
</property>
</bean>
引入集合类型的bean:是一种给List和Map赋值的方法
:使用util:list、util:map标签必须引入相应的命名空间,在xml头部添加一些东西
<!--list集合类型的bean-->
<util:list id="students">
<ref bean="studentOne"></ref>
<ref bean="studentTwo"></ref>
<ref bean="studentThree"></ref>
</util:list>
<!--map集合类型的bean-->
<util:map id="teacherMap">
<entry>
<key>
<value>10010</value>
</key>
<ref bean="teacherOne"></ref>
</entry>
<entry>
<key>
<value>10086</value>
</key>
<ref bean="teacherTwo"></ref>
</entry>
</util:map>
<bean id="studentFour" class="com.atguigu.spring6.bean.Student">
<property name="teacherMap" ref="teacherMap"></property>
<property name="students" ref="students"></property>
</bean>
3.1.7 p命名空间
<!--引入-->
<beans xmlns:p="http://www.springframework.org/schema/p"></beans>
<!--使用-->
<bean id="studentSix" class="com.atguigu.spring6.bean.Student"
p:id="1006" p:name="小明" p:clazz-ref="clazzOne" p:teacherMap-ref="teacherMap"></bean>
3.1.8 引入外部属性文件
:使用context
标签
例子:
创建外部属性文件,
jdbc.properties
,内容如下jdbc.user=root jdbc.password=atguigu jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC jdbc.driver=com.mysql.cj.jdbc.Driver
引入context名词空间,并使用context引入
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> </beans> <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>
3.1.9 bean生命周期
可以设定对应的钩子函数
bean对象创建(调用无参构造器)
给bean对象设置属性
bean的后置处理器(初始化之前)
bean对象初始化(需在配置bean时指定初始化方法)
bean的后置处理器(初始化之后)
bean对象就绪可以使用
bean对象销毁(需在配置bean时指定销毁方法)
IOC容器关闭
3.1.10 bean的作用域
:在类中多次ac.getBean(该bean)
得到A,B,C对象,singleton表示A,B,C为同一个,即A,B,C的Hash地址相同
:而prototype,则A,B,C的Hash地址不同
<!-- scope属性:取值singleton(默认值),bean在IOC容器中只有一个实例,IOC容器初始化时创建对象 -->
<!-- scope属性:取值prototype,bean在IOC容器中可以有多个实例,getBean()时创建对象 -->
<bean class="com.atguigu.spring6.bean.User" scope="prototype"></bean>
3.1.11 FactoryBean
:帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们
:用来整合Mybatis等框架
使用:
package com.atguigu.spring6.bean;
public class UserFactoryBean implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
return new User();
}
@Override
public Class<?> getObjectType() {
return User.class;
}
}
<bean id="user" class="com.atguigu.spring6.bean.UserFactoryBean"></bean>
注意:使用bean创建对象时,返回的是User
对象,不是UserFactoryBean
对象
3.1.12 XML的自动装配
:一种简化对象类型赋值的方法
:使用autowire
标签,
例子:给UserController类的对象属性userService赋值
public class UserController {
private UserService userService;
byType
方式:根据类型匹配IOC容器中的某个兼容类型的bean,为属性自动赋值,若在IOC中,没有任何一个兼容类型的bean能够为属性赋值,则该属性不装配,即值为默认值null,若在IOC中,有多个兼容类型的bean能够为属性赋值,则抛出异常
<bean id="userController" class="com.atguigu.spring6.autowire.controller.UserController" autowire="byType"></bean>
<bean id="userService" class="com.atguigu.spring6.autowire.service.impl.UserServiceImpl"></bean>
byName
:将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相对应的bean进行赋值
注意:byName
方法 id
值必须与对象属性名相同
<bean id="userController" class="com.atguigu.spring6.autowire.controller.UserController" autowire="byName"></bean>
<bean id="userService" class="com.atguigu.spring6.autowire.service.impl.UserServiceImpl"></bean>
3.2 基于注解管理bean
3.2.1 使用注解
开启组件扫描:Spring 会自动从扫描指定的包(base-package 值)及其子包下的所有类的注解进行装配
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启组件扫描功能-->
<context:component-scan base-package="com.atguigu.spring6"></context:component-scan>
</beans>
context:component-scan
还可以设定 指定要排除的哪些组件,或设定仅扫描指定的组件
3.2.2 将类定义成bean
将类标明,给Ioc容器管理,将类定义成bean
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"></bean>
将类定义成bean:在类上面添加注解@Component
、@Repository
、@Service
、@Controller
等
注意:四个注解无差异,只是为了区别Dao、Service、Controller层
@Component
public class User {}
3.2.3 属性注入-@Autowired
:为属性注入,默认byType
类型装配
:可以标在属性、构造方法、普通方法、形参、注解上
public class User {
//属性上
@Autowired
private UserDao userDao;
//构造方法上
public User(UserDao userDao) {
this.userDao = userDao;
}
//普通方法,set方法上
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
//形参上
public UserServiceImpl(@Autowired UserDao userDao) {
this.userDao = userDao;
}
}
与@Qualifier
搭配使用,byName,根据名称进行装配
@Autowired
@Qualifier("userDaoImpl") // 指定bean的名字
private UserDao userDao;
3.2.4 @Resource
:定义类生成bean,注入属性
与@Autowired区别:
@Resource注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标准注解,更加具有通用性。(JSR-250标准中制定的注解类型。JSR是Java规范提案。)
@Autowired注解是Spring框架自己的。
@Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配。
@Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解一起用。
@Resource注解用在属性上、setter方法上。
@Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上。
定义类生成bean:
@Repository("myUserDao")
public class UserDaoImpl implements UserDao {}
属性注入:
@Resource(name = "myUserDao")
private UserDao myUserDao;
3.2.5 全注解开发
不再使用spring配置文件了,写一个配置类来代替配置文件
原来:写xml文件,使用ClassPathXmlApplicationContext与xml文件建立联系
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启组件扫描功能-->
<context:component-scan base-package="com.atguigu.spring6"></context:component-scan>
</beans>
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
UserController userController = context.getBean("userController", UserController.class);
userController.out();
现在:写配置类,使用AnnotationConfigApplicationContext与配置类建立联系
@Configuration
//指定某个包下使用注解
@ComponentScan("com.atguigu.spring6")
public class Spring6Config {
}
ApplicationContext context = new AnnotationConfigApplicationContext(Spring6Config.class);
UserController userController = context.getBean("userController", UserController.class);
userController.out();
4. 面向切面:AOP
:可以在指定方法调用时,插入一些方法
:利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
:用户验证、日志管理、事务处理、数据缓存等一些非核心业务,将其抽离出来,做成切面类,哪个方法需要就动态代理方式将其逻辑上,插入到方法中执行
:简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。
:代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了。
4.1通知
:简言之,类似在方法被调用执行周期的钩子函数
@Before
前置通知:在被代理的目标方法前执行@After
返回通知:在被代理的目标方法成功结束后执行(寿终正寝)@AfterThrowing
异常通知:在被代理的目标方法异常结束后执行(死于非命)@AfterReturning
后置通知:在被代理的目标方法最终结束后执行(盖棺定论)@Around
环绕通知:使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
4.2 例子
添加spring aop依赖、spring aspects依赖
创建目标类
@Component public class CalculatorImpl implements Calculator { @Override public int add(int i, int j) { int result = i + j; System.out.println("方法内部 result = " + result); return result; } }
创建切面类,并指定方法,其他通知类似
// @Aspect表示这个类是一个切面类 @Aspect // @Component注解保证这个切面类能够放入IOC容器 @Component public class LogAspect { //@Before注解表示在指定方法中使用前置通知,括号内填切入点表达式,指定切入的目标方法 @Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))") public void beforeMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); String args = Arrays.toString(joinPoint.getArgs()); System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args); } }
beans.xml中应配置
<aop:aspectj-autoproxy />
结果,红色框内为前置通知的效果,
4.3 切入点表达式
表明在哪个或哪些方法中切入,括号内为切入点表达式
@Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
写法格式
重用切入点表达式:
:写一个方法,然后切入类的方法中使用
@Pointcut("execution(* com.atguigu.aop.annotation.*.*(..))")
public void pointCut(){}
//在同一切面
@Before("pointCut()")
public void beforeMethod(JoinPoint joinPoint){}
//不同切面
@Before("com.atguigu.aop.CommonPointCut.pointCut()")
public void beforeMethod(JoinPoint joinPoint){}
4.4 切面方法优先级
:相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。
使用@Order注解可以控制切面的优先级:
@Order(较小的数):优先级高
@Order(较大的数):优先级低
4.5 基于xml的AOP
<context:component-scan base-package="com.atguigu.aop.xml"></context:component-scan>
<aop:config>
<!--配置切面类loggerAspect,该类需在Ioc容器中-->
<aop:aspect ref="loggerAspect">
<!--配置切入点-->
<aop:pointcut id="pointCut"
expression="execution(* com.atguigu.aop.xml.CalculatorImpl.*(..))"/>
<!--指定切入类的方法做前置通知-->
<!--aop:before表示前置通知,beforeMethod是切面类的方法-->
<aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
</aop:aspect>
</aop:config>
5. Spring整合JUnit
在之前的测试方法中,几乎都能看到以下的两行代码:
ApplicationContext context = new ClassPathXmlApplicationContext("xxx.xml");
Xxxx xxx = context.getBean(Xxxx.class);
这两行代码的作用是创建Spring容器,最终获取到对象,但是每次测试都需要重复编写。针对上述问题,我们需要的是程序能自动帮我们创建容器。从而省去写这两行代码。
做法:
引入spring对
junit
的支持相关依赖,和junit
依赖添加配置文件,添加相关命名空间,命名约束
测试类中使用
SpringJUnitConfig
注解@SpringJUnitConfig(locations = "classpath:beans.xml") public class SpringJUnit5Test { @Autowired private User user; @Test public void testUser(){ System.out.println(user); } }
6. 事务
:Spring框架下的关于数据库的事务知识
6.1 Spring的JDBC封装
:Spring中封装了JDBC的jar包,导入后使用其来对数据库进行操作
:使用包内的jdbcTemplate
类的方法进行JDBC
如图,查询数据库数据的方法
6.2 基于注解的声明式事务
添加事务注解@Transactional
,表示使用事务执行
@Transactional标识在方法上,则只会影响该方法
@Transactional标识的类上,则会影响类中所有的方法
//括号内设置事务的属性
@Transactional(readOnly = true)
public void buyBook(Integer bookId, Integer userId) {}
readOnly
:只读,表示该方法操作数据库时只允许读操作,不允许写操作timeout
:超时,该事务超过指定时间未提交,则回滚noRollbackFor
、rollbackFor
:设置哪种异常回滚,哪种不用回滚isolation
:设置隔离级别(参考Mysql)propagation
:事务的传播,一个方法调用另一个方法时,其各自的事务的传递规则
7. 资源操作
:Spring中提供了一些接口来获取资源,获取网络资源,本地文件资源,本项目资源(css、img)等
UrlResource类访问网络资源:
Resource
的一个实现类,用来访问网络资源,它支持URL的绝对路径。
http:------该前缀用于访问基于HTTP协议的网络资源。
ftp:------该前缀用于访问基于FTP协议的网络资源
file: ------该前缀用于从文件系统中读取资源
ClassPathResource 类访问类路径下资源:
该类来访问类加载路径下的资源,即项目的包下的所有类class
FileSystemResource
类访问本地资源:
使用 FileSystemResource 来访问文件系统资源并没有太大的优势,因为 Java 提供的 File 类也可用于访问文件系统资源。
使用Resource 作为属性:
当资源改变时,需修改代码中的资源路径,麻烦,所以将Resource 作为属性,通过配置注入的方式,更方便的维护资源路径
8. 国际化 i18n
:由于软件发行可能面向多个国家,对于不同国家的用户,软件显示不同语言的过程就是国际化。
:通常来讲,软件中的国际化是通过配置文件来实现的,假设要支撑两种语言,那么就需要两个版本的配置文件。
:Spring 和 java都提供相应的方法
9. 数据校检
在开发中,我们经常遇到参数校验的需求,比如用户注册的时候,要校验用户名不能为空、用户名长度不超过20个字符、手机号是合法的手机号格式等等。如果使用普通方式,我们会把校验的代码和真正的业务处理逻辑耦合在一起,而且如果未来要新增一种校验逻辑也需要在修改多个地方。而spring validation允许通过注解的方式来定义对象校验规则,把校验和业务逻辑分离开,让代码编写更加方便
:Spring提供相应的接口