目录
前言
spring框架的核心特性:可以用于开发任何的java应用程序,但是在javaEE平台上构建web应用程序是需要扩展的。spring框架的目标是使javaEE变得更容易使用,通过基于pojo编程模型来促进良好的编程实践
Spring Framework:Spring基础框架,可以视为Spring基础设施,基本上任何其他的Spring项目都是以Spring Framework为基础的
Spring Framework五大功能模块
Spring Framework特性
- 非侵入式:使用spring framework开发应用程序时,spring对应用程序本身的结构影响非常小,对领域模型可以做到零污染;对功能性组件也只需要用几个简单的注解进行标记,完全不会破坏原有结构,反而能将组件结构进一步简化
- 控制反转:IOC——inversion of control,翻转资源获取方向。把自己创建资源、向环境索取资源变成环境将资源准备好,我们享受资源注入(将对象的控制权交给spring容器来保存)
- 面向切面编程:AOP——aspect oriented programming,在不修改源代码的基础上增强代码功能
- 容器:Spring IOC是一个容器,因为它包含并管理组件对象的生命周期。组件享受到了容器化的管理,替程序员屏蔽了组件创建过程的大量细节,极大的降低了使用门槛,大幅度的提高了开发效率
- 组件化:spring实现了使用简单的组件配置组合成一个复杂的应用。在spring中可以使用XML和java注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭建超大型复杂应用系统
- 声明式:很多以前需要编写的代码才能实现的功能,现在只需要声明需求即可由框架代为实现
- 一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且spring旗下的项目已经覆盖了广泛的领域,很多方面的功能性需求可以在spring framework的基础上全部使用spring来实现
IOC与DI
IOC:反转控制(inversion of control)的思想完全颠覆了应用程序组件获取资源的传统方式;反转了资源的获取方向——改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式
DI:dependency injection是ioc的另一种表述形式;即组件以一些预先定义好的方式(例如:setter方法)接收来自于容器的资源注入。相对于ioc而言,这种表述更直接
结论:IOC就是一种反转控制的思想,而DI是对IOC的一种具体实现
IOC容器
spring的ioc容器就是ioc思想的一个落地产品的实现。ioc容器中管理的组件叫做bean。在创建bean之前,首先需要创建ioc容器。spring提供了ioc容器的两种实现方式
- BeanFactory:这是IOC容器的基本实现,是spring内部使用的接口。面向spring本身,不提供给开发人员使用
- ApplicationContext:BeanFactory的子接口,提供了更多高级的特性。面向spring的使用者,几乎所有的场合都使用ApplicationContext,而不是底层的BeanFactory
BeanFactory的继承结构
基于Xml文件来管理bean
导入依赖
<!--设置打包方式-->
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<!--junit测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
</dependency>
</dependencies>
helloworld对象
public class HelloWorld implements World{
public void eat(){
System.out.println("我要吃100碗米饭");
}
}
配置spring的ioc容器,并将对象交给ioc容器来管理
<?xml version="1.0" encoding="UTF-8"?>
<!--applicationContext.xml文件内-->
<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">
<!--ioc容器配置文件-->
<!--id是bean的唯一标识,class是bean对象所对应的类型(将helloworld这个对象交给ioc容器来管理)-->
<!--scope属性用于区分spring提供的是单例还是多例:singleton为单例,prototype为多例(就是每次获取到的对象一样不一样)-->
<bean id="helloworld" class="cn.tedu.pojo.HelloWorld" scope="singleton"></bean>
</beans>
进行测试
@Test
public void test(){
//通过读取类路径文件来获取ioc容器(resources和java最终加载到了同一个目录下)
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取ioc容器中的bean对象
HelloWorld bean = (HelloWorld) context.getBean("helloworld");//根据bean的id来获取bean对象
bean.eat();
}
总结:我们把对象交给ioc容器来管理,之后我们获取了ioc容器我们就可以获取ioc容器中的对象,就可以调用对象中的方法
spring创建对象流程:获取spring容器,调用getbean方法的到对应的bean,该bean内由class类路径,之后根据class.forName(“类路径”).instance()反射创建对象(必须该对象有无参构造)
获取bean的三种方式
- 根据id获取bean(需要强转)
- 根据类型获取bean(注意:IOC容器中不能有多个类型匹配的bean)
- 根据类型和id获取bean(在类型匹配的情况下进一步筛选)
@Test
public void test(){
//通过读取类路径文件来获取ioc容器(resources和java最终加载到了同一个目录下)
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取ioc容器中的bean对象
//根据bean的id来获取bean
HelloWorld bean1 = (HelloWorld) context.getBean("helloworld");//根据bean的id来获取bean对象
bean1.eat();
//根据bean的类型来获取
HelloWorld bean2 = context.getBean(HelloWorld.class);
bean2.eat();
//根据类型和id获取bean
HelloWorld bean3 = context.getBean("helloworld", HelloWorld.class);
bean3.eat();
//根据接口类型获取bean(HelloWorld为World的实现类)
World bean = context.getBean(World.class);
System.out.println(bean);//但是仅能得到接口方法及属性
}
注意:如果组件的类实现了接口,那么可以根据接口类型获取到bean;但是,该接口有多个实现类,这些实现类都配置了bean,那么根据接口类型便不能获取了,会报错(和根据类型获取对象的冲突类似)
总结:根据类型获取bean时,在满足bean唯一性的前提下,其实只是看(对象 instanceof 指定的类型)的返回结果,只要返回的是true就可以认为和类型匹配,就能获取到
依赖注入
依赖注入之setter注入(得有set方法)
<!--setter注入-->
<bean id="studentTwo" class="cn.tedu.spring.pojo.Student" scope="singleton">
<!--setter注入sname属性并未其赋值为lili-->
<!--name为属性名,value为属性赋值,ref为引用-->
<property name="sname" value="lili"></property>
<property name="age" value="23"></property>
<property name="gender" value="女"></property>
<property name="sid" value="1001"></property>
</bean>
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-ioc.xml");
Student studentTwo = context.getBean("studentTwo", Student.class);
System.out.println(studentTwo);//Student(sid=1001, sname=lili, age=23, gender=女)
依赖注入之构造器注入(得有有参构造)
<!--构造器注入-->
<bean id="studentThree" class="cn.tedu.spring.pojo.Student">
<!--确定value参数是给name属性赋值-->
<constructor-arg name="sname" value="nana"></constructor-arg>
<constructor-arg name="age" value="17"></constructor-arg>
<constructor-arg name="gender" value="女"></constructor-arg>
<constructor-arg name="sid" value="2202"></constructor-arg>
</bean>
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-ioc.xml");
Student studentThree = context.getBean("studentThree", Student.class);
System.out.println(studentThree);//Student(sid=2202, sname=nana, age=17, gender=女)
特殊值处理
基本赋值
字面量:我们看到他是什么数据,那么他就是什么数据
value属性:value里面写的就是我们当前为字面量类型所赋的值(value里面写的是什么,那么为当前赋的值就是什么)
为属性赋值为null:在property标签里使用null标签(可以自闭)
xml实体:
常用xml实体(更多见我html帖子)
- <:<
- >:>
CDATA节:
注意:CDATA就代表纯文本数据,xml解析器看到CDATA节就知道这里是纯文本,就不会当作xml标签或属性来解析,所以CDATA节中写什么符号都随意(CDATA只能以标签的形式写在标签中)
<![CDATA[a<b]]>:代表a<b
<bean id="studentFive" class="cn.tedu.spring.pojo.Student">
<property name="sid" value="1003"></property>
<!--xml实体写<lili>-->
<property name="sname" value="<lili>"></property>
<!--为gender设置a<b-->
<property name="gender">
<value><![CDATA[a<b]]></value>
</property>
<!--将age赋值为null-->
<property name="age">
<null></null><!--也可以写成<null/>-->
</property>
</bean>
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-ioc.xml");
Student studentFive = context.getBean("studentFive", Student.class);
System.out.println(studentFive);//Student(sid=1003, sname=<lili>, age=null, gender=a<b)
为类/接口属性赋值
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private Integer sid;
private String sname;
private Integer age;
private String gender;
private Clazz clazz;//我是定义的Clazz类
}
@Data
public class Clazz {
private Integer cid;
private String cname;
}
ref属性引用赋值
含义:表示引用当前IOC容器中的某一个bean的id
<bean id="studentSix" class="cn.tedu.spring.pojo.Student">
<property name="sid" value="1004"></property>
<property name="sname" value="lili"></property>
<property name="age" value="27"></property>
<property name="gender" value="男"></property>
<!--为class赋值,ref表示引用,引用的是当前IOC容器中的某一个bean的id-->
<property name="clazz" ref="clazzOne"></property>
</bean>
<bean id="clazzOne" class="cn.tedu.spring.pojo.Clazz">
<property name="cid" value="1111"></property>
<property name="cname" value="黄阶二班"></property>
</bean>
级联方式
<!--级联方式-->
<bean id="studentSix" class="cn.tedu.spring.pojo.Student">
<property name="sid" value="1004"></property>
<property name="sname" value="lili"></property>
<property name="age" value="27"></property>
<property name="gender" value="男"></property>
<!--必须现给clazz赋值-->
<property name="clazz" ref="clazzOne"></property>
<!--这里面的属性就相当于把之前的clazz属性进行修改-->
<property name="clazz.cid" value="2222"></property>
<property name="clazz.cname" value="黄阶二班"></property>
</bean>
<bean id="clazzOne" class="cn.tedu.spring.pojo.Clazz">
<property name="cid" value="1111"></property>
<property name="cname" value="黄阶二班"></property>
</bean>
内部bean方式
<bean id="studentSeven" class="cn.tedu.spring.pojo.Student">
<property name="sid" value="1004"></property>
<property name="sname" value="lili"></property>
<property name="age" value="27"></property>
<property name="gender" value="男"></property>
<property name="clazz">
<bean id="classInner" class="cn.tedu.spring.pojo.Clazz">
<property name="cid" value="3333"></property>
<property name="cname" value="三班"></property>
</bean>
</property>
</bean>
注意:内部bean只能在bean的内部来使用,不能直接通过IOC来获取对象
为数组类型赋值
<bean id="studentSeven" class="cn.tedu.spring.pojo.Student">
<property name="sid" value="1004"></property>
<property name="sname" value="lili"></property>
<property name="age" value="27"></property>
<property name="gender" value="男"></property>
<property name="clazz">
<bean id="classInner" class="cn.tedu.spring.pojo.Clazz">
<property name="cid" value="3333"></property>
<property name="cname" value="三班"></property>
</bean>
</property>
<!--为数组类型赋值-->
<property name="hobby">
<array>
<value>足球</value>
<value>篮球</value>
</array>
</property>
</bean>
注意:数组值可以ref类型也可以为value类型(分别用不同的标签——如果用ref标签则直接在ref标签内的bean属性里写对应引用bean的名字)
为list集合类型赋值
<!--这里没值-->
<bean id="studentOne" class="cn.tedu.spring.pojo.Student" scope="singleton"/>
<!--setter注入-->
<bean id="studentTwo" class="cn.tedu.spring.pojo.Student" scope="singleton">
<property name="sname" value="lili"></property>
<property name="age" value="23"></property>
<property name="gender" value="女"></property>
<property name="sid" value="1001"></property>
</bean>
<!--方法1-->
<bean id="clazzTwo" class="cn.tedu.spring.pojo.Clazz">
<property name="cid" value="1111"></property>
<property name="cname" value="最强王者办"></property>
<!--为list集合属性赋值-->
<property name="studentList">
<list>
<ref bean="studentOne"></ref>
<ref bean="studentTwo"></ref>
</list>
</property>
</bean>
<!--方法2-->
<bean id="clazzTwo" class="cn.tedu.spring.pojo.Clazz">
<property name="cid" value="1111"></property>
<property name="cname" value="最强王者办"></property>
<!--为list集合属性赋值,name为属性名,ref后面接引用id-->
<property name="studentList" ref="studentList"></property>
</bean>
<!--配置集合类型的约束,使用1util约束-->
<util:list id="studentList">
<ref bean="studentOne"></ref>
<ref bean="studentTwo"></ref>
</util:list>
为map集合属性赋值
<!--不往里面赋值了,太累-->
<bean id="teacherOne" class="cn.tedu.spring.pojo.Teacher"></bean>
<bean id="teacherTwo" class="cn.tedu.spring.pojo.Teacher"></bean>
<!--方式1-->
<bean id="childOne" class="cn.tedu.spring.pojo.Child">
<property name="cid" value="1998"></property>
<property name="cname" value="lili"></property>
<property name="teacherMap">
<map>
<!--把老师的id作为键,把老师的对象作为值-->
<entry key="10086" value-ref="teacherOne"></entry>
<entry key="10010" value-ref="teacherTwo"></entry>
</map>
</property>
</bean>
<!--方式2-->
<bean id="childOne" class="cn.tedu.spring.pojo.Child">
<property name="cid" value="1998"></property>
<property name="cname" value="lili"></property>
<property name="teacherMap" ref="teacherMap"> </property>
</bean>
<util:map id="teacherMap">
<entry key="10086" value-ref="teacherOne"></entry>
<entry key="10010" value-ref="teacherTwo"></entry>
</util:map>
依赖注入之p命名空间
<bean id="childTwo" class="cn.tedu.spring.pojo.Child">
<property name="cname" value="爸爸"></property>
</bean>
<bean id="personOne" class="cn.tedu.spring.pojo.Person"
p:pid="1005" p:pname="lili" p:child-ref="childTwo"></bean>
注意:
- 使用时必须得有命名空间
- 通过bean标签的属性来为当前bean的属性赋值
spring管理数据源和引入外部属性文件
加入依赖
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!--数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql:///ssm?serverTimezone=UTC
jdbc.username=root
jdbc.password=root
创建新的spring配置文件
<!--spring-datasource.xml文件内-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--引入jdbc.properties文件,之后就可以通过${key}方式访问value-->
<context:property-placeholder location="jdbc.properties"></context:property-placeholder>
<!--class写接口实现类,不能写接口-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
</beans>
测试获取数据源
@Test
public void testDataSource() throws Exception {
//获取配置数据源的ioc容器
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-datasource.xml");
DataSource dataSource = context.getBean(DataSource.class);
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
bean的作用域
在spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,属性如下
如果是在WebApplicationContext环境下会有另外两个作用域
<!--scope:设置bean的作用域
singleton:表示获取该bean所对应的对象都是同一个
prototype:表示获取该bean所对应的对象都不是同一个
-->
<bean id="student" class="cn.tedu.spring.pojo.Student" scope="singleton">
<property name="sid" value="1001"></property>
<property name="sname" value="张三"></property>
</bean>
作用域注解:@Scope(value="singleton")
注意:该注解可以用在@Bean标识的方法中,也可以标识在@Component标识的类中;从而表明获取到的对象为单例或多例模式
bean的生命周期
- 实例化
- 依赖注入(给对象设置属性)
- bean对象初始化之前的操作(由bean的后置处理器负责)
- 初始化:通过bean的init-method属性指定初始化方法
- bean对象初始化后的操作(由bean的后置处理器负责)
- bean对象的使用
- 销毁:通过bean的destroy-method属性来指定销毁的方法
- IOC容器的关闭
测试生命周期
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
public User() {
System.out.println("实例化");
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setAge(Integer age) {
this.age = age;
}
public void setId(Integer id) {
System.out.println("依赖注入");
this.id = id;
}
void init(){
System.out.println("初始化");
}
void destroy(){
System.out.println("销毁");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!--spring-liftcycle.xml文件内-->
<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">
<!--init-method表示bean初始化方法,destory-meethod表示销毁方法-->
<bean id="user" class="cn.tedu.spring.pojo.User" init-method="init" destroy-method="destroy">
<property name="id" value="1"></property>
<property name="username" value="admin"></property>
<property name="password" value="123456"></property>
<property name="age" value="18"></property>
</bean>
</beans>
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-lifecycle.xml");
User user = context.getBean(User.class);
System.out.println(user);
//关闭容器
context.close();
注意:
- 若bean的作用域为单例时,生命周期的前三个步骤会在获取IOC容器时执行(默认为快速加载)
- 若bean的作用域为多例时,生命周期的前三个步骤会在获取bean时执行,默认为快速加载(销毁的方法就不由IOC容器来管理)
bean的后置处理器
bean的后置处理器会在初始化前后添加额外的操作,需要实现BeanPostProcessor接口,配置到ioc容器中,需要注意的是,bean的后置处理器不是单独针对某一个bean生效,而是针对当前ioc容器中的所有bean都会执行
public class MyBeanProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
//此方法在生命周期初始化之前执行
System.out.println("初始化之前执行");
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//此方法在生命周期初始化执行
System.out.println("初始化执行之后执行");
return bean;
}
}
注意:bean的后置处理器要想生效必须实现BeanPostProcessor接口,并且重写里面的方法;而且也需要将该处理器配置到ioc容器中方可使用
懒加载与快速加载
懒加载:多例模式下生命周期的前三个步骤会在获取bean时执行
快速加载:单例模式下生命周期的前三个步骤默认会在获取IOC容器时执行
懒加载注解:@lazy
注意:该注解可以用在@bean标识下的方法内,也可以用在@Component标识的类上;从而表明获取到的对象为懒加载模式
FactoryBean接口
方法
<T> getObject():通过一个对象交给ioc容器来管理
Class<?> getObjectType():设置所提供对象的类型
boolean isSingleton():所提供的对象是否为单例
注意:
- 当我们把FactoryBean的实现类配置为bean时,会将类中getObject方法所返回的对象交给ioc容器来管理
- 相对于传统工厂,传统工厂是先获取工厂对象再创建对象,而再factoryBean实现类中可以直接获取到对象
<!--如果获取该bean,则直接返回getObject方法所返回的对象-->
<bean class="cn.tedu.spring.factory.UserFactoryBean"></bean>
public class UserFactoryBean implements FactoryBean<User> {
public User getObject() throws Exception {
return new User();
}
public Class<?> getObjectType() {
return User.class;
}
}
基于xml的自动装配
手动装配
<bean id="userController" class="cn.tedu.spring.controller.UserController">
<property name="userService" ref="userService"></property>
</bean>
<bean id="userService" class="cn.tedu.spring.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao" class="cn.tedu.spring.dao.impl.UserDaoImpl"></bean>
自动装配
自动装配:根据指定策略,在ioc容器中匹配某一个bean,自动为指定的bean中所依赖的类类型或接口类型属性赋值
注意:我们可以通过bean标签中的autowire属性设置自动装配策略
自动装配的策略
- no\default:表示不装配,即bean中的属性不会自动匹配某个bean为属性赋值,此时属性使用默认值
- byType:根据类型在ioc容器中进行bean的匹配,来为该属性赋值(若通过类型没有找到任何一个类型匹配的bean,则此时属性使用default,若找到多个则报错)
- byName:根据bean的id名来进行bean的匹配。
基于注解管理bean
前言:和xml配置文件一样,注解本身并不能执行,注解本身仅仅只做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置,按照注解标记的功能来执行具体的操作
扫描:spring为了知道程序员在那些地方标记了什么注解就需要通过扫描的方式来进行检测,然后根据注解来进行后续操作(将扫描到的包内的类交给spring容器来保存)
标识组件的常用注解
- @Component:将类标识为普通组件
- @Controller:将类标识为控制层组件
- @Service:将类标识为业务层组件
- @Repository:将类标识为持久层组件
注意:这四个注解功能一模一样,没有任何区别,都是将对应的类交给spring容器来保存,只不过名字不一样(便于分辨组件的作用)
配置好之后还需要包扫描
<!--扫描组件,cn.tedu.spring包下所有的类均会被扫描-->
<!--use-default-filters为false表示不使用默认的扫描规则,默认的扫描规则为该包下的所有类均扫描-->
<context:component-scan base-package="cn.tedu.spring" use-default-filters="false">
<!--不扫描controller注解标识的类(annotation标识注解,assignable标识类)expression放注解或类的全类名-->
<!--<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>-->
<context:include-filter type="assignable" expression="cn.tedu.spring.controller.UserController"/><!--在不适用默认扫描规则情况下表示只扫描UserController类-->
</context:component-scan>
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-ioc-annotation.xml");
UserController userController = context.getBean(UserController.class);
System.out.println(userController);
基于注解管理bean之bean的id
- 通过注解+扫描配置的bean的id有默认值,默认为类的小驼峰(即,类名首字母小写)
- 改变注解管理bean的id为user:以Component为例——@Component(value = "user")
基于注解的自动装配@Autowire
@Controller
public class UserController {
//自动装配
@Autowired
private UserService userService;
public void saveUser(){
userService.saveUser();
}
}
@Autowire注解能够标识的位置
- 标识在成员变量上,此时不需要设置成员变量的set方法(直接根据策略在ioc容器中查找对应对象)
- 标识在set方法上
- 为当前成员变量赋值的有参构造上
@Autowire自动装配原理
@Autowire描述属性时用于告诉spring按照指定的规则为此注解描述的属性注入一个值,默认会先按照属性的类型查找对象,假如找不到则直接抛出异常,找到一个则直接注入;找到多个还会按照属性名与spring容器中的bean名称相比较,有相同则直接注入,没有相同则直接抛出异常
若有多个类型相匹配,之后按照名称进行匹配,但是根据默认的名称找不到具体名称,之后可以用@Qualifier("service")注解来指定一个bean的id来为当前的属性赋值
@Controller
public class UserController {
//自动装配
@Autowired(required = true)
@Qualifier("service")
private UserService userService;
public void saveUser(){
userService.saveUser();
}
}
注意:在@Autowired注解属性里有个required属性,默认为true,要求必须完成自动装配,可以将required设置为false,此时能装配则装配,不能装配则使用属性默认值
AOP
含义:AOP(aspect oriented programming)是一种设计思想,是软件设计领域中的面向切面编程,他是面向对象编程的一种补充和完善,它通过预编译的方式和运行期动态代理的方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术
相关概念
横切关注点:从每个方法中抽取出来的同一类核心业务,同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强
切面:用于封装横切关注点的类(每个横切关注点都表示为一个通知方法)
注意:我们要把横切关注点封装到切面中,而在这个切面中每一个横切关注点都表示一个横切方法
连接点:表示横切关注点抽出来的位置
切入点:定位连接点的方式(表达式)
通知:每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法叫通知方法
通知分类
- 前置通知:在被代理的目标方法前执行
- 返回通知:在被代理的目标方法成功结束后执行
- 异常通知:在被代理的目标方法异常结束后执行
- 后置通知:在被代理的目标方法最终结束后执行
- 环绕通知:目标方法的前后都可以执行某些代码,用于控制目标方法
AOP作用
- 简化代码:把方法中固定位置的重复代码抽取出来,让抽取的方法更专注于自己的核心功能,提高内聚性
- 代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了
注意:AOP依赖于IOC而存在
基于注解的AOP
切入点表达式
bean表达式
bean(bean的id)//没有引号
within表达式
//只拦截具体包下的具体类
within(com.jt.service.User)
//拦截具体包下的所有类
within(com.jt.service.*)
//拦截具体包下的所有包类
within(com.jt.service..*)
//拦截com.任意包.service包下的所有包类
within(com.*.service..*)
execution表达式
语法:execution(返回值类型 包名.类名.方法名(参数列表))
//拦截返回值任意的具体方法
execution(* com.jt.service.UserServiceImpl.addUser())
//拦截返回值任意,参数列表任意,具体包service所有子包与子类的所有方法
execution(* com.jt.service..*.*(..))
//拦截返回值任意,参数列表为两个int类型,具体包service所有子包与子类的add方法
execution(* com.jt.service..*.add(int,int))
@annoation表达式
前言:定义一个注解类,在需要扩展的方法上加注解
//对被有该注解标明的方法有效(里面填定义注解的位置)
@annotation(com.jt.anno.注解)
基于注解的AOP使用
- 导入特定依赖
- 定义切面类,用@Aspect标识
- 将切面类与目标类交给ioc容器管理,在ioc容器中进行扫描并开启注解的aop功能配置
- 写好切入点表达式以及通知,表明要扩展什么方法的什么功能
- 测试
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<!--junit测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
</dependency>
<!--aop依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
</dependencies>
@Component
@Aspect//通过该注解将当前组件标识为切面
public class LoggerAspect {
//这种写法只是有利于复用切点表达式
@Pointcut("execution(public int cn.tedu.spring.aop.annotation.Calculatorimpl.*(int,int))")
public void pointCut1(){}
@Before("pointCut1()")//当然,里面也可以直接写切入点表达式
public void beforeAdviceMethod(){
System.out.println("我是前置通知");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--aop注意事项:切面类和目标类都需要交给ioc容器管理-->
<context:component-scan base-package="cn.tedu.spring.aop.annotation"></context:component-scan>
<!--开启基于注解的aop功能-->
<aop:aspectj-autoproxy/>
</beans>
@Test
public void test1(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("aop-annotation.xml");
//获取目标对象的代理对象
Calculator calculator = context.getBean(Calculator.class);
calculator.add(1,2);
}
获取连接点信息(joinPoint)
@Component
@Aspect//通过该注解将当前组件标识为切面
public class LoggerAspect {
//这种写法只是有利于复用切点表达式
@Pointcut("execution(public int cn.tedu.spring.aop.annotation.Calculatorimpl.*(int,int))")
public void pointCut1(){}
//jointPoint用于获取连接点的信息,表达式定位的是哪个方法,那么那么joinPoint表示的的就是哪个方法的信息
@Before("pointCut1()")//当然,里面也可以直接写切入点表达式
public void beforeAdviceMethod(JoinPoint joinPoint){
//获取连接点所对应的方法的方法名
Signature signature = joinPoint.getSignature();
//获取对应连接点所对应的方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("我是前置通知"+signature.getName()+",参数:"+ Arrays.toString(args));
}
总结:在通知方法的参数位置,设置joinPoint类型的参数,就可以获取连接点所对应的方法的信息
通知方法的注解标识
- @Before:前置通知,在目标对象方法执行之前执行
- @After:后置通知,在目标对象方法的finally子句中执行
- @AfterReturning:返回后通知,在返回值返回后通知,其执行在后置通知之前
- @AfterThrowing:异常通知,在目标方法的catch子句中执行(有异常时执行)
- @Around:环绕通知,在目标方法的前后均可以执行
@Component
@Aspect//通过该注解将当前组件标识为切面
public class LoggerAspect {
//这种写法只是有利于复用切点表达式
@Pointcut("execution(public int cn.tedu.spring.aop.annotation.Calculatorimpl.*(int,int))")
public void pointCut1(){}
@Before("pointCut1()")//当然,里面也可以直接写切入点表达式
public void beforeAdviceMethod(JoinPoint joinPoint){
System.out.println("我是前置通知");
}
@After("pointCut1()")
public void afterAdviceMethod(){
System.out.println("我是后置通知");
}
//returning属性作用:设置接收目标对象方法的返回值的一个参数的参数名的
//在返回通知中若要获取目标对象方法的返回值,只需要通过@AfterReeturning注解的returning属性就可以将通知方法的某个参数指定为接收目标对象方法的返回值参数
@AfterReturning(value = "pointCut1()",returning = "result")
public void afterReturningAdviceMethod(JoinPoint joinPoint,Object result){
System.out.println("我是返回后通知"+"结果为"+result);
}
//在异常通知中若要获取目标对象方法的异常,只需要通过@AfterThrowing注解的throwing属性就可以将通知方法的某个参数指定为接收目标对象方法的出现异常的参数
@AfterThrowing(value = "pointCut1()",throwing = "e")
public void afterThrowingAdviceMethod(JoinPoint joinPoint,Exception e){
System.out.println("我是异常通知"+",异常为"+e);
}
//ProceedingJoinPoint表示可执行的连接点的对象
//环绕通知方法的返回值一定要和目标对象方法的返回值一致
@Around("pointCut1()")
public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint){
Object result=null;
try {
System.out.println("环绕通知前置通知的位置");
//目标对象方法的执行
result = joinPoint.proceed();
System.out.println("环绕通知返回通知的位置");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知异常通知的位置");
}finally {
System.out.println("环绕通知后置通知的位置");
}
return result;
}
}
通知的执行顺序
Spring版本5.3以后
- 前置通知
- 目标操作
- 返回通知或异常通知
- 后置通知
切面的优先级
- 在实际开发过程中,我们通常会定义多个切面,在切面类上加注解@Order(value = 1)可以定义切面的优先级
- value的数字越小切面优先级越高
- 每一个切面的优先级都有一个默认值,其实Integer的最大值
基于xml的AOP实现
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--扫描组件-->
<context:component-scan base-package="cn.tedu.spring.aop.annotation"></context:component-scan>
<aop:config>
<!--设置一个公共的切入点表达式-->
<aop:pointcut id="pointCut1" expression="execution(* cn.tedu.spring.aop.annotation.Calculatorimpl.*(..))"/>
<!--将ioc容器中的某个组件设置为切面-->
<aop:aspect ref="loggerAspect">
<!--将切面中的方法设置为通知-->
<aop:before method="beforeAdviceMethod" pointcut-ref="pointCut1"></aop:before>
<aop:after method="afterAdviceMethod" pointcut-ref="pointCut1"></aop:after>
<aop:after-returning method="afterReturningAdviceMethod" returning="result" pointcut-ref="pointCut1"></aop:after-returning>
<aop:after-throwing method="afterThrowingAdviceMethod" throwing="e" pointcut-ref="pointCut1"></aop:after-throwing>
<aop:around method="aroundAdviceMethod" pointcut-ref="pointCut1"></aop:around>
</aop:aspect>
<!--order表示设置优先级-->
<aop:aspect ref="validateAspect" order="1">
<aop:before method="beforeMethod" pointcut-ref="pointCut1"></aop:before>
</aop:aspect>
</aop:config>
</beans>
jdbcTemplate
前言:spring框架对jdbc进行了封装,使用jdbcTemplate方便实现对数据库的操作
具体流程
- 导入依赖
- 配置jdbc.properties文件并引入spring容器进行数据源配置
- 使用jdbcTemplate整合
- 测试文件内进行测试
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.1</version>
</dependency>
<!--spring整合junit,可以让测试类在spring的测试环境中执行-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
<!--aop依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
</dependencies>
<?xml version="1.0" encoding="UTF-8"?>
<!--spring-jdbc.xml文件内-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--引入properties-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--连接数据源-->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
#jdbc.properties文件内
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql:///ssm?serverTimezone=UTC
jdbc.username=root
jdbc.password=root
//指定当前测试类在spring的测试环境中执行,此时就可以通过注入的方式直接获取ioc容器中的bean
@RunWith(SpringJUnit4ClassRunner.class)
//设置spring测试环境的配置文件
@ContextConfiguration("classpath:spring-jdbc.xml")
public class JdbcTest {
@Autowired
private JdbcTemplate jdbcTemplate;
//插入一条数据(增删改都可以用update)
@Test
public void testInsert(){
String sql="insert into t_user values(null,?,?,?,?,?)";
jdbcTemplate.update(sql,"root","123","23","女","123@qq.com");
}
//查询单条数据
@Test
public void testGetUserById(){
String sql="select * from t_user where id=?";
User user = jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<User>(User.class), 6);
System.out.println(user);
}
//查询多条数据
@Test
public void testGetAllUser(){
String sql="select * from t_user";
List<User> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class));
System.out.println(query);
}
@Test
public void testGetCount(){
String sql="select count(*) from t_user";
Integer integer = jdbcTemplate.queryForObject(sql, Integer.class);
System.out.println(integer);
}
}
声明式事务
编程式事务:事务功能的相关操作全部通过自己编写代码来实现
编程式事务的缺陷
- 细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐
- 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用
声明式事务:既然事务控制的代码有规律可循,代码的结构基本上是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作
声明式事务属性
只读
对一个查询操作来说,如果我们把它设置成只读,就能够明确的告诉数据库,这个操作不涉及写操作,这样数据库就能够针对查询操作来进行优化(只有当前事务中全是查询操作时才能用)
超时
事务在执行的过程中,有可能因为遇到某些问题导致程序卡住,从而长时间占用数据库资源。而长时间占用资源大概率是因为程序运行出现了问题;此时这个很可能出现问题的程序应该被回滚,撤销他已做的操作,事务结束,把资源让出来,让其他程序可以正常执行
回滚策略
注意:声明式事务默认只针对运行时异常回滚,编译时异常不回滚
- rollback属性:需要Class对象数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚
- rollbackForClassName属性:需要异常类名数组,当方法中抛出指定异常类名称之内的数组时进行回滚
- noRollback属性:需要Class对象数组,当方法中抛出指定异常数组中的异常时,则不进行事务回滚
- noRollbackForClassName属性:需要异常类名数组,当方法中抛出指定异常类名称之内的数组时不进行回滚
事务的传播行为
理解:
- Spring中默认的事务的传播行为为第一种,事务的传播行为一共有7种。既然是传播行为,那么具有的对象应该为两个,单体事务不会发生传播行为。
- 所谓的事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。
- 例如:方法A的事务方法调用方法B的事务方法时,方法B是继续在调用者方法A的事务中运行呢,还是为自己开启一个新事务运行,这就是由方法B的事务传播行为决定的。
事务的隔离级别
详见mysql帖子
基于注解的声明式事务
<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="cn.tedu.spring"></context:component-scan>
<!--引入properties-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--连接数据源-->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--
开启事务的注解驱动——将使用@transactional注解所标识的方法或类中的所有方法使用事务进行管理
transaction-manager属性用来配置事务管理器的id,若事务管理器的id默认为transactionManage,则该属性可以不写
-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
然后在你想要保持原子性的方法上加个@Transactional注解即可
@Transactional
- 其注解可作用与接口,接口方法上,类上以及类方法上
- @Transactional注解应该只用到public修饰的类或方法上
- 在该注解内可以设置声明式事务的属性
- 当作用在类上时,该类上的所有public方法都将具有该类事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别定义
- 在接口或接口方法上使用该注解,只有在使用接口的代理时才会生效
- 只有来自外部的方法调用才会被AOP代理捕获,也就是本类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解修饰
@Transactional注解属性
基于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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="cn.tedu.spring"></context:component-scan>
<!--引入properties-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--连接数据源-->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--用来配置事务通知,tx表示事务的唯一标识-->
<tx:advice id="tx" transaction-manager="transactionManager">
<tx:attributes>
<!--针对每个方法采取什么样的事务属性-->
<tx:method name="buyBook" propagation="REQUIRED"/><!--可以写很多参数-->
</tx:attributes>
</tx:advice>
<aop:config>
<!--引用事务通知-->
<!--方法1-->
<!--<aop:advisor advice-ref="tx" pointcut="execution(* cn.tedu.spring.service.impl.BookServiceImpl.*(..))"></aop:advisor>-->
<!--方法2-->
<aop:pointcut id="txPoint" expression="execution(* cn.tedu.spring.service.impl.BookServiceImpl.*(..))"/>
<aop:advisor advice-ref="tx" pointcut-ref="txPoint"></aop:advisor>
</aop:config>
</beans>
理解:tx内符合切入点表达式的所有方法都会遵循对应的事务
ContextLoaderListener监听器
spring提供了ContextLoaderListener,实现了ServletContextListener接口,可以监听ServletContext的状态,在web服务器的启动,读取spring的配置文件,创建spring的IOC容器,web应用中必须在web.xml中配置
<listener>
<!--在服务器启动时加载spring的配置文件来获取ioc容器-->
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<!--启动时加载spring.xml文件-->
<param-value>classpath:spring.xml</param-value>
</context-param>
解决问题:由下面可知组件,HelloController组件依赖于helloService组件,因此helloService组件扫瞄应该先于HelloController组件(用监听器的执行优先原则实现了快速扫描)
@Controller
public class HelloController {
@Autowired
private HelloService helloService;
}