1、什么是Spring框架
Spring框架是一个容器,它是用来整合其他框架的框架。
它的核心是IOC(控制反转)和AOP(面向切面编程)。
它是一个大的家族,由20多个模块构成,在很多领域都提供了非常优秀的支持。
它的最终目标其实就是解耦合。
2、Spring框架的优点
(1)轻量级
它的核心jar包很小,总共3M多,对代码零污染。
(2)面向接口编程
Spring底层是面向接口的开发.
面向接口开发的规范:
1.类中的成员变量设计为接口
2.方法的参数设计为接口
3.方法的返回值设计为接口
4.调用时接口指向实现类
总结一句话,上接口就是为了更加灵活。
(3)AOP面向切面编程
1.切面就是程序中那些公共的,通用的,重复的功能。例如 :日志,事务,权限验证等。
2.面向切面编程AOP就是将切面单独拿出来开发,在需要的业务功能上自动反织回去。
(4)方便的整合其它框架
3、什么是IOC(控制反转)
控制反转IOC(Inversion of Control)它是一种概念,同时也是一种思想,就是让Spring容器干活的思想。
问题:
1.控制什么?
是创建对象和依赖注入的控制权
2.反转什么?
是由程序员控制权反转给Spring容器控制权
正转:由程序员创建对象和依赖注入
Student s = new Student(); //程序员在创建对象
s.setName("吃切糕的斯派克"); // 程序员依赖注入值
s.setAge(25); // 程序员依赖注入值
反转:Spring容器创建对象和依赖注入
<bean id="stu" class="com.spike.pojo.Student"> ===>Spring容器在创建对象
<property name="name" value="吃切糕的斯派克"></property> ===>Spring容器依赖注入值
<property name="age" value="25"></property> ===>Spring容器依赖注入值
</bean>
4、Spring的IOC实现
主要有两种方式
1.基于xml的实现形式
2.基于注解的实现形式
5、基于xml的IOC的实现
先在pom.xml文件中指定 Java 编译版本,可以是jdk8,jdk11或者jdk17,本处采用jdk17。
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
(1)创建对象
注意点:1.一定要有无参的构造方法
2.所有对象的创建和依赖注入是在容器启动时发生
3.使用<bean>标签创建对象
代码示例
<!--创建Student对象
参数:
id:创建的对象的名称
class:创建的对象的类型
-->
<bean id="stu" class="com.spike.pojo.Student"></bean>
(2)依赖注入
1.按注入值分为简单类型注入和引用类型注入
简单类型(8种基本类型(封装类型)+String)使用value注入值。
引用类型使用ref注入值。
2.按注入的方式分为setter注入和构造方法注入
A. setter注入法
注意点:一定要有setXXX()方法
使用<property>标签赋值
代码示例
<!--创建School对象-->
<bean id="school" class="com.spike.pojo.School">
<property name="name" value="天津大学"></property>
<property name="address" value="天津市南开区"></property>
</bean>
<!--创建Student-->
<bean id="stu" class="com.spike.pojo.Student">
<property name="name" value="吃切糕的斯派克"></property>
<property name="age" value="25"></property>
<property name="school" ref="school"></property> ===>引用类型注入值
</bean>
B. 构造方法注入
使用<constructor-arg>标签赋值。
a.使用构造方法参数名称注入值
<!--创建School对象,使用构造方法参数名称注入值
public School(String name1, String address1) {...} //构造方法
-->
<bean id="school" class="com.spike.pojo.School">
<constructor-arg name="name1" value="天津大学"></constructor-arg>
<constructor-arg name="address1" value="天津市南开区"></constructor-arg>
</bean>
b.使用构造方法参数下标注入值
<!--创建Student,使用构造方法参数下标注入值,默认下标从0开始
public Student(String name, int age, School school){...}
-->
<bean id="stuIndex" class="com.spike.pojo.Student">
<constructor-arg index="1" value="25"></constructor-arg>
<constructor-arg index="0" value="吃切糕的斯派克"></constructor-arg>
<constructor-arg index="2" ref="school"></constructor-arg>
</bean>
c.使用构造方法参数默认顺序注入值
<!--创建Student,使用构造方法参数默认顺序注入值
public Student(String name, int age, School school){...}
-->
<bean id="stuSequence" class="com.spike.pojo.Student">
<constructor-arg value="吃切糕的斯派克"></constructor-arg>
<constructor-arg value="25"></constructor-arg>
<constructor-arg ref="school"></constructor-arg>
</bean>
6、创建系统类型的对象
Spring的IOC的功能就是创建对象并依赖注入值,并不在乎是系统类型还是自定义类型。
<!--例如创建Date类型的对象-->
<bean id="myDate" class="java.util.Date">
<property name="time" value="1234567891011"></property>
</bean>
<!--注意setTime()方法不一定给time的成员变量赋值,只是调用setTime()的方法-->
7、Spring创建对象的作用域
Spring创建对象默认的作用域范围是单例的(singleton)。可以设置为非单例(prototype)。
1.默认单例(因为创建的过程很复杂):
<bean id="myDate" class="java.util.Date" scope="singleton"></bean>
<bean id="myDate" class="java.util.Date" ></bean>
<!--每次取都返回同一个第一次创建好的对象-->
2.设置为非单例:
<bean id="myDate" class="java.util.Date" scope="prototype"></bean>
<!--每次取都返回一个新创建的对象-->
8、引用类型的属性自动注入
前提:引用类型才可以实现自动注入。
自动注入有两种方式:按类型自动注入 ,按名称自动注入。
<bean id="school" class="com.bjpowernode.s02.School">
<property name="name" value="天津大学"></property>
<property name="address" value="天津市南开区"></property>
</bean>
<!--创建Student-->
<!--按名称自动注入-->
<bean id="stu" class="com.spike.pojo.Student" autowire="byName">
<property name="name" value="吃切糕的斯派克"></property>
<property name="age" value="25"></property>
</bean>
<!--按类型自动注入-->
<bean id="stu" class="com.spike.pojo.Student" autowire="byType">
<property name="name" value="吃切糕的斯派克"></property>
<property name="age" value="25"></property>
</bean>
9、基于注解的IOC实现
依赖注入:DI(Dependency Injection)是IOC的实现技术,所以说DI就是IOC。两种不同的说法而已,就是Spring去创建对象并依赖注入。
(1)创建对象的注解
@Comconpent:创建任意对象,默认对象的名称是类名的驼峰命名法,也可以自定义对象名称
@Controller:专门用于创建界面层的对象
@Service:专门用来创建业务逻辑层的对象
@Repository:专门用来数据访问层的对象
(2)依赖注入的注解
@Value:简单类型注入
@Autowired:引用类型按类型注入
注意:
@Autowired
@Qualifier("名称")
俩注解一起用是引用类型的按名称注入
代码示例
@Component
public class Student {
@Value("吃切糕的斯派克")
private String name;
@Value("25")
private int age;
@Autowired //===>单独使用是按类型注入
@Qualifier("school") //===>俩注解一起使用是按名称注入
private School school;
}
切记!!!!一定要在applicationContext.xml配置文件中添加包扫描。
<context:component-scan base-package="com.spike.pojo"></context:component-scan>
(3)同源类型
1.被注入的对象的类型(Student中的School对象)与注入的对象(Bean工厂中创建好的对象)的类型完全一致。
2.被注入的对象的类型(Student中的School对象--父)与注入的对象(Bean工厂中创建好的对象--子)的类型是父子类类型。
按类型注入时如果有多个可被注入的对象,再次按名称进行筛选注入
按名称注入时如果有多个可被注入的对象,直接按名称指定注入
3.被注入的对象的类型(Student中的School对象--接口)与注入的对象(Bean工厂中创建好的对象--实现类)的类型是接口和实现类。
10、添加包扫描的方式
(1)单个包扫描(推荐使用)
<context:component-scan base-package="com.spike.mapper"></context:component-scan>
<context:component-scan base-package="com.spike.service.impl"></context:component-scan>
<context:component-scan base-package="com.spike.controller"></context:component-scan>
(2)多个包扫描,多个包之间以逗号或分号或空格分隔
<context:component-scan base-package="com.spike.mapper;com.spike.service.impl,com.spike.controller">
(3)扫描根包(不推荐使用,因为多扫无用包,降低容器启动效率)
<context:component-scan base-package="com.spike"></context:component-scan>
11、Spring配置文件的拆分
因为项目越来越大,多人协作开发,要进行配置文件拆分。
(1)按层拆(推荐使用)
<!--applicationContext_mapper.xml-->
<bean id="uMapper" class="com.spike.mapper.UsersMapperImpl"></bean>
<bean id="bMapper" class="com.spike.mapper.BookMapperImpl"></bean>
<!--applicationContext_service.xml-->
<bean id="uService" class="com.spike.service.impl.UsersServiceImpl">
<property name="usersMapper" ref="uMapper"></property>
</bean>
<bean id="bService" class="com.spike.service.impl.BookServiceImpl">
<property name="bookMapper" ref="bMapper"></property>
</bean>
<!--applicationContext_controller.xml-->
<bean id="usersController" class="com.spike.controller.UsersController">
<property name="usersService" ref="uService"></property>
</bean>
<bean id="bookController" class="com.spike.controller.BookController">
<property name="bookService" ref="bService"></property>
</bean>
(2)按功能拆
<!--applicationContext_users.xml-->
<bean id="uMapper" class="com.spike.mapper.UsersMapperImpl"></bean>
<bean id="uService" class="com.spike.service.impl.UsersServiceImpl">
<property name="usersMapper" ref="uMapper"></property>
</bean>
<bean id="usersController" class="com.spike.controller.UsersController">
<property name="usersService" ref="uService"></property>
</bean>
<!--applicationContext_book.xml-->
<bean id="bMapper" class="com.spike.mapper.BookMapperImpl"></bean>
<bean id="bService" class="com.spike.service.impl.BookServiceImpl">
<property name="bookMapper" ref="bMapper"></property>
</bean>
<bean id="bookController" class="com.spike.controller.BookController">
<property name="bookService" ref="bService"></property>
</bean>
12、配置文件的整合
(1)单个文件导入
<import resource="applicationContext_mapper.xml">
<import resource="applicationContext_service.xml">
<import resource="applicationContext_controller.xml">
(2)批量导入
<import resource="applicationContext_*.xml">
13、IOC实现之基于xml与注解的区别
注解优点:
方便
直观
高效(代码少,没有配置文件的书写那么复杂)。
注解弊端: 以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的。
XML方式优点:
配置和代码是分离的。
在 xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载。
XML方式缺点:编写麻烦,效率低,大型项目过于复杂。
14、什么是AOP
AOP(Aspect Orient Programming),面向切面编程。
切面就是程序中那些公共的通用的,重复的代码和功能称为切面。例如:日志,事务 ,权限等。
面向切面编程:将切面单拎出来开发,在需要的方法中自动反织回去.
通过运行期动态代理实现程序功能的统一维护的一种技术。
面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到主业务逻辑中。提高程序的可重用性,同时提高了开发的效率。
15、手写AOP框架
目的:完成业务和切面功能的解耦合
业务:图书或商品购买业务功能
切面:事务或日志或权限
代理设计模式是23种设计模式之一。目标对象不可访问,通过代理对象增强功能访问就是代理设计模式。
代理模式分为静态代理和动态代理。动态代理分为JDK动态代理和CGLib动态代理。
静态代理的设计规范:
(1)必须有业务接口
(2)代理对象和目标(业务)对象实现同一个业务接口
(3)静态代理对象在程序运行前就已经存在
(4)静态代理负责业务和切面的整合
JDK动态代理的设计规范:
(1)必须有业务接口
(2)动态代理对象在程序运行时动态的在内存中构建
(3)动态代理通过方法传参进行业务和切面的整合
(4)动态代理对象不能代理业务接口外的方法
JDK动态代理涉及到的类和接口
(1)Proxy类 java.lang.reflect包下的类
用来生成动态代理对象
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
(2)InvocationHandler接口
代理处理器,用来进行切面和业务整合
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
(3)Method类
用来传递测试类中正在调用的目标方法buy(),one()
method.invoke(); ===>进行手工调用目标方法
代码示例 手写AOP-------->循序渐进
第一个版本:业务和切面耦合在一起
/**
* 第一个版本:业务和切面耦合在一起
*/
public class BookServiceImpl {
public void buy(){
try {
//切面
System.out.println("事务开启............");
//业务
System.out.println("图书购买业务功能实现 ...........");
//切面
System.out.println("事务提交............");
} catch (Exception e) {
System.out.println("事务回滚...........");
}
}
}
第二个版本:使用静态子类代理拆分业务和切面
/**
* 第二个版本:通过子类实现切面功能,父类只做业务实现
*/
public class BookServiceImpl {
public void buy(){
//只完成主业务功能
System.out.println("图书购买功能实现..............");
}
}
/**
* 第二个版本:子类完成切面功能(事务)的处理,整合父类的业务功能
*/
public class SubBookServiceImpl extends BookServiceImpl {
@Override
public void buy() {
//子类中提供事务的切面
try {
System.out.println("事务开启...........");
super.buy();//父类中主业务功能实现
System.out.println("事务提交...........");
} catch (Exception e) {
System.out.println("事务回滚...........");
}
}
}
第三个版本:使用静态代理,完成业务和切面的解耦合
/**
* 第三个版本:接口规定业务功能
*/
public interface Service {
//购买的业务
void buy();
}
public class BookServiceImpl implements Service {
@Override
public void buy() {
System.out.println("图书购买业务功能实现 ..............");
}
}
public class ProductServiceImpl implements Service {
@Override
public void buy() {
System.out.println("商品业务功能实现.........");
}
}
/**
* 第三个版本的静态代理类,在本类中完成业务和切面的整合
* 灵活目标对象的切换
*/
public class Agent implements Service{
//类中的成员变量设计为接口
Service target;
//通过构造方法传入目标对象,方法的参数设计为接口
public Agent(Service target){
this.target = target;
}
@Override
public void buy() {
try {
//事务切面
System.out.println("事务开启.............");
//调用目标对象的业务功能
// BookServiceImpl bookService = new BookServiceImpl();
// bookService.buy();
// ProductServiceImpl productService = new ProductServiceImpl();
// productService.buy();
target.buy();//图书对象来了,就是图书功能实现 ,商品对象来了,就是商品功能实现
//事务切面
System.out.println("事务提交............");
} catch (Exception e) {
System.out.println("事务回滚............");
}
}
}
@Test
public void test02(){
//调用时接口指向实现类
Service agent = new Agent(new ProductServiceImpl());
agent.buy();
}
第四个版本 使用静态代理,以面向接口的方式进行业务和切面的各种拆分整合
/**
* 切面接口定义
*/
public interface AOP {
//JDK8以后的新特性,默认接口实现default
default void before(){}
default void after(){}
default void exception(){}
}
/**
* 事务的切面实现
*/
public class TransAop implements AOP {
@Override
public void before() {
System.out.println("事务开启............");
}
@Override
public void after() {
System.out.println("事务提交............");
}
@Override
public void exception() {
System.out.println("事务回滚............");
}
}
/**
* 日志的切面实现
*/
public class LogAop implements AOP {
@Override
public void before() {
System.out.println("前置日志输出...........");
}
}
/**
* 静态代理对象,完成业务和切面的整合
* 通过接口灵活的进行业务功能切换和切面功能切换
*/
public class Agent implements Service {
//成员变量设计为接口(面向接口编程)
Service target; //灵活切换目标对象
AOP aop; //灵活切换切面对象
//构造方法传入参数(方法的参数设计为接口--->面向接口编程)
public Agent(Service target,AOP aop){
this.target = target;
this.aop = aop;
}
@Override
public void buy() {
try {
//切面
aop.before(); //不同的切面功能
//业务
target.buy(); //不同的业务功能
//切面
aop.after();
} catch (Exception e) {
aop.exception();
}
}
}
@Test
public void test02(){
//通过静态代理对象完成功能调用
Service agent = new Agent(new ProductServiceImpl(),new LogAop());
agent.buy();
}
第五个版本 使用动态代理,解耦合业务和切面
public class ProxyFactory {
public static Object getAgent(Service yewu,AOP aop){
return Proxy.newProxyInstance(
yewu.getClass().getClassLoader(), // ClassLoader loader,类加载器,将目标对象加载进JVM被解析执行
yewu.getClass().getInterfaces(), //Class<?>[] interfaces, 目标对象实现的所有业务接口
// InvocationHandler h 代理处理器,进行业务和切面整合
new InvocationHandler() {
@Override
public Object invoke(
//生成的动态代理对象(本次没用)
Object proxy,
//外部测试类中正在调用的目标方法通过method对象传进来,可以手工在此调用 method.invoke();===>buy()
Method method,
//目标方法的参数
Object[] args) throws Throwable {
//进行业务和切面整合
Object obj = null;
try {
//切面
aop.before();
//业务方法 buy() 通过反射调用目标方法 method.invoke(yewu,args)===> buy();
obj = method.invoke(yewu,args);
//切面
aop.after();
} catch (Exception e) {
//切面
aop.exception();
}
return obj; //目标方法的返回值
}
}
);
}
}
16、Spring原生AOP的实现
Before通知:在目标方法被调用前调用,涉及接口org.springframework.aop.MethodBeforeAdvice;
After通知:在目标方法被调用后调用,涉及接口org.springframework.aop.AfterReturningAdvice;
Throws通知:目标方法抛出异常时调用,涉及接口org.springframework.aop.ThrowsAdvice;
Around通知:拦截对目标方法调用,涉及接口为org.aopalliance.intercept.MethodInterceptor;
17、AspectJ框架
它是专门针对AOP的非常优秀的框架,易学易用。
它是基于java语言开发的,可以无缝扩展功能。
AspetJ 是 Eclipse 的开源项目。
(1)AspectJ常见通知类型
(1)@Before:前置通知
(2)@AfterReturning:后置通知
(3)@Around:环绕通知
(4)@After:最终通知
(5)@Pointcut:给切入点表达式起别名
(2)AspectJ 的切入点表达式(重点)
用来指定切入切面的位置
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
简化版公式:
execution(方法返回值 方法声明(参数))
常见符号解析:
* 代表任意字符(通配符)
.. 出现在方法的参数中,代表任意参数
出现在路径中,代表当前路径及其子路径及其所有的类
代码示例
execution(public * *(..)) //所有公共访问权限的方法
execution(* set*(..)) //以set开头的任意方法
execution(* com.spike.service.impl.*.*(..)) //com.spike.service.impl下的所有类中的所有方法
execution(* com.spike.service..*.*(..)) //com.spike.service及其子包和所有类
execution(* *..service.*.*(..)) //以service结尾的包,前面可以有多级包
execution(* *.service.*.*(..)) //service前面只能有一级包
(3)@Before前置通知
在目标方法前切入切面功能,不能影响目标方法的返回值。
@Aspect //交给AspectJ框架去处理切面功能
@Component
public class MyAspect {
/**
* 实现切面功能要依靠切面方法
* 前置通知切面方法的规范:
* 1)访问权限是public
* 2)切面方法没有返回值void
* 3)切面方法名称自定义
* 4)切面可以没有参数,如果有参数只能是JoinPoint类型
* 5)使用@Before注解声明是前置通知
* 参数:
* value:指定切入点表达式
* 目标方法:
* public String doSome(String name, int age)
*/
// @Before(value = "execution(public String com.spike.service.SomeServiceImpl.doSome(String,int))")
//@Before(value = "execution(public String com.spike.service.SomeServiceImpl.*(String,int))")
//@Before(value = "execution(public String com.spike.service.SomeServiceImpl.*(..))")
//@Before(value = "execution(public * com.spike.service.SomeServiceImpl.*(..))")
//@Before(value = "execution(public * com.spike.service.*.*(..))")
//@Before(value = "execution(public * com.spike.service..*.*(..))")
@Before(value = "execution(public * *..service..*.*(..))")
public void myBefore(){
System.out.println("前置通知功能实现");
}
}
(4)@AfterReturning后置通知
在目标方法执行后切入切面功能,在切面方法中可以得到目标方法的返回值。
目标方法的返回值是通过切面方法的参数传入,所以受传参的值传递和引用传递的影响,值传递 (String+8种基本类型)不能影响目标方法的返回值,但引用传递可以改变目标方法的返回值。
@Aspect
@Component
public class MyAspect {
/**
* 后置通知切面方法的规范
* 1)访问权限是public
* 2)切面方法没有返回值void
* 3)切面方法名称自定义
* 4)切面方法可以没有参数,如果有参数就是目标方法的返回值
* 5)使用@AfterReturning注解声明是后置通知
* 参数
* value:指定切入点表达式
* returning:指定返回的目标方法的返回值的名称,此名称与切面方法参数名称一致
*/
@AfterReturning(value = "execution(* com.spike.service.*.*(..))",returning ="obj" )
public void myAfterReturning(Object obj){
//先判断是否有返回值
if(obj != null){
//再判断是否是String类型
if(obj instanceof String){
String s = (String) obj;
System.out.println("在切面方法中目标方法的返回值是:"+s.toUpperCase());
}
//再判断是否是Student类型
if(obj instanceof Student){
Student stu = (Student) obj;
stu.setName("吃切糕的斯派克");
System.out.println("在切面方法中目标方法的返回值是:"+stu);
}
}
System.out.println("后置通知功能实现");
}
}
(5)@Around环绕通知
是通过拦截目标方法,在其前后切入切面功能,它是功能最强大的通知,事务就是使用的环绕通知
在切面方法中可以随意改变目标方法的返回值。
切面方法的参数就是目标方法本身。
切面方法的返回值就是目标方法的返回值。
切面方法中可以控制目标方法的访问。
//需求:根据目标方法的第一个参数是吃切糕的斯派克,则可访问目标方法,否则拒绝访问目标方法.
@Aspect
@Component
public class MyAspect {
/**
* 环绕通知切面方法规范
* 1)访问权限是public
* 2)切面方法有返回值,其返回值就是目标方法的返回值
* 3)切面方法名称自定义
* 4)切面 法有参数,参数就是目标方法本身
* 5)要回避异常Throwable
* 6)使用@Around注解声明是环绕通知
* 参数:
* value:指定切入点表达式
*/
@Around(value = "execution(* com.spike.service.*.*(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
//取出目标方法的参数
Object[] params = pjp.getArgs();
if (params.length > 1) {
String name = (String) params[0];
if ("吃切糕的斯派克".equals(name)) {
//前切功能
System.out.println("环绕通知中的前置功能");
//调用目标方法
Object obj = pjp.proceed();
//后切功能
System.out.println("环绕通知中的后置功能");
return obj.toString().toUpperCase();
}
}
System.out.println("您无权访问目标方法");
return null;
}
}
(6)@After最终通知
是在目标方法执行后切入切面,不管目标方法是否正常执行,切面方法中的功能都会执行,它有点类似于finally,是程序的一个出口,用来进行善后处理,关闭流,清空对象等。
@Aspect
@Component
public class MyAspect {
/**
* 实现切面功能要依靠切面方法
* 最终通知切面方法的规范:
* 1)访问权限是public
* 2)切面方法没有返回值void
* 3)切面方法名称自定义
* 4)切面可以没有参数,如果有参数只能是JoinPoint类型
* 5)使用@After注解声明是最终通知
* 参数:
* value:指定切入点表达式
*
*/
@After(value = "execution(* com.spike.service.*.*(..))")
public void myAfter(){
System.out.println("最终通知功能实现");
}
}
(7)@Pointcut切入点表达式起别名
如果多个切面切入到同一个切入点,则可以使用@Pointcut注解起别名,使用一个无参无返空实现的方法名称作为别名。
@Aspect
@Component
public class MyAspect {
/**
* 实现切面功能要依靠切面方法
* 最终通知切面方法的规范:
* 1)访问权限是public
* 2)切面方法没有返回值void
* 3)切面方法名称自定义
* 4)切面可以没有参数,如果有参数只能是JoinPoint类型
* 5)使用@After注解声明是最终通知
* 参数:
* value:指定切入点表达式
*
*/
@After(value = "mycut()")
public void myAfter(){
System.out.println("最终通知功能实现");
}
@Before(value = "mycut()")
public void myBefore1(){
System.out.println("前置通知功能实现1");
}
@Before(value = "mycut()")
public void myBefore2(){
System.out.println("前置通知功能实现2");
}
@AfterReturning(value = "mycut()")
public void myAfterReturning(){
System.out.println("后置通知功能实现");
}
@Pointcut(value = "execution(* com.spike.service.*.*(..))")
public void mycut(){}
}
(8)AspectJ框架切换动态代理模式
AspectJ框架默认使用的是JDK动态代理,可以设置为CGLib动态代理。
(1)JDK动态代理必须实现接口
<aop:aspectj-autoproxy></aop:aspectj-autoproxy> ===>JDK动态代理
代理对象必须使用接口来接。
SomeService service = (SomeService) ac.getBean("someServiceImpl");
动态代理对象的类型是:class jdk.proxy2.$Proxy19
如果使用实现类来接动态代理对象则报错:class jdk.proxy2.$Proxy19 cannot be cast to class com.spike.service.SomeServiceImpl
(2)设置为CGLib动态代理,可以不实现接口,它是通过动态生成子类代理重写父类中业务方法来扩展功能。
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy> ===>CGLib动态代理
可以使用接口或实现类来接动态代理对象
SomeServiceImpl service = (SomeServiceImpl) ac.getBean("someServiceImpl");
SomeService service = (SomeService) ac.getBean("someServiceImpl");
18、Spring的事务
事务的处理是由Spring来控制,Spring事务控制的方式有两种:编程式事务和声明式事务
(1)编程式事务(注解式事务)
1.在配置文件中添加事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--必须配置数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
2.在配置文件中添加事务的注解驱动
<tx:annotation-driven></tx:annotation-driven>
3.在类上或方法上添加@Transactional注解声明事务
@Transactional
public Integer saveAccounts(Accounts accounts) {
Integer num = accountsMapper.saveAccounts(accounts);
System.out.println(accounts.getaName()+"帐户增加成功!num="+num);
//手工抛出异常
System.out.println(1/0);
return num;
}
4.@Transactional注解参数详解
@Transactional(
noRollbackForClassName = "ArithmeticException", //发生指定的异常不回滚事务,使用异常的名称
noRollbackFor = ArithmeticException.class, //发生指定的异常不回滚事务,使用异常的类型
rollbackForClassName = "NullPointerException", //发生指定的异常必须回滚事务,使用异常的名称
rollbackFor = NullPointerException.class, //发生指定的异常必须回滚事务,使用异常的类型
isolation = Isolation.DEFAULT , //使用数据库默认的事务隔离级别
timeout = -1, //设置连接超时时间,默认-1,永不超时
readOnly = false, //查询时必须设置只读属性为true,默认是false
propagation = Propagation.REQUIRED //事务的传播特性
)
(2)事物的传播特性
事务的传播特性可以决定多个事务之间的互斥,归并,加入等。
常用
PROPAGATION_REQUIRED:必被包含事务
PROPAGATION_REQUIRES_NEW:自己新开事务,不管之前是否有事务
PROPAGATION_SUPPORTS:支持事务,如果加入的方法有事务,则支持事务,如果没有,不 单开事务
PROPAGATION_NEVER:不能运行中事务中,如果包在事务中,抛异常
PROPAGATION_NOT_SUPPORTED:不支持事务,运行在非事务的环境
不常用
PROPAGATION_MANDATORY:必须包在事务中,没有事务则抛异常
PROPAGATION_NESTED:嵌套事务
(3)声明式事务(一般推荐使用)
只需要在配置文件中声明,不需要硬编码实现事务管理。
需要方法有命名规范:
增加: insert save add create
更新: update change modify set
删除: delete drop remove clear
查询: select search find query get
代码示例
<!-- 基于注解的开发添加包扫描-->
<context:component-scan base-package="com.spike.service.impl"></context:component-scan>
<!-- 添加事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 添加事务的切面-->
<tx:advice id="myadive" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*select*" read-only="true"/>
<tx:method name="*find*" read-only="true"/>
<tx:method name="*get*" read-only="true"/>
<tx:method name="*search*" read-only="true"/>
<tx:method name="*query*" read-only="true"/>
<tx:method name="*insert*" propagation="REQUIRED" />
<tx:method name="*add*" propagation="REQUIRED"/>
<tx:method name="*save*" propagation="REQUIRED" />
<tx:method name="*create*" propagation="REQUIRED"/>
<tx:method name="*update*" propagation="REQUIRED"/>
<tx:method name="*change*" propagation="REQUIRED"/>
<tx:method name="*modify*" propagation="REQUIRED"/>
<tx:method name="*set*" propagation="REQUIRED"/>
<tx:method name="*delete*" propagation="REQUIRED"/>
<tx:method name="*drop*" propagation="REQUIRED"/>
<tx:method name="*remove*" propagation="REQUIRED"/>
<tx:method name="*clear*" propagation="REQUIRED"/>
<tx:method name="*" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<!-- 切面和切入点绑定-->
<aop:config>
<aop:pointcut id="mycut" expression="execution(* com.spike.service.impl.*.*(..))"/>
<aop:advisor advice-ref="myadive" pointcut-ref="mycut"></aop:advisor>
</aop:config>
(4)事务管理器
但凡要进行事务的处理,必须添加事务管理器。
事务管理器是根据不同的技术或框架来生成提交和回滚的对象。
JDBC: Connection con.commit(); con.rollback();
MyBatis: SqlSession sqlSession.commit(); sqlSession.rollback();
Hibernate: Session session.commit(); session.rollback();
MyBatis框架的事务管理器是:DataSourceTransactionManager
19、Spring中常见的设计模式
(1)工厂模式:Spring通过工厂模式BeanFactory,ApplicationContext创建Bean对象。
(2)代理设计模式:SpringAOP的实现,底层使用了动态代理模式。
(3)单例模式:Spring中的Bean默认都是单例的。
(4)模板方法模式:Spring中jdbcTemplate,hibernateTemplate等以Template结尾的类都用到了模板模式。
(5)装饰模式:我们的项目需要连接多个数据库,而不同的客户在访问时可能会访问不同的数据库,这种模式可以让我们根据用户的需求动态的切换数据库。
(6)观察者模式:Spring的事件驱动是观察者模式的应用。
(7)适配器模式:SpringAOP的增强功能使用到了适配器模式。
笔者的话
写到这里已经2w字了,可能还有些知识点未列出,笔者在后续会进行更新。同时也恭喜你成功的学到了最后👍,如果你将本文的这些知识点都理解透了,那么你对Spring框架的理解其实已经非常好了💪,可以奖励自己一个🍗。
笔者码字不易,如果你觉得本文对你有一点点帮助的话,请给笔者点个赞。🌹🌹🌹
最后声明:本文为笔者原创内容,如需转载请附上原文出处链接。