Spring框架
一、spring简介
简介:spring框架是一个轻量级的控制反转和面向切面的容器框架,用来解决项目开发中的复杂度问题–解耦
轻量级:体积小,对代码没有侵入性(代码侵入性:指的是业务代码中不会调用spring中任何一个类)
控制反转:IoC(Inverse of contral),把对象创建的工作交给spring完成,并且他还可以给对象属性赋值。
面向切面:AOP(Aspect Oriented Programming)面向切面编程,可以在不改变原有业务逻辑的情况下实现对业务逻辑的增强
容器:实例的容器,管理创建的对象
图片显示的就是spring家族 这篇文章最主要介绍Core Container这里边的组件也是最核心的组件
Web组件也就是我们常说的SpringMVC框架
Test组件是spring提供的测试框架
Data组件是spring对jdbc的封装,相当于mybatis框架。但是我们这里只使用他的transctions组件即事务
先介绍spring最核心的功能IoC和Aop
二、SpringIoC
一、Spring框架部署(IoC)
1.1、创建Maven工程
创建这两个工程都可以.我创建java工程
- java
- web
1.1.2、添加springIoC依赖
- core
- beans
- aop
- expression
- context
注意:正常情况下这5个依赖是都要导入的,但是他们5个是互相依赖的所以导入一个就行,maven会自动导入其他的依赖。
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.22.RELEASE</version>
</dependency>
1.1.3、创建Spring配置文件
通过配置文件告诉Spring容器创建什么对象,给对象属性赋什么值
在rescources目录下创建applicationContext.xml文件(注意名字是可以随便起的,大家都用这个名所以我也用)
聪明的人会把下面代码存成模板
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
1.2、创建Student实体类
public class Student {
private String stuName;
private Integer stuAge;
private String stuGender;
@Override
public String toString() {
return "Student{" +
"stuName='" + stuName + '\'' +
", stuAge=" + stuAge +
", stuGender='" + stuGender + '\'' +
'}';
}
public String getStuName() {
return stuName;
}
public void setStuName(String stuName) {
this.stuName = stuName;
}
public Integer getStuAge() {
return stuAge;
}
public void setStuAge(Integer stuAge) {
this.stuAge = stuAge;
}
public String getStuGender() {
return stuGender;
}
public void setStuGender(String stuGender) {
this.stuGender = stuGender;
}
}
配置配置文件
<bean id="Student" class="com.xxp.bean.Student">
<property name="stuName" value="张三"/>
<property name="stuGender" value="男"/>
<property name="stuAge" value="25"/>
</bean>
写测试类(这里你会发现你已经没有在new出Student啦 直接从spring容器中获取)
public class StudentTest {
public static void main(String[]args){
//这个就是创建spring容器的对象
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//从spring容器中获取Student对象 注意:这里的参数是配置文件中的id
Object student = context.getBean("Student");
System.out.println(student);
}
}
二、spring的IoC和DI
IoC控制反转,依赖Spring对象工厂完成对象的创建
DI依赖注入,对创建好的对象的属性赋值
2.1、DI依赖注入
2.1.1、三种方式
spring容器通过反射给类创建对象,并给对象赋值
通过反射,给对象赋值的方式有3种:
- set方式注入
- 构造器注入
- 接口注入(不常用)
2.1.1.1、set注入
使用到的实体类:
/**
* 书类
* @author xxp
*/
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Book {
private Integer bookId;
private String bookName;
}
/**
* Student实体类
* @author xxp
*/
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private String stuName;
private Integer stuAge;
private String stuGender;
private Book book;
private List<Book> course;
private Set<String> achievement;
private Map<String ,String> testMap;
}
一、基本类型可以通过<property标签直接注入
<bean id="Student" class="com.xxp.bean.Student">
<property name="stuName" value="张三"/>
<property name="stuGender" value="男"/>
<property name="stuAge" value="25"/>
</bean>
二、对象类型有两种方式
- 第一种:先把对象交给spring容器 然后引入
<bean id="book" class="com.xxp.bean.Book">
<property name="bookId" value="01"/>
<property name="bookName" value="java"/>
</bean>
<bean id="Student" class="com.xxp.bean.Student">
<property name="book" ref="book"/>
</bean>
- 第二种方式:
<bean id="Student" class="com.xxp.bean.Student">
<property name="book">
<bean class="com.xxp.bean.Book">
<property name="bookId" value="01"/>
<property name="bookName" value="java"/>
</bean>
</property>
</bean>
三、List类型:使用list标签
基本类型赋值:
- 第一种方式:
<bean id="Student" class="com.xxp.bean.Student">
<property name="course">
<list>
<value>1</value>
<value>2</value>
</list>
</property>
</bean>
第二种方式:
<property name="course" value="1,2,3"/>
对象类型赋值:
- 第一种方式:
<bean id="Student" class="com.xxp.bean.Student">
<property name="course">
<list>
<bean class="com.xxp.bean.Book">
<property name="bookId" value="01"/>
</bean>
<bean class="com.xxp.bean.Book">
<property name="bookId" value="02"/>
<property name="bookName" value="mysql"/>
</bean>
</list>
</property>
</bean>
- 第二种方式:
<bean id="book" class="com.xxp.bean.Book"></bean>
<bean id="Student" class="com.xxp.bean.Student">
<property name="course">
<list>
<ref bean="book"></ref>
<ref bean="book"></ref>
</list>
</property>
</bean>
</beans>
四、set类型:与list一致
五、map类型:
<property name="testMap">
<map>
<entry key="a" value="1"/>
<entry key="b" value="2"/>
</map>
</property>
2.1.1.2、构造器注入
基本数据类型可以使用下面方式注入 index是参数的顺序下标,可以不要这个index,但是必须保证参数列表的顺序是正确的
<bean id="Student" class="com.xxp.bean.Student">
<constructor-arg index="0" value="张三"/>
<constructor-arg index="1" value="22"/>
<constructor-arg index="2" value="男"/>
</bean>
对于集合类型采用下面方式注入
<bean id="Student" class="com.xxp.bean.Student">
<constructor-arg index="0" value="张三"/>
<constructor-arg index="1" value="22"/>
<constructor-arg index="2" value="男"/>
<constructor-arg index="3">
<bean class="com.xxp.bean.Book"></bean>
</constructor-arg>
<constructor-arg index="4">
<list>
<bean class="com.xxp.bean.Book"></bean>
<bean class="com.xxp.bean.Book"></bean>
</list>
</constructor-arg>
<constructor-arg index="5">
<set>
<value>1</value>
<value>2</value>
</set>
</constructor-arg>
</bean>
三、Spring-IoC----Bean的作用域
在spring中bean默认是单例模式,bean标签中设置scope=“singleton” 表示该对象为单例模式
如果想使用非单例模式则将值改为scope=“prototype”
spring中默认是饿汉模式spring容器在初始化的时候就会完成对象创建,在bean标签中设置**lazy-init=“true”**则表示开启懒汉模式
3.1、bean声明周期方法
在bean标签中通过init-method="init"配置当前类的初始化方法(该方法在对象创建之后执行)
使用destroy-method="destroy"来配置当前类的销毁方法(该方法在对象销毁之前执行)
配置文件
<bean id="book" class="com.xxp.bean.Book" scope="prototype" init-method="init" destroy-method="destroy"></bean>
实体类
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Book {
private Integer bookId;
private String bookName;
public void init(){
System.out.println("对象创建后执行");
}
public void destroy(){
System.out.println("对象销毁前调用");
}
}
四、Spring-IoC----自动装配
spring可以在实例化bean的时候,从spring容器中找到匹配的实例赋值给当前bean的属性
自动装配有两种方式:
第一种:byName 通过id名装配 缺点:如果id名字一致,但是类型不一致就会装配失败然后抛出异常
第二种:byType 通过类型装配 缺点:如果有多个类型一致的话就会抛出异常
第一种方式:配置autowire=“byName”
<bean id="student" class="com.xxp.bean.Student" autowire="byName"></bean>
第二种方式:配置autowire=“byType”
<bean id="student" class="com.xxp.bean.Student" autowire="byType"></bean>
五、Spring-IoC----注解配置
前面我们可以通过使用xml配置文件的方式将类声明给spring容器管理,从而让spring容器实现对象的创建和属性赋值
我们还可以通过注解的方式将我们的实体类交给spring容器管理,从而简化开发。
5.1、项目部署
5.1.1、引入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.22.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
</dependencies>
5.1.2、创建实体类
package com.xxp.bean;
import lombok.*;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Setter
@Getter
public class Student {
private String name;
private Integer age;
private String sex;
}
5.1.3、创建配置文件
在配置文件中需要使用context:annotation-config/来声明使用注解配置
并且要通过<context:component-scan base-package=“com.xxp.bean”/>来告诉spring工厂注解的扫描范围,从而让spring容器完成初始化
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--声明使用注解配置-->
<context:annotation-config/>
<!--声明Spring工厂注解的扫描范围-->
<context:component-scan base-package="com.xxp.bean"/>
</beans>
六、Spring-IoC----常用注解(注解编程)
6.1.1、@component注解
- 类注解,声明此类被spirng容器管理,相当于bean标签 @component(value=“类名”)相当于bean标签的id属性,也可以不写默认是类名的首字母小写
- 除了@component注解外还有 @Service @Controller@Repository这三个注解也可将类声明到spring容器中,他们主要就是语义上的区别(也就是用的地方不一样)
1.@Service主要用在业务层,比如Service接口的实现类
2.@Controller主要用在控制层,比如servlet
3.@Repository主要用在持久层,比如Dao接口
4.@component主要用在上述中的其他类
实体类
@Component(value = "student")
public class Student {
private String name;
private Integer age;
private String sex;
}
6.1.2、@Scope注解
类注解,在实体类上加上@Scope注解来声明,对象为单例还是多例
单例模式
@Scope(value = "singleton")
public class Student {
private String name;
private Integer age;
private String sex;
}
多例模式
@Scope(value = "prototype")
public class Student {
private String name;
private Integer age;
private String sex;
}
6.1.3、@Lazy注解
类注解,开启饿汉模式或者懒汉模式
@Lazy(value = true)
public class Student {
private String name;
private Integer age;
private String sex;
}
6.1.4、@PostConstruct和 @PreDestroy注解
方法注解,@PostConstruct表示在创建对象之后执行
方法注解, @PreDestroy表示在对象销毁之前执行
@PostConstruct
public void init(){
System.out.println("初始化方法");
}
@PreDestroy
public void destroy(){
System.out.println("销毁方法");
}
6.1.5、@Autowired注解和@Resource注解
属性注解、方法注解,@Autowired注解用于spring在创建对象的时候对对象的属性进行初始化操作
默认是使用的byType根据类型赋值,如果类型不匹配就会报错,可以使用它的required = false属性设置如果类型不匹配就设置当前属性值为null
要是想使用byName类型进行赋值,可以在set方法参数处加上@Qualifier注解
public class Student {
private String name;
private Integer age;
private String sex;
@Autowired(required = false)
private Book book;
}
byName类型
public class Student {
private String name;
private Integer age;
private String sex;
private Book book;
@Autowired(required = false)
public void setBook(@Qualifier Book book){
this.book=book;
}
}
@Resource注解 也是自动装配属性,它是java注解包提供的,并不是spring提供的,它默认是byName如果byName找不到那么会byType,如果根据类型也找不到那么就会抛出异常
public class Student {
private String name;
private Integer age;
private String sex;
private Book book;
@Resource(required = false)
public void setBook(@Qualifier Book book){
this.book=book;
}
}
三、SpringAOP
先学习一下代理模式,代理模式比较抽象,可以选择暂时跳过这一部分,理论上不会影响SpringAOP的学习,当然学会啦更容易理解SpringAOP。
代理模式的优点:将通用性工作交给代理对象处理,被代理对象只需要关注自己的业务代码
比如下面这个例子:
这个你会发现开启事务和关闭事务每个方法都需要使用,造成啦代码重复。
这个时候就可以使用代理模式,把这些代码交给代理对象来完成
代理模式分两种:静态代理和动态代理 下面分别介绍
public class StudentDao {
public void insert(){
System.out.println("开启事务");
System.out.println("insert-------");
System.out.println("提交事务");
}
public void delete(){
System.out.println("开启事务");
System.out.println("delete-------");
System.out.println("提交事务");
}
}
一、SpringAOP----静态代理
静态代理只能为特定的类产生代理对象,不能代理任意类
好处:
1、被代理类只需要关注自己的业务代码的实现,将通用型的管理型逻辑(事务管理、日志管理)和业务逻辑分离。
2、 将通用的代码放在代理类中,提高代码的复用性
3、 通过在代理类中添加业务逻辑实现对原有业务逻辑的增强
1.1、创建一个通用代理方法接口
/**
* @author xxp
*/
public interface GeneralProxy {
void insert();
void delete();
}
1.2、创建StudentDao并实现GeneralProxy接口重写方法
public class StudentDao implements GeneralProxy {
public void insert() {
System.out.println("StudentInsert");
}
public void delete() {
System.out.println("StudentDelete");
}
}
1.3、创建BookDao并实现GeneralProxy接口重写方法
public class BookDao implements GeneralProxy {
public void insert() {
System.out.println("BookInsert");
}
public void delete() {
System.out.println("BookDelete");
}
}
1.4、创建代理类,定义通用的方法,如:开启事务,提交事务
/**
* 静态代理类
* @author xxp
*/
public class MyProxy {
private GeneralProxy generalProxy;
//这个构造方法是重点,它决定代理哪个类
public MyProxy(GeneralProxy generalProxy) {
this.generalProxy = generalProxy;
}
public void insert() {
begin();
generalProxy.insert();
commit();
}
public void delete() {
begin();
generalProxy.delete();
commit();
}
public void begin(){
System.out.println("开启事务");
}
public void commit(){
System.out.println("提交事务");
}
}
1.5、测试类
public class MyTest {
public static void main(String[] args) {
BookDao bookDao = new BookDao();
StudentDao studentDao = new StudentDao();
//这里就实现啦代理,参数可以是bookDao也可以是 studentDao
MyProxy myProxy = new MyProxy(bookDao);
myProxy.delete();
}
}
二、SrpingAOP----动态代理
静态代理有个缺点,就是只能代理实现啦特定接口的类。
动态代理则可以代理几乎所有的类。
动态代理实现的方式有两种:
1.JDK动态代理
2.CGLib动态代理
2.1、JDK动态代理
JDK动态代理是通过被代理对象实现的接口(这也是它的缺点)产生其代理对象的
实现步骤
1.创建一个类,实现InvocationHandler接口,重新invoke方法
2.定义一个Object类型的变量,并提供其有参构造方法,方便被代理对象传递进来
3.定义getProxy方法来获取被代理对象的实现的接口,并为它创建代理对象返回出去(这个过程是通过反射实现的)
/**
* JDK动态代理
* @author xxp
*/
public class JDKDynamicProxy implements InvocationHandler {
//定义一个Object类型的变量,并提供其有参构造方法,方便被代理对象传递进来
private Object object;
public JDKDynamicProxy(Object object) {
this.object = object;
}
public Object getProxy(){
//获取被代理对象的类加载器
ClassLoader loader = object.getClass().getClassLoader();
//获取被代理对象实现的接口
Class<?>[] interfaces = object.getClass().getInterfaces();
//产生代理对象(通过被代理对象的类加载器及实现的接口)
//1、第一个参数 被代理对象的类加载器
//2、第二个参数 被代理对象实现的接口
//3、第三个参数 用于拦截 产生的代理对象 调用的方法
Object proxyInstance = Proxy.newProxyInstance(loader, interfaces, this);
return proxyInstance;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
begin();
Object invoke = method.invoke(object,args);
commit();
return invoke;
}
public void begin(){
System.out.println("开启事务");
}
public void commit(){
System.out.println("关闭事务");
}
}
测试类
public class MyTest {
public static void main(String[] args) {
BookDao bookDao = new BookDao();
StudentDao studentDao = new StudentDao();
//产生代理对象
JDKDynamicProxy jdkDynamicProxy = new JDKDynamicProxy(bookDao);
GeneralProxy proxy = (GeneralProxy) jdkDynamicProxy.getProxy();
//使用代理对象调用方法时候并不会执行方法,而是被拦截啦进入到啦InvocationHandler中的invoke方法
//调用的方法被作为参数传递给啦method
proxy.delete();
}
}
2.2、CGLib动态代理
前面的JDK动态代理有一个缺点:被代理的类必须实现接口
所以这里介绍另一个代理,CGLib动态代理
CGLib动态代理是通过创建被代理类的子类来创建代理对象的,因此没有实现接口的对象也可以创建代理对象
实现步骤:
1.创建一个类,实现MethodInterceptor接口,重写intercept方法
2.定义一个Object类型的变量,并提供其有参构造方法,方便被代理对象传递进来
3.定义getProxy方法来获取被代理对象,通过创建被代理对象的子类来实现
导入依赖
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
代理类
/**
* CGLib‘动态代理
* @author xxp
*/
public class CGLibDynamicProxy implements MethodInterceptor {
private Object object;
public CGLibDynamicProxy(Object object) {
this.object = object;
}
public Object getProxy(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(object.getClass());
enhancer.setCallback(this);
Object proxy = enhancer.create();
return proxy;
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
begin();
Object returnValue = method.invoke(object, objects);
commit();
return returnValue;
}
public void begin(){
System.out.println("开启事务");
}
public void commit(){
System.out.println("关闭事务");
}
}
测试类
/**
* @author xxp
*/
public class MyTest {
public static void main(String[] args) {
BookDao bookDao = new BookDao();
StudentDao studentDao = new StudentDao();
CGLibDynamicProxy cgLibDynamicProxy = new CGLibDynamicProxy(bookDao);
BookDao bookDaoProxy = (BookDao) cgLibDynamicProxy.getProxy();
bookDaoProxy.delete();
}
}
三、SpringAOP
3.1、AOP
AOP面向切面编程,是一种利用“横切的技术”(底层实现就是动态代理),对原有的逻辑进行拦截,并且可以在这个横切面上进行添加特定的业务逻辑,对原有代码进行增强
简单来说就是基于动态代理实现在不改变原有业务的情况下对业务逻辑进行增强。
专业术语:
切点:要增强的方法
切面:增强类
通知:织入切点处(这个通知分好多,后续详细介绍)
下面的例子是对studentDao中的insert和delete方法进行增强
导入依赖
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
定义切面
/**
* 事务切面
* @author xxp
*/
public class TxManager {
public void begin(){
System.out.println("开启事务!");
}
public void commit(){
System.out.println("关闭事务!");
}
}
创建studentDao
/**
* @author xxp
*/
public class StudentDao {
public void insert(){
System.out.println("student添加");
}
public void delete(){
System.out.println("student删除");
}
}
配置文件
<bean id="studentDao" class="com.xxp.dao.StudentDao"></bean>
<!--切面类必须交给spring容器管理-->
<bean id="txManager" class="com.xxp.util.TxManager"></bean>
<aop:config>
<!--声明切点-->
<aop:pointcut id="studentDaoInsert" expression="execution(* com.xxp.dao.StudentDao.delete())"/>
<!--声明切面-->
<aop:aspect ref="txManager">
<!--声明通知-->
<aop:before method="begin" pointcut-ref="studentDaoInsert"/>
<aop:after method="commit" pointcut-ref="studentDaoInsert"/>
</aop:aspect>
</aop:config>
</beans>
测试类
/**
* @author xxp
*/
public class AspetTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
StudentDao studentDao = (StudentDao) context.getBean("studentDao");
studentDao.delete();
}
}
3.2、通知的详解
AOP中通知分为:
前置通知:在指定切点的前面执行
后置通知:在指定切点的后面执行
环绕通知:在指定切点的前后执行
<bean id="studentDao" class="com.xxp.dao.StudentDao"></bean>
<bean id="txManager" class="com.xxp.util.TxManager"></bean>
<aop:config>
<!--声明切点-->
<aop:pointcut id="studentDaoInsert" expression="execution(* com.xxp.dao.StudentDao.delete())"/>
<!--声明切面-->
<aop:aspect ref="txManager">
<!--声明通知-->
<!--前置通知-->
<aop:before method="begin" pointcut-ref="studentDaoInsert"/>
<!--后置通知-->
<aop:after method="commit" pointcut-ref="studentDaoInsert"/>
<!--环绕通知-->
<aop:around method="method5" pointcut-ref="studentDaoInsert"/>
</aop:aspect>
</aop:config>
四、SrpingAop注解实现
@Aspect声明该类为切面类
@Pointcut声明切点
@Before前置通知
@After后置通知
@Around环绕通知
配置文件
<!--开启注解配置-->
<context:annotation-config/>
<!-- 注解扫描的范围-->
<context:component-scan base-package="com.xxp"/>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
切面类
/**
* 事务切面
* @author xxp
*/
@Component
@Aspect
public class TxManager {
@Pointcut("execution(* com.xxp.dao.StudentDao.*(..))")
public void tx() {
}
@Before("tx()")
public void begin(){
System.out.println("开启事务!");
}
@After("tx()")
public void commit(){
System.out.println("关闭事务!");
}
@Around("tx()")
public Object method5(ProceedingJoinPoint point) throws Throwable {
System.out.println("------环绕通知--前------");
Object proceed = point.proceed();
System.out.println("-------环绕通知---后------");
return proceed;
}
}