文章目录
Spring
1 Spring框架概述
1.1 什么是Spring
- Spring是一款优秀的轻量级Java开发的开源企业级框架
- Spring出现之后,大大简化了企业级开发,提高了开发效率。
- 相比于其他框架的专注于某一个领域,(例如MyBatis专注于数据库操作,),Spring则是一个保罗万象的框架。
- Spring是一个管理框架的框架,尽管Spring包罗万象,但它又不具体做任何事情,Spring奉行一个“不重复制造轮子”的原则,如已经存在了MyBatis这样的持久化框架,Spring就不会重复的开发一个持久化框架,而是使用MyBatis等持久化框架,实现持久化功能,Spring是通过集成其他框架实现各领域的管理和协作。
- Spring通过管理和整合软件开发中使用到的各种组件和框架,实现各组件和框架的搭配组合使用,并让各组件和框架的使用更简洁、更方便,Spring在整合其他框架的过程中,将一些通用的功能、各组件间的依赖关系自动管理起来,让开发者从这些繁琐重复的代码中解脱出来,从而实现“优雅编程”(开发者只需编写个性化和业务相关的代码),让开发变得更简单、更方便。
- spring采用面向接口编程
Spring的作用
①对系统中各个组件进行有效管理让其能协调工作
②使各个组件之间的耦合度降低,提高各个组件的可维护性(高内聚低耦合)
1.2 Spring的核心技术
IOC(控制反转)和AOP(面向切面编程)
-
IOC:称为控制反转,将对象的创建及对象间依赖关系的维护权由对象自身交给外部容器管理,这种管理权的转移我们称为控制反转,实现各组件间的解耦;DI(依赖注入)实现IOC
-
AOP:面向切面(方面)编程, 将系统中多处都要使用的通用功能,从各个部分分离出来,单独开发和维护,不再各个部分代码中调用。而是在系统运行时由外部容器将这些功能嵌入到各部分代码中。(过滤器、拦截器都属于面向切面思想)
1.3 Spring体系结构
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式 .
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
- 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
- Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
- Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
- Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
- Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
- Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
- Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
2 Spring基于xml的简单使用
基于xml实现(不建议),在学习注解开发之前我们都使用xml实现
-
Maven中引入spring-context依赖包
<!--spring--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.3</version> </dependency>
-
编写spring的核心配置文件applaction.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 https://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
-
创建相应的类
public class UserServiceImpl implements UserService { @Override public void addUser(User user) { System.out.println("执行addUser"); } }
-
在applaction.xml中配置bean标签,将指定类交给Spring管理
类对象由spring创建
-
bean:bean标签用于配置spring的受管对象
-
id:指定对象名
-
class:指定哪个类交给spring管理
-
配置后spring会创建class对应的类的对象,对象名为id的值
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <!--Spring核心配置文件--> <!-- 配置类对应的bean标签 bean:bean标签用于配置spring的受管对象 id:指定对象名 class:指定哪个类交给spring管理 配置后spring会创建class对应的类的对象,对象名为id的值 --> <bean id="userService" class="com.jiazhong.spring01.service.impl.UserServiceImpl"> </bean> </beans>
-
-
使用Spring中的受管对象
- 初始化Spring容器对象
- 从Spring中获取一个对象(根据对象名获取)
@Test public void addUser(){ //初始化spring容器对象,此时spring容器会解析application.xml中的内容 ApplicationContext context=new ClassPathXmlApplicationContext("application.xml"); //从容器中获取对象 UserService userService=(UserService) context.getBean("userService"); userService.addUser(new User()); }
3 Spring的依赖注入
Spring是IOC框架,所依赖的对象由Spring进行管理,Spring通过依赖注入的方式将对象所需要的属性注入到对象中
- Spring进行依赖注入的方式有两种:
- set注入:通过Set方法为对象的属性赋值
- 构造器注入:通过构造器为属性赋值
3.1 Set注入(重点)
要求属性必须有Set方法
使用
<property>
标签注入
<name>
:userMapper中的属性
<ref>
:对应的值必须为spring受管对象
<value>
:注入的具体的值
- 注入数组用
<array>
- 注入map集合用
<map>
- 注入list结合用
<list>
<bean id="userService" class="com.jiazhong.spring01.service.impl.UserServiceImpl">
<!-- 将userMapper注入到userService对象的userMapper属性中
name:userMapper中的属性
ref:对应的值必须为spring受管对象
value:注入的具体的值
-->
<property name="userMapper" ref="userMapper"/>
<property name="str" value="str"/>
<property name="num" value="2"/>
<property name="strs">
<array>
<value>str1</value>
<value>str2</value>
</array>
</property>
<property name="list">
<list>
<value>list1</value>
<value>list2</value>
</list>
</property>
<property name="map">
<map>
<entry key="map1" value="mapValue1"/>
<entry key="map2" value="mapValue2"/>
</map>
</property>
</bean>
3.2 构造器注入
- 必须提供对应构造方法
- 使用
<constructor-arg>
进行注入
<bean id="userService" class="com.jiazhong.spring01.service.impl.UserServiceImpl">
<!-- 通过构造器注入
-->
<!-- 第一种根据index参数下标设置 -->
<constructor-arg index="0" ref="userMapper"/>
<!-- 第二种根据参数名字设置 -->
<!-- <constructor-arg name="userMapper" ref="userMapper"/> -->
<!-- 第三种根据参数类型设置 -->
<!-- <constructor-arg type="java.lang.String" ref="userMapper"/> -->
<constructor-arg index="1" value="str"/>
<constructor-arg index="2" value="1"/>
<constructor-arg index="3">
<array>
<value>str1</value>
<value>str2</value>
</array>
</constructor-arg>
<constructor-arg index="4" >
<list>
<value>list1</value>
<value>list2</value>
</list>
</constructor-arg>
<constructor-arg index="5">
<map>
<entry key="key1" value="value1"/>
<entry key="key2" value-ref="userMapper"/>
</map>
</constructor-arg>
</bean>
4 Spring的对象管理
4.1 Spring对象的创建时机
-
默认情况下,当spring容器被加载时,spring容器里的对象自动创建
-
spring默认会创建所有受管对象,但是有一部分长时间不使用,一启动就创建会特别耗费资源
- 可以通过设置
<bean>
标签的lazy-init
属性为ture,设置某个受管对象使用懒加载机制,当第一次使用该对象才创建对象
<!-- lazy-init:使用懒加载方式创建对象 当第一次使用该对象才创建该对象 --> <bean id="userMapper01" class="com.jiazhong.spring01.mapper.impl.UserMapperImpl01" lazy-init="true"/>
- 还可以通过设置
<beans>
标签的default-lazy-init
属性为true,设置所有受管对象使用懒加载机制
<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 https://www.springframework.org/schema/beans/spring-beans.xsd" default-lazy-init="true"> </beans>
- 可以通过设置
4.2 Spring的受管对象作用域scope
-
默认情况下,spring的受管对象是Singleton(单例模式)的,当对象创建后就不再创建
-
我们可以通过设置
<bean>
标签的scope·
属性为prototype(原型模式),使当前受管对象为非单例的,每次使用该对象,就会创建一个新对象<!-- lazy-init:使用懒加载方式创建对象 当第一次使用该对象才创建该对象 scope="prototype":每次使用该对象都创建一个新对象 --> <bean id="userMapper01" class="com.jiazhong.spring01.mapper.impl.UserMapperImpl01" lazy-init="true" scope="prototype"/>
-
当把对象设置为prototype(原型模式)时,这些对象还受spring管理吗?
不受,当设置为prototype(原型模式)时,spring需不停创建对象,会及其耗费资源,所以当设置为prototype(原型模式)时,这些对象不受spring管理
-
4.3 对象初始化和销毁方法
- 基于XML配置中的init-method属性指定初始化方法(构造方法执行后自动调用),
destory-method
方法指定销毁方法(对象销毁前自动调用)
5 Spring基于编程和注解的实现
5.1 Spring基于编程和注解的简单使用
-
Maven中引入spring-context依赖包
-
创建一个类,使用
@Configuration
注解该类就是spring的配置类等同于上文的
application.xml
/** * 当一个类使用了@Configuration注解后该类就是一个配置类 * 配置类可以对spring进行相关配置 * 该类等同于spring的xml配置文件 */ @Configuration public class AppConfig { }
-
创建相应的类
-
在配置类中配置bean注解元素,将指定类交给Spring管理
@Bean
可以注解的name属性单独设置对象名@Bean("name")
默认使用的是name属性
/** * 当一个类使用了@Configuration注解后该类就是一个配置类 * 配置类可以对spring进行相关配置 * 该类等同于spring的xml配置文件 */ @Configuration public class AppConfig { /** * 当一个方法使用@Bean注解后,则表示该方法返回值为受管对象 * 等同于xml文件的bean标签 * 默认情况下,方法名为对象名 * @return */ @Bean public UserService userService(){ return new UserServiceImpl(); } }
-
使用Spring中的受管对象
@Test public void userMapperTest(){ //主要此时解析配置类的类变成了`AnnotationConfigApplicationContext` ApplicationContext applicationContext=new AnnotationConfigApplicationContext(AppConfig.class); UserService userService=(UserService) applicationContext.getBean("userService"); userService.addUser(new User()); }
5.2 依赖注入
-
在配置类里,将指定类交给spring进行管理,并对其进行依赖注入
@Autowired
:自动写入(自动注入)
- 默认使用名字进行匹配,如果没有找到,则使用类型进行匹配
/**
* 当一个类使用了@Configuration注解后该类就是一个配置类
* 配置类可以对spring进行相关配置
* 该类等同于spring的xml配置文件
*/
@Configuration
public class AppConfig {
/**
* 当一个方法使用@Bean注解后,则表示该方法返回值为受管对象
* 等同于xml文件的bean标签
* 默认情况下,方法名为对象名
*
* @Autowired:自动写入(自动注入)
* 默认使用名字进行匹配,如果没有找到,则使用类型进行匹配
* @return
*/
@Bean
public UserService userService(@Autowired UserMapper userMapper){
UserServiceImpl userService=new UserServiceImpl();
userService.setUserMapper(userMapper);
return userService;
}
@Bean
public UserMapper userMapper(){
return new UserMapperImpl();
}
@Bean
public UserMapper userMapper01(){
return new UserMapperImpl01();
}
}
-
利用注解进行依赖注入
上述方法,依赖注入都必须一个一个配置,在实际开发中会很麻烦,所以我们引入下列方法
①在配置类里,配置扫描那些包的注解
/** * 当一个类使用了@Configuration注解后该类就是一个配置类 * 配置类可以对spring进行相关配置 * 该类等同于spring的xml配置文件 * @ComponentScan:组件扫描注解,使用该注解,该注解会指定spring容器启动时扫描的包 * 扫描器会自动扫描指定包下带有@Component * @Service * @Repository * @Controller * 的类创建对象并将这些对象管理起来 */ @Configuration @ComponentScan({ "com.jiazhong.spring02.mapper","com.jiazhong.spring02.service"}) public class AppConfig { }
②在指定类上加入注解
@Component
,@Service
,@Repository
,@Controller
- 使用
@Autowired
注解
@Service public class UserServiceImpl implements UserService { @Autowired//自动注入注解 private UserMapper userMapper; @Override public void addUser(User user) { System.out.println("执行addUser"); userMapper.addUser(user); } }
③测试
@Test public void userMapperTest(){ ApplicationContext context=new AnnotationConfigApplicationContext(AppConfig.class); UserService userService=(UserService) context.getBean("userServiceImpl"); userService.addUser(new User()); }
- 使用
5.3 Spring基于编程和注解实现注解
-
@Component
: 将使用该注解的类的实例交给Spring进行管理,默认情况下对象为类名首字母小写,可以通过value属性设置对象名@Service
:该注解的作用和@Component
注解一样,@Component
注解的别名,属于语义注解,表示业务层实例(业务层注解)@Repository
:该注解的作用和@Component
注解一样,@Component
注解的别名,属于语义注解,表示持久层实例(持久层注解)@Controller
:该注解的作用和@Component
注解一样,@Component
注解的别名,属于语义注解,表示控制层实例(控制层注解)
-
@ComponentScan
:组件扫描注解,使用该注解,该注解会指定spring容器启动时扫描的包扫描器会自动扫描指定包下带有@Component
,@Service
,@Repository
,@Controller
的类创建对象并将这些对象管理起来 -
@Autowired
:按类型装配,自动将spring受管对象中同类型的实例注入到相对应的属性中。@Autowired(required=false)
说明:false,对象可以为null;true,对象必须存对象,不能为null。- 当同类型的对象有多个时就会报NoUniqueBeanDefinitionException(不是唯一Bean实例异常)异常
- 当Spring中存在多个类型一致的实例时,需要使用
@Qualifier
注解,指定要匹配的实例名@Qualifier
:按实例名称装配,该注解在属性上使用时不能单独使用,需要和@Autowired
一起使用
-
@Resource
:按名字装配,当指定的对象名不存在时在按照类型装配,当同类型的实例有多个时报异常,可以通过name属性指定实例名(默认为属性名)JDK11后 ,javax.annotation这个包被移除了,所以无法使用@resource注解。
引入该包即可
<dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency>
-
@Value
:用于注入基本类型和字符串类型,当使用外部属性文件注入基本类型或字符串类型时需要使用@PropertySource()
指定外部文件,并在Value注解中使用Spring的EL表达式获取文件中的值,如:@Value(“${key}”)
5.4 自定义注解的基本使用
注解本身没有太多意义,也没有什么功能
每个注解背后都是一个或多个强大的功能类,来处理该注解的功能
注解的处理类都是使用"反射机制"是实现的
-
标准注解
Java在java.lang包中内置了三种标准注解
@Override
:表示当前的方法定义将覆盖父类中的方法,即重写@Deprecated
:注解有该注解的元素会标记为过时@SuppressWarnings
:抑制编译器警告
-
元注解
JavaSE5定义了四种元注解,从Java7开始额外添加了三种
-
@Target
:标记该注解是哪种Java成员标记自定义的注解可以在哪些方法使用(类,方法,构造器,属性,包,参数)
ElementType取值 范围 ElementType.ANNOTATION_TYPE 可以给一个注解进行注解 ElementType.CONSTRUCTOR 可以给构造方法进行注解 ElementType.FIELD 可以给属性进行注解 ElementType.LOCAL_VARIABLE 可以给局部变量进行注解 ElementType.METHOD 可以给方法进行注解 ElementType.PACKAGE 可以给一个包进行注解 ElementType.PARAMETER 可以给一个方法内的参数进行注解 ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举 -
@Retention
:标记注解的生命周期,注解是在源码中或class中或运行期起作用RetentionPolicy取值 生命周期 RetentionPolicy.SOURCE 源码中起作用,编译后不存在 RetentionPolicy.CLASS class表示字节码文件中起作用,不能通过反射获得 RetentionPolicy.RUNTIME 运行期起作用,可以通过反射获得 -
@Documented
:标记该注解是否包含在用户文档中 -
@Inherited
:允许子类继承父类中的注解 -
@SafeVarargs
:Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告 -
@FunctionalInterface
: Java 8 开始支持,标识一个匿名函数或函数式接口 -
@Repeatable
: Java 8 开始支持,标识某注解可以在同一个声明上使用多次
-
-
自定义注解
例:
- 注解类
/** * 自定义注解 */ @Target({ ElementType.TYPE,ElementType.METHOD,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { /** * 为注解定义属性 * 当属性名为value时,在使用该属性时可用直接写属性值,不需要加属性名 * 注解中属性的权限可使用public和默认的,但不可以使用protected或private * @return */ public String value() default "";//声明一个属性并设置默认值 int num(); }
- 使用该注解
@MyAnnotation(value = "",num = 20) public class AnnotationDemo { //当注解存在多个属性,value不能用默认方式,必须加上属性名 @MyAnnotation(value = "",num = 20) private String string; @MyAnnotation("") public void say(){ } }
- 注解处理类
public class ParseAnno { public static void main(String[] args) throws NoSuchMethodException { Class<AnnotationDemo> annotationDemoClass = AnnotationDemo.class; Method method = annotationDemoClass.getDeclaredMethod("say"); //获得方法上面的注解 MyAnnotation myAnnotation = method.getDeclaredAnnotation(MyAnnotation.class); String value = myAnnotation.value(); int num = myAnnotation.num(); System.out.println(value+"\n"+num); } }
6 SpringAop的使用
AOP(面向切面编程)全称Aspect Oriented Programming
6.1 SpringAop的使用
AOP就是把系统中重复的代码抽取出来,单独开发 ,在系统需要时,使用动态代理技术,在不修改源码的基础上,将单独开发的功能通知织入(应用)到系统中的过程,完成完整的代码逻辑
- 优点:减少重复代码、提高开发效率、提高维护性
-
AOP相关术语
① Joinpoint(连接点):连接点指被拦截到的点。在spring中这些点指的是方法,spring只支持方法类型连接点,指切面在系统中的重要位置,spring能够使用切面的位置为方法,所以连接指方法。
- 连接点指要调用某个方法时,拦截到对这个方法的调用,做一些其他事情,然后再执行目标方法
②Pointcut(切入点):指我们要对那些Joinpoint进行拦截的定义,spring中使用切入点表达式定义(切入点表达式)
- 简单理解,切入点是对连接点的描述,它将多个连接点进行概括的定义
③ Advice(通知):指拦截到joinpoint之后所要做的事情就是通知
- 通知:前置通知、后置通知、异常通知、最终通知、环绕通知
④Target(目标对象):代理的目标对象
⑤Weaving(织入):将通知应用到切入点的过程
⑥Proxy(代理):代理对象
⑦Aspect(切面):切入点和通知的总称
- 简单说AOP是,在系统执行某个方法时,在执行这个方法前或方法后去执行另一段程序(另一段程序就是AOP中的通知),而拦截这个方法的定义可以称为切入点
-
AOP的简单使用(基于注解)
① 引入spring依赖包和aop植入依赖包AspectJ Weaver包
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.3</version> </dependency> <!--aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.9.1</version> <scope>runtime</scope> </dependency>
② 创建一个切面类使用
@Aspect
注解- 当一个类使用了
@Aspect
注解该类就成为了一个切面类
③ 把切面类交给spring进行管理,使用
@Component
注解④ 编写切入点(Pointcut),使用切入点表达式定义连接点< Joincut>(拦截到的方法)。
-
Execution(表达式)
表达式写法:
访问修饰符 返回类型 包名.包名.类名.方法名(参数列表)
//指定那些方法做为切入点(拦截那些方法)定义一个确定的方法:
@Pointcut("execution(public void com.jiazhong.service.impl.UserServiceImpl.deleteUser(int))")
定义一个包中任意类的方法且方法类型任意:
@Pointcut("execution(public void com.jiazhong.service.impl.*.*(..))")
- (…)表示方法的参数可以为任意数量、任意类型
定义当前包及其子包:
@Pointcut("execution(public void com.jiazhong.service..*.*(..))")
排除某个方法:
@Pointcut("execution(public void com.jiazhong.service..*.*(..))&& !execution(public void com.jiazhong.service.impl.UserServiceImpl.addUser())")
- 当一个类使用了
/**
* 定义切入点
* 切入点的声明类似于方法,但不能有方法体内容和参数
* myPointCut():切入点的名字
* 在切入点声明上加入连接点定义
* 连接点使用切入点表达式定义(拦截到方法的定义)
* spring连接点只能是方法
*/
@Pointcut("execution(public void com.jiazhong.service.impl.UserServiceImpl.addUser())")
public void myPointCut(){
}
⑤ 编写相关通知
-
前置通知
/** * 定义相关通知:拦截到指定方法后要做的事情 * 通知是指具体功能的方法 * @Before(切入点):通过切入点名字指定当前通知的连接点,当执行哪些方法方法时执行哪些通知 */ @Before("myPointCut()")//前置通知注解,当一个方法使用该注解后则该方法称为 public void beforeAdvice(){ System.out.println("前置通知被执行"); }
-
后置通知
/** * 后置通知 * returning="obj":获得目标方法的返回值并将返回值赋给obj参数,此时在方法内部即可使用 */ @AfterReturning("myPointCut()",returning = "obj") public void afterRunningAdvice(Object obj){ System.out.println("后置通知被执行"); }
-
异常通知
/** * 异常通知,目标方法异常时才执行 */ @AfterThrowing("myPointCut()") public void exceptionAdvice(){ System.out.println("异常通知被执行"); }
-
最终通知
/** * 最终通知 */ @After("myPointCut()") public void afterAdvice(){ System.out.println("最终通知被执行..."); }
-
环绕通知
可通过编程方法替换,前置,后置,异常,最终通知
目标方法必须在环绕通知内部手动调用
在环绕通知中为了实现更加灵活的通知处理需要传入参数:ProceedingJoinPoint
ProceedingJoinPoint
:处理连接点参数,改参数内部封装了目标方法的签名
使用该参数后可以在通知内部获得内部方法并调用目标方法
方法签名:方法的声明
@Around("myPointCut()") public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) { try { System.out.println("环绕通知-->前置"); proceedingJoinPoint.proceed();//调用目标方法 /* Signature signature = proceedingJoinPoint.getSignature();//获得目标方法签名 System.out.println(signature);*/ System.out.println("环绕通知-->后置"); } catch (Throwable e) { e.printStackTrace(); System.out.println("环绕通知-->异常通知"); } finally { System.out.println("环绕通知-->最终通知"); } } }
⑥ 切面类源码
package com.jiazhong.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
/**
* 切面类
* 切面类定义两方面内容:
* (1)定义切入点,用于定义要拦截的方法(连接点)
* (2)定义通知,拦截到方法后要做的事情
*/
@Aspect//切面注解当一个类使用该注解,该类就成为了一个切面类
@Component
@EnableAspectJAutoProxy //开启切面自动代理模式
public class MyAspect {
/**
* 定义切入点
* 切入点的声明类似于方法,但不能有方法体内容和参数
* myPointCut():切入点的名字
* 在切入点声明上加入连接点定义
* 连接点使用切入点表达式定义(拦截到方法的定义)
* spring连接点只能是方法
*/
// @Pointcut("execution(public void com.jiazhong.service.impl.UserServiceImpl.addUser())")
@Pointcut("execution(public void com.jiazhong.service.impl.UserServiceImpl.deleteUser(int))")
public void myPointCut() {
}
/**
* 定义相关通知:拦截到指定方法后要做的事情
* 通知是指具体功能的方法
*
* @Before(切入点):通过切入点名字指定当前通知的连接点,当执行哪些方法方法时执行哪些通知
**/
// @Before("myPointCut() && args(args)")//前置通知注解,当一个方法使用该注解后则该方法称为
// public void beforeAdvice(Object args) {
// System.out.println("前置通知被执行"+args);
// }
//
// /**
// * 后置通知
// */
// @AfterReturning("myPointCut()")
// public void afterRunningAdvice(){
// System.out.println("后置通知被执行");
// }
// /**
// * 异常通知,目标方法异常时才执行
// */
// @AfterThrowing("myPointCut()")
// public void exceptionAdvice(){
// System.out.println("异常通知被执行");
// }
//
// /**
// * 最终通知
// */
// @After("myPointCut()")
// public void afterAdvice(){
// System.out.println("最终通知被执行...");
// }
/**
* 环绕通知
* 可通过编程方法替换,前置,后置,异常,最终通知
* 目标方法必须在环绕通知内部手动调用
* 在环绕通知中为了实现更加灵活的通知处理需要传入参数:ProceedingJoinPoint
* ProceedingJoinPoint:处理连接点参数,改参数内部封装了目标方法的签名
* 使用该参数后可以在通知内部获得内部方法并调用目标方法
* 方法签名:方法的声明
*/
@Around("myPointCut()")
public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
try {
System.out.println("环绕通知-->前置");
proceedingJoinPoint.proceed();//调用目标方法
/* Signature signature = proceedingJoinPoint.getSignature();//获得目标方法签名
System.out.println(signature);*/
System.out.println("环绕通知-->后置");
} catch (Throwable e) {
e.printStackTrace();
System.out.println("环绕通知-->异常通知");
} finally {
System.out.println("环绕通知-->最终通知");
}
}
}
6.2 使用AOP和自定义注解实现操作日志统计功能
7 Spring和MyBatis整合
7.1 引入依赖包
-
Spring-context
<!--spring--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.12.RELEASE</version> </dependency>
-
Aspectj-weaver
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.9.1</version> </dependency>
-
spring-jdbc
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.2.RELEASE</version> </dependency>
-
spring-tx(spring事务管理)
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.2.2.RELEASE</version> </dependency>
-
MyBatis核心包
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.10</version> </dependency>
-
MyBatis-spring整合包
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。
<!-- mybatis-spring --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>3.0.1</version> </dependency>
-
mysql依赖包
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency>
-
Druid依赖包
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.14</version> </dependency>
-
Junit
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency>
7.2 编写spring核心配置类,将MyBatis的相关配置整合到spring中
-
myBatis配置
-
dataSource:数据源
@Value("${driverClassName}") private String driverClassName; @Value("${url}") private String url; @Value("${username}") private String username; @Value("${password}") private String password; @Bean public DataSource dataSource(){ //创建dataSource对象 DruidDataSource dataSource=new DruidDataSource(); //添加数据库连接属性 dataSource.setDriverClassName(driverClassName); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); //数据库连接池属性 dataSource.setMaxActive(50); dataSource.setInitialSize(20); dataSource.setMaxWait(1000); dataSource.setMinIdle(10); return dataSource; }
-
SqlSessionFactory:创建sqlsessionFactory对象
/** * 配置sqlSessionFactory对象 */ @Bean @Autowired public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { //创建sqlSessionBean对象 SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean(); //设置数据源 sqlSessionFactoryBean.setDataSource(dataSource); return sqlSessionFactoryBean.getObject(); }
-
事务管理
/** * 配置事务管理器 */ @Bean @Autowired public DataSourceTransactionManager txManager(DataSource dataSource){ //基于事务平台管理器 //PlatformTransactionManager txManager //创建事务管理器对象 DataSourceTransactionManager txManager =new DataSourceTransactionManager(); txManager.setDataSource(dataSource); return txManager; }
-
配置SQL映射文件
@MapperScan("com.sm.mapper")//映射器扫描器
配置类代码如下
@Configuration @PropertySource("classpath:mybatis.properties")//引入外部文件 @ComponentScan("com.sm.service") @MapperScan("com.sm.mapper")//映射器扫描器 @EnableTransactionManagement //开启事务管理器,开启后就可以使用spring提供的声明式事务 public class SMConfig { @Value("${jdbc.driverClassName}") private String driverClassName; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean public DataSource dataSource(){ //创建dataSource对象 DruidDataSource dataSource=new DruidDataSource(); //添加数据库连接属性 dataSource.setDriverClassName(driverClassName); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); //数据库连接池属性 dataSource.setMaxActive(50); dataSource.setInitialSize(20); dataSource.setMaxWait(1000); dataSource.setMinIdle(10); return dataSource; } /** * 配置sqlSessionFactory对象 * @param dataSource * @return * @throws Exception */ @Bean @Autowired public SqlSessionFactory sqlSessionFactory(DataSource dataSource, ApplicationContext applicationContext) throws Exception { //创建sqlSessionBean对象 SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean(); //设置数据源 sqlSessionFactoryBean.setDataSource(dataSource); //设置日志 org.apache.ibatis.session.Configuration configuration=new org.apache.ibatis.session.Configuration(); configuration.setLogImpl(StdOutImpl.class); sqlSessionFactoryBean.setConfiguration(configuration); //配置映射文件 //classpath:当前应用的根目录,编译后的classes目录 Resource resource = applicationContext.getResource("classpath:mapper/userMapper.xml"); sqlSessionFactoryBean.setMapperLocations(resource); return sqlSessionFactoryBean.getObject(); } /** * 配置事务管理器 */ @Bean @Autowired public DataSourceTransactionManager txManager(DataSource dataSource){ //基于事务平台管理器 //PlatformTransactionManager platformTransactionManager //创建事务管理器对象 DataSourceTransactionManager txManager =new DataSourceTransactionManager(); txManager.setDataSource(dataSource); return txManager; } }
-
7.3 声明式事务
- 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!
- 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。
事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用。
-
事务四个属性ACID
①原子性(atomicity)
- 事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用
②一致性(consistency)
- 一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
③隔离性(isolation)
- 可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
④持久性(durability)
- 事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中
-
事务管理
①编程式事务管理
- 将事务管理代码嵌到业务方法中来控制事务的提交和回滚
- 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码
②声明式事务管理
-
一般情况下比编程式事务好用。
-
将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
-
将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。spring管理事务(基于注解)
①在spring核心配置类中加入注解
@EnableTransactionManagement
开启事务管理器,开启后就可以使用spring提供的声明式事务
@Configuration @PropertySource("classpath:mybatis.properties")//引入外部文件 @ComponentScan("com.sm.service") @MapperScan("com.sm.mapper")//映射器扫描器 @EnableTransactionManagement //开启事务管理器,开启后就可以使用spring提供的声明式事务 public class SMConfig { }
②在类中使用
@Transactional
使用该注解后该类的每一个方法都是一个独立的事务
@Service("UserService") @Transactional //使用该注解后该类的每一个方法都是一个独立的事务 public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public void addUser(User user) { userMapper.addUser(user); } @Override @Transactional(propagation = Propagation.NOT_SUPPORTED) public User selectUserById(int userId) { User user = userMapper.selectUserById(userId); return user; } }
③可设置事务的传播特性
事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:
- propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
- propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
- propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
- propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
- propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
- propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作
@Transactional(propagation = Propagation.NOT_SUPPORTED) public User selectUserById(int userId) { User user = userMapper.selectUserById(userId); return user; }
- 学习来自于西安加中实训