Spring是什么?由何而来?
Spring是一个开放源代码
的设计底层框架,他解决的是业务逻辑层与其它层之间的耦合度关系,spring是由Rod Johnson在2003年这个获得过悉尼大学的音乐教授
创建而成的。
Spring 是一个轻量级的
、控制反转(IOC)
、面向切面编程(AOP)
的容器框架.
Spring的特点有哪些?
解耦
、便于开发
AOP(Aspect Oriented Programming)编程
声明式事务
开发非入侵式框架
(可以继承其它的框架、而不影响程序正常运行)Spring的组织架构
目前绝大部分企业公司使用的都是spring全家桶一站式开发
:
Spring全家桶:Spring
、Spring Data
、Spring MVC
、Spring Boot
、Spring Cloud
Spring的官方下载地址
Spring官网:Spring官网
Spring官方下载地址:Spring官方下载地址
Spring中必须掌握的核心模块
spring-core
:依赖注入IOC与DI的最基本实现(核心)
spring-beans
:Bean工厂与bean的装配(Bean的工厂模式)
spring-context
:spring的context上下文——IoC容器
spring-expression:spring表达式语言
IOC与DI的区别
IOC(Inversion of Control):字面翻译为控制反转
,是
面向对象编程的一种设计原则
,可以用来降低计算机各个组件之间的耦合度
。
DI(Dependency Injection):字面翻译为依赖注入
,是
为了实现IOC
这种设计原则的一种实现策略
,对于我们来说就是创建对象实例过程中
,同时给
这个对象注入它依赖的属性
。
快速使用Spring
步骤1:通过Maven工程中的pom.xml文件添加jar包
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>5.3.9version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.9version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-beansartifactId>
<version>5.3.9version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-context-supportartifactId>
<version>5.3.9version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-expressionartifactId>
<version>5.3.9version>
dependency>
或者直接导入spring-webmvc这个包也是一样可以的
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.3.9version>
dependency>
步骤2:在resources文件夹目录下创建Spring的配置文件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">
beans>
步骤3:在配置文件中装载对象bean
<bean id="对象名" class="类的完整路径">
<property name="属性名" ref="对象的id值"/>
bean>
<bean id="userDao" class="com.alascanfu.dao.impl.UsersDaoImpl">bean>
<bean id="userService" class="com.alascanfu.service.impl.UsersServiceImpl">
<property name="usersDaoImpl" ref="userDao"/>
bean>
步骤4:通过ClassPathXmlApplicationContext
来加载配置文件,获得上下文以此来获取对象。
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userServiceImpl");
userService.getUser();
}
}
bean中出现的标签以及属性介绍
Spring中创建对象的方式
当注入属性为对象赋值时,对象类型需要进行引用ref,非对象类型选择value
User.class
package com.alascanfu.pojo;
public class User {
private String name ;
public User() {
}
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
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">
<bean id="user1" class="com.alascanfu.pojo.User">
bean>
<bean id="user2" class="com.alascanfu.pojo.User">
<constructor-arg index="0" value="Alascanfu"/>
bean>
<bean id="user3" class="com.alascanfu.pojo.User">
<constructor-arg name="name" value="Alascanfu"/>
bean>
<bean id="user4" class="com.alascanfu.pojo.User">
<constructor-arg type="java.lang.String" value="Alascanfu"/>
bean>
beans>
StaticFactory.java
package com.alascanfu.pojo;
public class StaticFactory {
public static User createUserByStatic(){
return new User();
}
}
Test.java
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user5 = (User) context.getBean("user5");
System.out.println(user5);
}
}
返回结果:
静态工厂模式创建Bean
User{name='null'}
Process finished with exit code 0
DynamicFactory.java
package com.alascanfu.pojo;
public class DynamicFactory {
public User createUserByDynamic(){
System.out.println("动态工厂模式创建Bean");
return new User();
}
}
applicationContext.xml
<bean id="dynamicFactory" class="com.alascanfu.pojo.DynamicFactory"/>
<bean id="user6" class="com.alascanfu.pojo.DynamicFactory" factory-method="createUserByDynamic" factory-bean="dynamicFactory"/>
Test.java
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user6 = (User) context.getBean("user6");
System.out.println(user6);
}
}
运行结果:
动态工厂模式创建Bean
User{name='null'}
Process finished with exit code 0
Spring的生命周期
Bean 生命周期的整个执行过程描述如下:
1) 根据配置情况调用 Bean 构造方法或工厂方法实例化 Bean。
2) 利用依赖注入完成 Bean 中所有属性值的配置注入。
3) 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入
当前Bean 的 id 值。
4) 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。
5) 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
6
) 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的预初始化方法postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。
7) 如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。初始化bean的时候执行,可以针对某个具体的bean进行配置。afterPropertiesSet 必须实现 InitializingBean接口。实现 InitializingBean接口必须实现afterPropertiesSet方法。
8) 如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
9) 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。
10) 如果指定了该 Bean 的作用范围为 scope=“singleton”,则将该 Bean 放入 Spring IoC 的缓存池中,将触发 Spring 对该 Bean 的生命周期管理;如果指定了该 Bean 的作用范围为scope=“prototype”,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该Bean。
11) 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法将 Spring 中的 Bean销毁;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。
这里是摘自小王曾是少年
的笔记 膜拜大佬!地址链接: Spring基础学习笔记
小伙子,你知道在Spring框架中,Bean组件的生命周期是什么样子的么?来聊一聊你的看法。
(小付假装挠了下脑阔~)说道:
bean的生命周期一般分为三个大的部分
:
一、bean创建过程:
步骤1:调用bean的
构造方法
创建bean。
步骤2:通过
反射调用setter方法
进行属性的依赖注入
。
步骤3:如果
实现了BeanNameAware接口
的话,则Spring会调用bean的setBeanName()方法
,传入
Bean当前的id值
。
public interface BeanNameAware extends Aware {
void setBeanName(String var1);
}
步骤4:如果
实现了BeanFactoryAware接口
的话,Spring会调用setBeanFactory()方法
,传入当前工厂实例引用。
public interface BeanFactoryAware extends Aware {
void setBeanFactory(BeanFactory var1) throws BeansException;
}
步骤5:如果实现了
ApplicationContextAware接口
,Spring会调用setApplicationContext() 方法
,传入当前 ApplicationContext 实例
的引用。
public interface ApplicationContextAware extends Aware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
步骤6:如果
实现了BeanPostProcessor接口
,Spring会先调用预初始化方法postProcessBeforeInitialzation()
对 Bean 进行加工操作,执行前置处理方法,此处非常重要,Spring 的 AOP 就是利用它实现的。
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
步骤7:如果
实现了InitializingBean接口
,Spring则会调用afterPropertiesSet()
方法针对具体的Bean进行配置。
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
步骤8:执行自定义init方法 配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
步骤9:如果
实现了BeanPostProcessor接口
,Spring会先调用预初始化方法postProcessAfterInitialzation()
对 Bean 进行加工完成操作。此时,就完成了bean的创建过程
二、bean的使用过程
三、bean的销毁过程
步骤1:如果 Bean
实现了 DisposableBean 接口
,则 Spring 会调用destory() 方法
将 Spring 中的 Bean销毁。
步骤2:自定义的destory方法,在配置文件中通过
destory-method
属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。
singleton
:单例模式
prototype
:原型模式request:对于每次 HTTP 请求,使用 request 定义的 bean 都将产生一个新实例,即每次 HTTP 请求将会产生不同的 bean 实例。
session
:同一个 Session 共享一个 bean 实例。global-session:同 session 作用域不同的是,所有的Session共享一个Bean实例。
DI(Dependency Injection)是IOC这种设计思想的具体实现的一种策略。
主要有两种方式为属性注入赋值。
调取属性的set()方法
进行属性赋值
。构造器
来对属性赋值
初始化一个User
类:
public class User {
private Long id;
private String name ;
private Address address;
public User() {
}
public User(Long id, String name,Address address) {
this.name = name;
this.id = id;
this.address = address;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", address=" + address +
'}';
}
}
初始化一个引用类Address
public class Address {
private String province;
private String city;
private String road;
public Address() {
}
public Address(String province, String city, String road) {
this.province = province;
this.city = city;
this.road = road;
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getRoad() {
return road;
}
public void setRoad(String road) {
this.road = road;
}
@Override
public String toString() {
return "Address{" +
"province='" + province + '\'' +
", city='" + city + '\'' +
", road='" + road + '\'' +
'}';
}
}
基本属性类型值注入
<bean class="com.alascanfu.pojo.Address" id="address">
<property name="province" value="Si Chuan"/>
<property name="city" value="Cheng Du"/>
<property name="road" value="TianFu Road"/>
bean>
引用属性类型值注入
<bean class="com.alascanfu.pojo.User" id="user2">
<property name="id" value="201901094106"/>
<property name="name" value="Alascanfu"/>
<property name="address" ref="address"/>
bean>
通过name属性,按照参数名 赋值
<bean class="com.alascanfu.pojo.User" id="user2">
<constructor-arg name="id" value="201901094106"/>
<constructor-arg name="name" value="Alascanfu"/>
<constructor-arg name="address" ref="address"/>
bean>
通过index属性,按照参数索引位置 赋值
<bean class="com.alascanfu.pojo.User" id="user2">
<constructor-arg name="id" value="201901094106" index="0"/>
<constructor-arg name="name" value="Alascanfu" index="1"/>
<constructor-arg name="address" ref="address" index="2"/>
bean>
通过type属性,按照参数属性 赋值
<bean class="com.alascanfu.pojo.User" id="user2">
<constructor-arg type="java.lang.Long" value="201901094106" index="0"/>
<constructor-arg type="java.lang.String" value="Alascanfu" index="1"/>
<constructor-arg type="com.alascanfu.pojo.Address" ref="address" index="2"/>
bean>
使用p命名空间:属性名 完成注入,通过set()方法完成注入
基本类型
: p:属性名=“值”
引用类型
:p:属性名-ref=“bean的id”
步骤1:导入名称空间到applicationContext中
xmlns:p="http://www.springframework.org/schema/p"
步骤2:通过上述类型进行注入 赋值
基本类型:
<bean class="com.alascanfu.pojo.Address" id="address" p:province="Si Chuan" p:city="Cheng Du" p:road="TianFu Road"/>
引用类型:
<bean class="com.alascanfu.pojo.User" p:id="201901094106" p:name="Alascanfu" p:address-ref="address"/>
步骤1:导入名称空间到applicationContext中
xmlns:c="http://www.springframework.org/schema/c"
步骤2:通过上述类型进行注入 赋值
<bean class="com.alascanfu.pojo.User" c:id="201901094106" c:name="Alascanfu" c:address-ref="address" />
数组、列表、集合、map、Properties
等复杂类型注入
<bean id="u1" class="com.alascanfu.pojo.Users">bean>
<bean id="t1" class="com.alascanfu.pojo.Teacher">
<property name="objects">
<list>
<value>小付1value>
<value>123value>
<value>abcvalue>
<ref bean="u1">ref>
list>
property>
<property name="list">
<list>
<value>小付2value>
<value>1234value>
<value>abcdvalue>
<ref bean="u1">ref>
list>
property>
<property name="set">
<set>
<value>小付3value>
<value>12345value>
<value>abcdevalue>
<ref bean="u1">ref>
set>
property>
<property name="map">
<map>
<entry key="班长" value="康康">entry>
<entry key="团支书" value="术达">entry>
<entry key="user" value-ref="u1">entry>
map>
property>
<property name="properties">
<props>
<prop key="username">rootprop>
<prop key="password">rootprop>
props>
property>
bean>
autowire:
no 不自动装配(默认值)
byName 属性名=id名 ,调取set方法赋值
byType 属性的类型和id对象的类型相同
,当找到多个同类型的对象时报错,调取set方法赋值
constructor 构造方法的参数类型和id对象的类型相同
,当没有找到时,报错。调取构造方法赋值
步骤1:向配置文件中添加context上下文命名空间以及约束配置
<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>
步骤2:配置注解扫描:指定扫描包下所有类中的注解,扫描包时,会扫描所有的子孙包
<context:component-scan base-package="com.alascanfu"/>
步骤3:在合适的地方添加适合的注解
添加在类上
@Component("对象名")
@Service("person") // service层
@Controller("person") // controller层
@Repository("person") // dao层
@Scope(scopeName="singleton") //单例对象
@Scope(scopeName="prototype") //多例对象
添加在属性上
User.java
@Componet
public class User {
@Value("201901094106")
private Long id;
@Value("Alascanfu")
private String name ;
@Autowired
@Qualifier("address")
private Address address;
}
Address.java
@Component("address")
public class Address {
@Value("四川")
private String province;
@Value("成都")
private String city;
@Value("天府新区")
private String road;
}
Test.java
public class Test {
@org.junit.Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = context.getBean("user", User.class);
System.out.println(user.toString());
}
}
执行结果
User{id=201901094106, name='Alascanfu', address=Address{province='四川', city='成都', road='天府新区'}}
添加在方法上:
@PostConstruct //等价于init-method属性
public void init(){
System.out.println("自定义初始化方法");
}
@PreDestroy //等价于destroy-method属性
public void destroy(){
System.out.println("自定义销毁方法");
}
开发常见使用
在Dao实现类上添加注解:
@Repository("userDaoImpl")
public class UserDaoImpl implements UserDao{
public void add() {
System.out.println("添加数据中...");
}
}
在Service层实现类添加注解
@Service("userServiceImpl")
public class UserServiceImpl implements UserService{
@Autowired
private UserDao userDao;
public void add() {
userDao.add();
}
}
注意事项:
当使用@Autowired进行注入属性时,默认是byType
,如果有另一个dao层的实现类,也配置了@Repository("userDaoImpl2")的话就会报错
,此时就要用到@Qualifier
@Autowired // 默认使用byType
@Qualifier("userDaoImpl")
private UsersDao usersDao;
或者使用@Resource来代替他们两个
@Autowired // 默认使用byType
@Qualifier("userDaoImpl")
private UsersDao usersDao;
Test.java
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UsersService userService = (UsersService)context.getBean("userServiceImpl");
userService.add();
}
添加在类上,非单例模式,利用原型模式创建。
User.java
@Component("user")
@Scope(scopeName = "prototype")
public class User {
@Value("201901094106")
private Long id;
@Value("Alascanfu")
private String name ;
@Autowired
@Qualifier("address")
private Address address;
public User() {
}
}
Test.java
public class Test {
@org.junit.Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user1 = context.getBean("user", User.class);
User user2 = context.getBean("user", User.class);
System.out.println(user1.hashCode() == user2.hashCode());//false
}
}
使用@Value注解指定属性为默认值
public class User {
@Value("201901094106")
private Long id;
@Value("Alascanfu")
private String name ;
@Autowired
@Qualifier("address")
private Address address;
public User() {
}
}
通常我们在开发的时候是注解与配置文件共同配置,那你能聊聊@Autowired与@Resource的区别么?
相同点:二者均可以写在字段和setter方法上
不同点:
@Auitowired
其是
按照类型
来装配对象的,默认情况下
,他要求依赖的对象必须存在
,如果允许了null值,可以设置他的required属性为false
。如果我们想使用按照名称来装配,就需要结合@Qualifier注解一起使用
:
@Service
public class UserServiceImpl implements UserService{
@Autowired
@Qualifier("userDaoImpl")
private UserDao userDao;
public void add() {
userDao.add();
}
}
@Resource
其
默认是按照名称
来进行自动注入的,有两个重要的属性name
和type
,Spring会将@Resource 注解的name属性解析为bean的名字,而type属性则解析为bean的类型。如果使用了name属性,则会使用名称的自动注入策略,而是用了type属性时则会使用
按照类型进行自动注入策略
,如果什么都不设置其会通过反射来按照名称自动注入
。
@Service
public class UserServiceImpl implements UserService{
@Resource(name = "userDaoImpl")
private UserDao userDao;
public void add() {
userDao.add();
}
}
什么是AOP?
AOP(Aspect Oriented Programming) 面向切面编程,在不改变原来程序的基础上可以为代码增加新的功能
。应用在权限认证、日志、事务
。
AOP的作用在于分离系统中的各种关注点
,将核心关注点和横切关注点分离开来
。
AOP的实现原理与机制
InvocationHandler接口。
MethodInterceptor接口。
这里用到了面向对象编程的23种设计模式中的——代理模式
。
如果还不知道代理模式是什么请看小付的专栏:
《(1条消息) Java23种设计模式一栏拿捏_Alascanfu的博客-CSDN博客》
ProxyUtils.java
public class ProxyUtil implements InvocationHandler {
//需要代理的对象
public Object target;
//返回代理对象
public Object getProxy(Object target){
this.target = target;
System.out.println("利用动态工厂创建对象,返回实例对象的方法");
//这里返回调用的是静态类Proxy的newProxyInstance方法
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
}
//在方法执行的前后进行代理
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target,args);
}
}
这是JDK自带的动态代理方法的通用实现
在调用接口方法的前后都会添加代理类的方法
public class Test {
@org.junit.Test
public void test(){
ProxyUtil proxyUtil = new ProxyUtil();
UserService userService = (UserService) proxyUtil.getProxy(new UserServiceImpl());
userService.add();
}
}
注意事项:
这里获得得到的代理对象不能强转为类实例,只能转换为接口调用方法
,否则会报错
,代理对象无法进行转换。
newProxyInstance(ClassLoader loader,Class [] interfaces,InvocarionHandler h)
中看的很清楚
JDK动态代理技术显然已经黔驴技穷
,CGLib作为一个替代者,填补了这一空缺
.字节码技术
,可以为一个类创建子类
,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑.如何进行代理呢?
CGlibProxy.java
public class CglibProxy implements MethodInterceptor {
public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//参数:Object为由CGLib动态生成的代理类实例,Method为上文中实体类所调用的被代理的方法
//引用,Object[]为参数值列表,MethodProxy为生成的代理类对方法的代理引用。
Object obj = methodProxy.invokeSuper(target,args);
return obj;
}
}
Test.java
public class Test {
@org.junit.Test
public void test(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(User.class);
enhancer.setCallback(new CglibProxy());
User user = (User) enhancer.create();
System.out.println(user.toString());
}
}
Spring同时使用了这两种方式,可以通过配置文件选择代理模式。
<aop:aspectj-autoproxy proxy-target-class="false"/>
默认为false,是调用的JDK的代理模式进行创建的。
当将其改为true时,则是通过CGlib通过字节码技术,代理创建其对象实例。
1、JDK动态 代理生成的代理类和委托类实现了相同的接口
。
2、CGlib动态代理中生成了的字节码更加的复杂
,生成的代理类是委托类的子类,且不能被final进行修饰
3、JDK动态代理
采用的是反射机制调用委托类
的方法,CGlib
采用类似索引的方式直接调用委托类方法
。
步骤1:添加需要使用AOP相关的jar包
<dependencies>
<dependency>
<groupId>aopalliancegroupId>
<artifactId>aopallianceartifactId>
<version>1.0version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.8.13version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
<version>5.3.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>5.3.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>5.3.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-beansartifactId>
<version>5.3.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-context-supportartifactId>
<version>5.3.9.RELEASEversion>
dependency>
dependencies>
步骤2:添加原有的功能方法
步骤3:配置文件中的aop名称空间以及相关约束
<beans
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<aop:aspectj-autoproxy proxy-target-class="false"/>
beans>
步骤4:创建增强类,对之前的功能方法进行切面,切入通知
AOPConfig.java
@Aspect
public class AOPConfig {
public void before(){
System.out.println("前置通知");
}
public void afterReturning(){
System.out.println("后置通知");
}
public void after(){
System.out.println("最终通知");
}
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("环绕之前");
Object proceed = point.proceed();
System.out.println("环绕之后");
return proceed;
}
public void afterThrowing(){
System.out.println("异常通知");
}
}
配置文件中进行配置通知
<bean id="aopConfig" class="com.alascanfu.config.AOPConfig"/>
<aop:config>
<aop:pointcut id="p" expression="execution(* com.alascanfu.service.UserServiceImpl.*(..))"/>
<aop:aspect ref="aopConfig">
<aop:after method="after" pointcut-ref="p"/>
<aop:before method="before" pointcut-ref="p"/>
<aop:after-returning method="afterReturning" pointcut-ref="p"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="p"/>
<aop:around method="around" pointcut-ref="p"/>
aop:aspect>
aop:config>
第一步:将写好的增加类加入到IOC容器当中,随后配置aop的相关设置
第二步:配置好aop:pointcut作为需要切入功能的位置
第三步:配置将增强类植入目标对象
Test.java
public class Test {
@org.junit.Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userServiceImpl", UserService.class);
userService.add();
}
}
前置通知
环绕之前
添加数据中...
环绕之后
后置通知
最终通知
Process finished with exit code 0
通过向通知的方法中添加JoinPoint参数获取对象的信息:
System.out.println("切入点对象:"+point.getTarget().getClass().getSimpleName());
System.out.println("切入点方法:"+point.getSignature());
System.out.println("切入点的参数:"+point.getArgs()[0]);
前置通知
环绕之前
切入点对象:UserServiceImpl
切入点方法:void com.alascanfu.service.UserService.add()
后置通知
最终通知
用于只有前置
,而且必须是前置的情况
。事务管理中的开启事务。
如何实现?
步骤1:创建特殊的前置增强类,要求其需要实现MethodBeforeAdvice接口。
步骤2:修改配置文件
创建增强类对象
定义增强通知和切入点之间的联系
public class BeforeLog implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("MethodBeforeAdvice方法执行前...");
System.out.println("特殊的前置增强");
System.out.println("目标方法:" + method.getName());
System.out.println("参数:" + args);
System.out.println("对象:" + target);
}
}
<bean id="bflog" class="com.alascanfu.log.BeforeLog"/>
<aop:config>
<aop:pointcut id="point" expression="execution(* com.alascanfu.service.UserServiceImpl.*(..))"/>
<aop:advisor advice-ref="bflog" pointcut-ref="point"/>
aop:config>
注意:
增强类也需要添加到IOC容器当中才可以进行注解开发
哦 <context:component-scan base-package="com.alascanfu"/>
<context:annotation-config/>
<aop:aspectj-autoproxy proxy-target-class="false"/>
除了启动spring的注解之外 还要启动aspectJ的注解方式
在切面类(增强类)上添加:@Aspect
定义一个任意方法
在其方法上添加@Pointcut作为切入点
@Aspect
public class AOPConfig {
@Before("execution(* com.alascanfu.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("前置通知");
}
@AfterReturning("execution(* com.alascanfu.service.UserServiceImpl.*(..))")
public void afterReturning(){
System.out.println("后置通知");
}
@After("execution(* com.alascanfu.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("最终通知");
}
@Around("execution(* com.alascanfu.service.UserServiceImpl.*(..))")
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("环绕之前");
Object proceed = point.proceed();
System.out.println("环绕之后");
return proceed;
}
@AfterThrowing("execution(* com.alascanfu.service.UserServiceImpl.*(..))")
public void afterThrowing(){
System.out.println("异常通知");
}
}
注解方式中注解执行顺序:
没有异常存在的情况下:
有异常的情况下:
AOP的应用场景:事务底层实现,日志,权限的控制,mybatis中sql绑定,性能的监测等等…
中文官方文档:
mybatis-spring 中文官方文档
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合
到 Spring 中
。它将允许 MyBatis 参与到 Spring 的事务管理
之中,创建映射器 mapper
和 SqlSession
并注入到 bean
中,以及将 Mybatis 的异常
转换为 Spring 的 DataAccessException
。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。
步骤1:导入mybatis-spring 的 jar包 与 spring-jdbc 的jar包
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>2.0.6version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>5.3.9version>
dependency>
步骤2:构建测试的用户类以及用户接口
User.java
public class User {
private int id ;
private String username ;
private List<Role> userList ;
public User() {
}
public User(int id, String username, List<Role> userList) {
this.id = id;
this.username = username;
this.userList = userList;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public List<Role> getUserList() {
return userList;
}
public void setUserList(List<Role> userList) {
this.userList = userList;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", userList=" + userList +
'}';
}
}
UserMapper.java
public interface UserMapper {
User getUserById(int id);
}
UserMapperImpl.java
@Repository("userMapper")
public class UserMapperImpl implements UserMapper{
@Autowired
private SqlSessionFactory sqlSessionFactory;
public User getUserById(int id) {
SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
List<User> user = sqlSessionTemplate.selectList("com.alascanfu.pojo.mapper.UserMapper.getUserById",id);
return user.get(0) == null ? null : user.get(0);
}
}
UserMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.alascanfu.pojo.mapper.UserMapper">
<cache readOnly="true" eviction="FIFO" size="512" flushInterval="60000"/>
<resultMap id="userById" type="User">
<id column="id" property="id"/>
<result property="username" column="username"/>
<collection property="userList" ofType="Role">
<result property="rid" column="id"/>
<result property="name" column="name"/>
<result property="nameZh" column="nameZh"/>
collection>
resultMap>
<select id="getUserById" resultMap="userById">
SELECT u.id,u.username,r.name ,r.nameZh,r.id from user u ,role r , user_role ur where r.id = ur.rid and ur.uid = u.id and u.id = #{id}
select>
mapper>
数据库初始数据
mysql> select * from user;
+----+-----------+
| id | username |
+----+-----------+
| 1 | Alascanfu |
| 2 | HHXF |
+----+-----------+
2 rows in set (0.00 sec)
mysql> select * from role;
+----+-------+--------------------+
| id | name | nameZh |
+----+-------+--------------------+
| 1 | admin | 系统管理员 |
| 2 | DBA | 数据库管理员 |
| 3 | user | 普通用户 |
+----+-------+--------------------+
3 rows in set (0.00 sec)
mysql> select * from user_role;
+----+-----+-----+
| id | uid | rid |
+----+-----+-----+
| 1 | 1 | 3 |
| 2 | 1 | 1 |
| 3 | 2 | 1 |
| 4 | 2 | 2 |
| 5 | 1 | 2 |
+----+-----+-----+
5 rows in set (0.00 sec)
步骤3:配置初始Mybatis必要设置环境到Spring的IOC容器当中
在之前学过Mybatis的小伙伴们都知道Mybatis的工作流程以及原理,这里就不再赘述了。
如有疑问可以去看看博主之前写的文章:
Mybatis入门到入坟 一站式基础及进阶——囊括面试点与初学基础、框架分析——从0到1 不会私我 我手把手教你
这里主要说一下
通过SqlSession这个会话
连接来完成。SqlSessionFactory
这个工厂模式创建
的,但是这并非是线程安全的,同一个工厂可以在同一时间创建多个SqlSession进行连接
,操作数据库,所以这里也要考虑到线程安全
的问题。SqlSessionFactory
这个工厂是由实现了23种设计模式中的建造者模式
的SqlSesssionFactoryBuilder
这个类来build()
出来的。所以就需要将必要的环境配置到IOC容器当中
比如说:一个 SqlSessionFactory
和至少一个数据映射器类(Mapper)。
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/alascanfu/pojo/mapper/*.xml"/>
bean>
将SqlSession一并注入到IOC容器当中
注意:因为SqlSessionTemplate这个类没有set()方法所以只能通过构造器来进行注入了
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
bean>
因为此时的这个配置文件是属于spring-mybatis的一个整合的spring的配置文件可以将其拖出来单独配置然后再导入总的spring配置文件当中
mybatis-config.xml
文件中无需要进行额外的配置
,博主比较喜欢将setting与typealiases
写在当中 ,其余的一律交给spring-mybatis.xml
的配置文件中处理。所以最后的配置文件如下
mybatis-config.xml
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="logImpl" value="LOG4J"/>
settings>
<typeAliases>
<package name="com.alascanfu.pojo"/>
typeAliases>
configuration>
spring-mybatis
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="username" value="root"/>
<property name="password" value="fujiawei2013"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"/>
bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/alascanfu/pojo/mapper/*.xml"/>
bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
bean>
beans>
步骤4:进行测试
public class TestForSpringAndMyBatis {
@Test
public void testGetUserById(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
System.out.println(userMapper.getUserById(1).toString());
}
}
执行结果
User{id=1, username='Alascanfu', userList=[Role{name='user', nameZh='普通用户'}, Role{name='admin', nameZh='系统管理员'}, Role{name='DBA', nameZh='数据库管理员'}]}
Process finished with exit code 0
首先我们还是要知其原理才能做出改进方案:
Spring整合Mybatis
最重要的是什么? 将Mybatis的必要配置注入到IOC容器当中,也就是我们常用的SqlSessionFactory
那这一步就是必不可少的,所以这一处就不能进行改进简化了。
之后你会发现我们每次使用Mybatis查询数据库时都要写一个Mapper接口的实现类,让其中调用SqlSessionTemplate
去进行操作数据库,这无疑增加了代码量的同时,造成了内存冗余。
解决方案
正因为出现了上述的问题,为了避免手动编写SqlSessionTemplate操作数据库的Mapper实现类,spring-mybatis为我们提供了一个叫做MapperScannerConfigurer这个类。
MapperScannerConfigurer
:它 将 会 查 找类 路 径 下 的 映 射 器
并 自 动 将 它 们 创 建 成 MapperFactoryBean
。没 有 必 要 去 指 定 SqlSessionFactory 或 SqlSessionTemplate
, 因 为 MapperScannerConfigurer 将会创建 MapperFactoryBean
,之后自动装配
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.alascanfu.pojo.mapper"/>
bean>
那么为什么mapper接口可以直接调用,而无需实现类呢?
MapperFactoryBean
因为代替手工使用 SqlSessionDaoSupport
或SqlSessionTemplate
编写数据访问对象 (DAO)的代码,MyBatis-Spring 提供了一个动态代理的实现
:MapperFactoryBean
。这个类 可以直接注入数据映射器接口到你的 service 层 bean 中
。当使用映射器时,你仅仅如调 用你的 DAO 一样调用它们就可以了,但是你不需要编写任何 DAO 实现的代码
,因为 MyBatis-Spring 将会为你创建代理
。
事务(Transaction)是什么?
学过MySQL的同学都应该知道在数据库服务器中也有事务管理,比如操作数据库开启事务start transaction
、提交事务commit
、回滚rollback
等等操作都是事务管理的方式之一。
事务控制的工作流程一般分为如下几步:
举个:
如果小付想通过网上购物去购买商品时,其主要步骤操作如下:
1、首先服务器会更新库存信息,查看是否还有库存完成交易
2、然后获取小付同志的交易订单信息
3、生成订单信息保存到数据库
4、更新用户的已购买信息等等
5、上述都顺利的话提交事务,等待下一次订单。
MySQL数据库中定义的四种隔离级别:
读未提交
:read uncommitted读已提交
:read committed可重复读
:repeatable read串行化
:serializable我们必须先了解一下Spring中的事务定义
TransactionDefinition.java 源码分析
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0; //事务的传播行为Required 支持事务 不存在 就新建
int PROPAGATION_SUPPORTS = 1;//事务的传播行为Support支持事务 不存在 就不用
int PROPAGATION_MANDATORY = 2;//事务的传播行为MANDATORY 支持事务 不存在 就抛错
int PROPAGATION_REQUIRES_NEW = 3;//如果事物存在 挂起当前 新建事务
int PROPAGATION_NOT_SUPPORTED = 4;//不使用事务运行 如果存在事务 挂起事务执行
int PROPAGATION_NEVER = 5;// 不使用事务运行 如果存在事务 抛错
int PROPAGATION_NESTED = 6;// 事务存在就嵌套使用
int ISOLATION_DEFAULT = -1; //默认的隔离级别
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
int TIMEOUT_DEFAULT = -1; //默认超时时间 默认不超时
//获取当前事务传播方式
default int getPropagationBehavior() {
return 0;
}
//获取当前事务的隔离级别
default int getIsolationLevel() {
return -1;
}
//获取超时时间
default int getTimeout() {
return -1;
}
//是否只读
default boolean isReadOnly() {
return false;
}
@Nullable
default String getName() {
return null;
}
}
了解Spring中的事务定义后就来进行配置
步骤1:在applicationContext.xml文件中配置命名空间以及约束条件
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
">
beans>
步骤2:在Spring-Mybatis.xml文件中配置事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg name="dataSource" ref="dataSource"/>
bean>
这里调用的是DataSourceTransactionManager用于操作对JDBC的数据库进行的事务管理。
步骤3:配置事务通知(通过AOP方式)也被称之为声明式事务
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg name="dataSource" ref="dataSource"/>
bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="getUserById" propagation="REQUIRED"/>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.alascanfu.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>aop:config>
上述的tx:method中的属性都是在Spring中的事务定义哦~
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.8.M1version>
dependency>
最后整合的spring-mybatis.xml
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
">
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${driverClass}"/>
<property name="url" value="${url}"/>
<property name="username" value="root"/>
<property name="password" value="${password}"/>
bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/alascanfu/pojo/mapper/*.xml"/>
bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.alascanfu.pojo.mapper"/>
bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg name="dataSource" ref="dataSource"/>
bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="getUserById" propagation="REQUIRED" />
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.alascanfu.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
aop:config>
beans>
步骤1:配置xml文件开启事务注解
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg name="dataSource" ref="dataSource"/>
bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
步骤2:将@Transactional注解添加到服务层动态代理方式面向切面插入通知
@Transactional //对业务类进行事务增强的标注
@Service("userService")
public class userServiceImpl implements UserService {}
步骤3:对需要的事务属性进行配置
@Transactional 注解源码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
String[] label() default {};
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
String timeoutString() default "";
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
还是和Spring事务定义有关 可以逐一进行配置
@Transacation可以标注在哪里呢?
@Transactional注解可以直接用于接口定义和接口方法
,类定义和类的public方法上
.但Spring建议在业务实现类
(Service层)上使用@Transactional注解,当然也可以添加到业务接口
上,但是这样会留下一些容易被忽视的隐患,因为注解不能被继承,所以业务接口中标注的@Transactional
注解不会被业务类实现继承.