Spring
Spring 一般指的是Spring家族里的SpringFremaker
Spring 家族 Springfremaker springdata springboot springcloud springsecurity springsession等
springfremaker 即spring主要是ioc(控制权反转)/di(依赖注入) aop 事务 JdbcTemplate
创建spring项目
通过创建maven 来手动构建spring ,加spring-context 依赖,
看依赖结构,spring-context 依赖有aop,beans,core,expression四个子依赖且互相依赖,根据路径最短优先原则,远的重复的依赖会变灰,不通过那条路径调用
在resource 文件夹下加配置文件,applicationContenxt .xml 将bean注入到spring容器中,底层是xml解析加反射
例:<bean class="com.qfedu.demo.User" id="user"/>
通过类名反射初始化对象,id就是对象名,为user
IOC 控制反转(控制权的反转)
ioc 控制反转
A.没spring容器,service 层和dao 层,Userservice 类里new 相应的一个userdao类,userdao 的控制权在userservice里,
B.有spring容器,userdao,在spring容器(也叫工厂)里new即通过反射初始化,告诉spring容器我是dao类和实例化对象名,这也叫注册到spring容器,
userservice 需要userdao 就需要像spring容器申请,控制权在spring 容器中了,这就是ioc 控制反转
spring容器初始化,注入到spring容器中的bean 也会初始化。
默认一般注入到spring 容器的类,只有一个,即一个bean 为User类,id为user注入到spring容器,不会再有一个bean为User类,
但是是可以有的,即再有一个bean也为User类,id为user2注入到spring容器,这时就有两个User类对象,user,user2
向spring 容器中申请获取对象,可以用id,也可以用类名.class,用类名.class获取对象,如果有两个相同类的bean,就会报错,找到两个对象,不确定要的哪个
只有一个的话,三种方式获取的对象,多次重复获取的对象都是同一个,就是那个在spring容器初始化的对象。
public static void main(String[] args){
//加载Spring 配置文件,配置文件加载出来后,会自动的创建出Spring容器
//创建出来的ctx 相当于就是 Spring 容器
//当spring 容器初始化之后。配置文件中配置的所有Bean 就都被初始化了
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//1.通过 id 获取实例
User u1 =(User) ctx.getBean("user");
//2.通过 .class 即类型 获取实例
User u2 =ctx.getBean(User.class);
//3.根据id找bean,但是同时要求类型为User
User u3 =ctx.getBean("user",User.class);
}
spring 容器初始化bean,没有设置属性,就是通过无参构造方法实例化
DI(依赖注入)
通过bean实例化有一些复杂的属性的对象
3种属性注入方式
1.构造方式注入
<constructor-arg name="id" value="99"/>
2.set方法注入(推荐)
<property name="id" value="100"/>
3.p名称空间注入(本质上还是set方法注入,很少使用不推荐)
<!--
给 User 中的各个属性赋值。
向上面的案例,什么都没指定的时候,默认调用了 User 的无参构造方法完成实例化的
下面案例演示通过构造方法给各个属性赋值
-->
<bean class="com.qfedu.demo.p1.User" id="user2">
<!--这个配置,将来就不会调用 User 的无参构造方法完成对象的初始化了,这里将来调用的是有参构造方法完成对象的初始化-->
<constructor-arg name="id" value="99"/>
<constructor-arg name="username" value="zhangsan"/>
</bean>
<!--
也可以通过 set 方法为各个属性注入值
-->
<bean class="com.qfedu.demo.p1.User" id="user3">
<!--
这个 property 最终调用的是各个属性的 set 方法进行值的设置的
这里没有指定构造方法相关的属性,所以 user3 对象的创建使用默认的无参构造方法
-->
<property name="id" value="100"/>
<property name="username" value="lisi"/>
</bean>
各种注入属性:
- 基本数据类型+String 直接使用标签的 value 属性注入
- 对象
- 外部定义好一个对象bean,然后通过 ref 引用对象
- 直接在需要的地方通过 bean 标签定义一个对象(局限性,定义好的 bean 无法复用)
- List 集合:list
- 数组:array
- Map:
- Properties :props
<!--向 Spring 容器注入一个 Author 对象-->
<bean class="com.qfedu.demo.p2.Author" id="author">
<property name="id" value="100"/>
<property name="name" value="罗贯中"/>
</bean>
<!--向 Spring 容器注入 Book 对象-->
<bean class="com.qfedu.demo.p2.Book" id="book">
<property name="id" value="99"/>
<property name="name" value="三国演义"/>
<!--author 属性的值来自 author 对象-->
<property name="author" ref="author"/>
<property name="tags">
<!--如果类型是 List,这里就用 list 标签-->
<list>
<value>通俗小说</value>
<value>小说</value>
</list>
</property>
<property name="publish">
<!--如果类型是 map,这里就用 map 标签-->
<map>
<!--map 是键值对的形式,所以设置的时候,有 key 有 value-->
<entry key="name" value="人民文学出版社"/>
<entry key="address" value="北京"/>
</map>
</property>
<property name="other">
<!--props 标签用于 Properties 对象-->
<props>
<prop key="price">30</prop>
<prop key="publishTime">2022-01-01</prop>
</props>
</property>
</bean>
工厂类型
<!--
这是一个静态工厂注入
系统会自动调用 OkHttpFactory01 的 okHttpClient 方法,将该方法返回的结果注入到 Spring 容器中
-->
<bean class="com.qfedu.demo.p4.OkHttpFactory01" factory-method="okHttpClient"/>
<!--
实例工厂注入
-->
<bean class="com.qfedu.demo.p4.OkHttpFactory02" id="factory02"/>
<!--
factory02 中的 okHttpClient 方法必须通过一个对象来调用
-->
<bean class="okhttp3.OkHttpClient" id="okHttpClient02" factory-bean="factory02" factory-method="okHttpClient"/>
自动注入加 autowire=“byType/byName”
单例、多例模式 scope=“singleton/prototype” 多例模式每次生成的对象不一样,且对象由jvm垃圾回收器回收销毁
scope 属性值还有 request,session,application ,这三个在web环境生效
spring 工厂是饿汉式
生命周期注解
@PostConstruct //构造方法初始化 后
public void init(){
System.out.println("init method executed");
}
@PreDestroy //销毁前
public void destroy(){
System.out.println("destroy method executed");
}
单例bean: singleton
随工厂启动创建 ==》 构造方法 ==》 set方法(注入值) ==》 init(初始化) ==》 构建完成 ==》随工厂关闭销毁
**多例bean: ** prototype
被使用时创建 ==》 构造方法 ==》 set方法(注入值) ==》 init(初始化) ==》 构建完成 ==》[JVM垃圾回收销毁]
java代码配置bean(即使用注解)
@Configuration 注解表示这个是我的配置类,这个配置类的作用相当于 applicationContext.xml 配置文件的作用
@Bean 注解表示当前方法的返回值是 Spring 容器中的一个 Bean,其实这里就相当于 XML 文件中的 bean 标签。默认情况下,方法名就是 bean 名称,如果想为 bean 自定义名字,用@Bean(“user2”)
@Scope 对应xml配置文件的scope属性 单例、多例
@Configuration 和@Component 的区别
只有一个bean时,或者bean 没有引用另外一个bean时,是一样的,相当于 applicationContext.xml 配置文件的作用
如果是多个bean且存在引用机会时,类上的注解是 @Configuration ,会优先选择引用spring容器里的对象,而不是自己单独new一个对象,类上的注解是 @Component,则直接单独new一个对象(不会先去 Spring 容器中查找了),且idea有警告,解除警告可以把对象当参数传给需要引用的方法,或者换成Configuration注解
@Qualifier(“author01”) 注解表示告诉 Spring 容器,我这里需要的 author 名为 author01。 有多个相同类型的bean时用
@Profile 组合注解,里面有@Conditional 注解,相当于条件注解,多环境切换时用ctx.getEnvironment().setActiveProfiles();切换
@Conditional 条件注解,条件满足bean才会注册
自动包扫描注解
@Repository,@Service,@Controller,@Component 和@Bean 作用一样
@Repository:一般加在 dao 层,表示将当前类注册到 Spring 容器中,默认情况下,bean 名称就是类名首字母小写。
@Service:一般加在 service 层,表将当前类注册到Spring 容器中, 当 Spring 容器调用当前方法初始化 UserService 的时候,会自动去 Spring 容器中查找到 UserDao 并作为参数传进来。如果 Spring 容器中没有 UserDao,那么 UserService 就会创建失败。
@Controller:一般加在 servlet/controller 层,表示将当前类注册到 Spring 容器中
@Component:作用1:同@Configuration, 作用2:同@bean,身份不明的组件,用这个注解
@Autowired :作用1:当存在多个构造方法的时候,即无参有参构造方法都存在,可以使用此注解在构造方法上来指定到底使用哪个构造方法,作用2:表去Spring 容器中查找类型为UserDao 的实例,并赋值给当前变量,@Autowired UserDao userDao;
@ComponentScan(basePackages = “com.qfedu.demo.p1”) :这个注解用来配置需要扫描哪里的组件,basePackages 指明需要扫描的包,这个包以及子包下的带有 @Repository、@Service、@Controller 以及 @Component 注解的类,都会被注册到 Spring 容器中,一般配合@Configuration 使用
如果没扫描到,@Bean之类作用的注解就不会生效,就会注册不到spring容器中,只是声明,没有实例化
xml配置文件里自动包扫描 <context: component-scan base-package=“”/>
/**
* @Configuration 注解表示这个是我的配置类,这个配置类的作用相当于 applicationContext.xml 配置文件的作用
*/
@Configuration
public class JavaConfig {
/**
* @return
* @Bean 注解表示当前方法的返回值是 Spring 容器中的一个 Bean,其实这里就相当于 XML 文件中的 bean 标签
* <p>
* 默认情况下,方法名就是 bean 名称,如果想为 bean 自定义一个名字,那么可以在 @Bean 注解中进行设置。
*/
@Bean("user2")
User user() {
User user = new User();
user.setId(99);
user.setUsername("lisi");
return user;
}
}
代理设计模式
-
基于JDK(不需要额外引入jar):被代理的对象存在接口,基于接口
//Proxy.newProxyInstance()用来生产代理对象 Proxy.newProxyInstance(loader,interfaces,h)
Proxy.newProxyInstance(loader,interfaces,h) 方法用来生成代理对象。返回值就是生成的代理对象
参数 loader:这是当前的 classloader,interfaces:这是代理的对象所实现的接口, h:代理的核心逻辑就是这个 h,InvocationHandler h = new InvocationHandler() {方法}这个动态代理,本质上就是你给出来一个接口,JDK 自动给这个接口生成一个实现类,这个实现类中,方法的执行逻辑就是 java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) 方法的执行逻辑
Proxy.newProxyInstance 方法的返回值其实是 com.sun.proxy.$Proxy0 对象,这个对象本质上是根据 interfaces 参数生成的一个类的对象,
也可以理解为,有一个接口和接口实现类,现在需要重写接口实现类的方法但是不改动接口实现类代码,用jdk动态代理接口实现类,通过Proxy.newProxyInstance(loader,interfaces,h) 反射创建一个接口的实现类的对象,该实现类是com.sun.proxy.$Proxy0 ,其实现类的重写方法在h,即new InvocationHandler() {invoke方法}中重写或执行其他操作,method.invoke(calculatorImpl, objects);,实现不改接口实现类,重写方法。
例子
接口
public interface Calculator { int add(int a, int b); void minus(int a, int b); }
接口实现类
public class CalculatorImpl implements Calculator{ @Override public int add(int a, int b) { return a + b; } @Override public void minus(int a, int b) { System.out.println(a + "-" + b + "=" + (a - b)); } }
jdk动态代理实现
public class ProxyDemo { public static void main(String[] args) { CalculatorImpl calculatorImpl = new CalculatorImpl(); //类加载器,代理对象从由哪一个类加载器负责加载 ClassLoader loader = ProxyDemo.class.getClassLoader(); //代理对象的类型 即有哪些方法 Class<?>[] interfaces = new Class[]{Calculator.class}; //当调用代理对象其中的方法时需执行的代码 InvocationHandler h = new InvocationHandler() { /** * @param o 代理对象本身 * @param method 被代理的方法,对于这里来说,无非就是 add 和 minus 方法 * @param objects 方法的参数,可以自己new objects[]{a,b...} */ @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { long startTime = System.nanoTime(); //执行代理的方法,说白了就是执行 add 和 minus 方法 Object result = method.invoke(calculatorImpl, objects); long endTime = System.nanoTime(); //获取执行的方法名称 String name = method.getName(); System.out.println(name + " 方法执行耗时 " + (endTime - startTime)); //这个retrun 返回的 就是代理的实现类方法的返回值 return result; } }; //将生成的代理对象强转成 Calculator,不能转为CalculatorImpl,类似多态子类不能互转 Calculator calculator = (Calculator) Proxy.newProxyInstance(loader, interfaces, h); int add = calculator.add(3, 4); System.out.println("add = " + add); calculator.minus(5, 6); } }
基于cglib(念作c g lai be)(需要引入外部jar):被代理的对象可以没有接口,基于继承
一个普通类(需要的代理类),没有接口也能用cglib实现动态代理,不改类的代码,对方法进行随意操作,具体操作就是创建一个类实现MethodInterceptor方法拦截器接口,其重写方法intercept 与jdk动态代理中的invoke类似,在其操作代理类的方法methodProxy.invokeSuper(o, objects);使用时需要创建一个字节码增强器Enhancer,调用enhancer的setSuperclass,setCallback,create方法获取代理类的子类即代理对象,在强转为代理类,使用代理类的方法
类
public class Dog {
public void run(String name) {
System.out.println(name + " 在跑");
}
}
cglib动态代理实现
//这是一个方法拦截器
public class DogInterceptor implements MethodInterceptor {
/**
* 所有被拦截(被代理)的方法最终都会调用到这个拦截器中
* <p>
* 这个方法有点类似于 JDK 动态代理中的 invoke 方法
* <p>
* cglib 动态代理,相当于给 Dog 这个类生成了一个子类,相当于新建了一个子类继承自 Dog
* @param o 被代理的对象
* @param method 代理的方法
* @param objects 方法的参数
* @param methodProxy 方法的代理对象
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//获取被拦截的方法名称
String name = method.getName();
long startTime = System.nanoTime();
//调用父类的方法
Object result = methodProxy.invokeSuper(o, objects);
long endTime = System.nanoTime();
System.out.println(name + " 方法执行耗时 " + (endTime - startTime));
return result;
}
}
测试
public class DogDemo {
public static void main(String[] args) {
//创建一个字节码增强器
Enhancer enhancer = new Enhancer();
//设置父类是 Dog,将来就根据 Dog 来生成子类
enhancer.setSuperclass(Dog.class);
enhancer.setCallback(new DogInterceptor());
//获取代理对象,这里拿到的 dog 对象,其实是 Dog 子类的一个对象
Dog dog = (Dog) enhancer.create();
System.out.println("dog.getClass() = " + dog.getClass());
dog.run("小黑");
}
}
Spring 中的 AOP 底层就是动态代理:
- 在 Spring 中,如果代理的对象有接口,默认就自动使用 JDK 动态代理;如果没有接口,就是用 CGLIB 动态代理。
- Spring Boot2.0 之前和 Spring 是一样的;2.0 之后,Spring Boot 中的 AOP 统一都是 CGLIB 动态代理。
AOP(面向切面编程)
-
概念
AOP(Aspect Oriented Programming),即面向切面编程,利用一种称为"横切"的技术,剖开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
实际应用就是在一个正在运行的代码中,动态的加入新代码,就是面向切面编程
-
AOP开发术语
- 连接点(Joinpoint):连接点是程序类中客观存在的方法,可被Spring拦截并切入内容。
- 切点(Pointcut):被Spring切入连接点。
- 通知、增强(Advice):可以为切入点添加额外功能,分为:前置通知、后置通知、异常通知、环绕通知、返回通知等。
- 目标对象(Target):代理的目标对象
- 引介(Introduction):一种特殊的增强,可在运行期为类动态添加Field和Method。
- 织入(Weaving):把通知应用到具体的类,进而创建新的代理类的过程。
- 代理(Proxy):被AOP织入通知后,产生的结果类。
- 切面(Aspect):由切点和通知组成,将横切逻辑织入切面所指定的连接点中。
-
作用: 通过动态代理类为原始类的方法添加额外功能
-
实战例子1 基于JDK
导入依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.1.6.RELEASE</version> </dependency>
接口
public interface Calculator { int add(int a, int b); void minus(int a, int b); }
接口实现类
public class CalculatorImpl implements Calculator { @Override public int add(int a, int b) { // int i = 1 / 0; return a + b; } @Override public void minus(int a, int b) { System.out.println(a + "-" + b + "=" + (a - b)); } }
设置切面类(即动态代理类),有五种通知,来为原始类的方法添加额外功能
-
前置通知:目标方法执行直接触发 @Before 也会在测试类中用,执行test之前都会先执行before
-
后置通知:目标方法执行之后触发 @After
-
返回通知:当目标方法有返回值的时候触发 @AfterReturning
-
异常通知:当目标方法抛出异常的时候触发(全局异常处理可用他) @AfterThrowing
-
环绕通知:以上四个的集大成者 @Around
package com.qfedu.demo; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; /** * 这是我的日志切面类 * <p> * Spring AOP 包含五种通知: * <p> * 1. 前置通知:目标方法执行直接触发 * 2. 后置通知:目标方法执行之后触发 * 3. 返回通知:当目标方法有返回值的时候触发 * 4. 异常通知:当目标方法抛出异常的时候触发(全局异常处理可用他) * 5. 环绕通知:以上四个的集大成者 */ public class LogAspect { /** * 这是前置通知,目标方法执行之前,这个方法会被触发 * 目标方法也就是我们要拦截的方法 */ public void before(JoinPoint jp) { //获取目标方法的名称(被拦截的方法名称) String name = jp.getSignature().getName(); System.out.println(name + " 方法开始执行了。。。"); } public void after(JoinPoint jp) { Signature signature = jp.getSignature(); String name = signature.getName(); System.out.println(name + " 方法执行结束了..."); } /** * 返回通知 * <p> * 我想知道目标方法的返回值到底是多少? * <p> * 注意这个接收目标方法返回值的参数的类型,必须要匹配,用了 int,那么当返回值类型为 int 的时候,才会进入到当前方法中 * * @param jp */ public void returning(JoinPoint jp, Object result) { System.out.println("返回通知。。。" + result); } /** * 异常通知 * 只有当目标方法抛出异常的时候,这个方法会被触发 * <p> * 注意,只有拦截的异常类型能够匹配上实际抛出的异常,这个方法才会被触发。 * 例如如果这里的参数是 ArithmeticException,实际抛出的异常是空指针异常,那么这个方法就不会被触发 * <p> * 如果向拦截所有异常,那么这里的参数类型可以使用 Exception * * @param jp */ public void exception(JoinPoint jp, ArithmeticException e) { System.out.println(jp.getSignature().getName() + " 方法抛出异常了 " + e.getMessage()); } /** * 环绕通知,这个是集大成者 */ public Object around(ProceedingJoinPoint pjp) { //这个方法类似于之前的 method.invoke() 方法 try { //这一句其实是在调用目标方法 System.out.println("====这里就相当于前置通知===="); long startTime = System.nanoTime(); Object proceed = pjp.proceed(); long endTime = System.nanoTime(); System.out.println(pjp.getSignature().getName() + " 方法执行耗时:" + (endTime - startTime)); System.out.println("====这里就相当于后置通知===="); return proceed; } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println("====这里相当于异常通知"); } return null; } }
配置文件 applicationContext.xml
配置切点:即要拦截哪些方法?id 就是切点名称,expression 就是切点的表达式
execution(int com.qfedu.demo.service.CalculatorImpl.add(int,int)) 表示拦截 com.qfedu.demo.service.CalculatorImpl.add 方法一开始的 * 表示返回值任意
后面的 * 表示方法名任意
… 表示参数任意:参数可有可无;如果有参数的话,参数类型、个数也是任意的execution( com.qfedu.demo.service.CalculatorImpl. * (…)) 拦截 CalculatorImpl 类中的所有方法*
execution( com.qfedu.demo.service. * . * (…)) 拦截 com.qfedu.demo.service 包下的所有类的所有方法*<bean class="com.qfedu.demo.service.CalculatorImpl" id="calculator"/> <bean class="com.qfedu.demo.LogAspect" id="logAspect"/> <!-- 开始 AOP 的配置 --> <aop:config> <!--配置切点:即要拦截哪些方法?id 就是切点名称,expression 就是切点的表达式--> <aop:pointcut id="pc1" expression="execution(* com.qfedu.demo.service.*.*(..))"/> <aop:aspect ref="logAspect"> <!--这是定义一个前置通知,即将方法拦截下来之后,目标方法执行之前会触发的方法--> <aop:before method="before" pointcut-ref="pc1"/> <!--配置后置通知--> <aop:after method="after" pointcut-ref="pc1"/> <!--返回通知,returning 表示目标方法的返回值映射的参数--> <aop:after-returning method="returning" pointcut-ref="pc1" returning="result"/> <aop:after-throwing method="exception" pointcut-ref="pc1" throwing="e"/> <aop:around method="around" pointcut-ref="pc1"/> </aop:aspect> </aop:config> </beans>
-
实战例子2 用Java代码写aop,即用注解
导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.18</version>
</dependency>
<!--如果要用 Java 代码去配置 AOP,还需要 aspectjweaver 和 aspectjrt 两个依赖,但是由于 spring-aspects 中已经依赖的 aspectjweaver,所以这里我们只需要加一下 aspectjrt 即可。-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.7</version>
</dependency>
切面类 三个注解 @Component 表示将 LogAspect 注入到 Spring 容器中 @Aspect 表示当前类是一个切面@EnableAspectJAutoProxy 开启切面的自动代理
@Pointcut(“execution( * com.qfedu.demo.service. * . * (…))”)
@Before(“pc()”) @After(“pc()”) @AfterReturning(value = “pc()”, returning = “r”)
@AfterThrowing(value = “pc()”, throwing = “e”)
@Around(“pc()”)
package com.qfedu.demo.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
/**
* LogAdvice
* <p>
* LogAspect=PointCut+LogAdvice
*
* @Component 表示将 LogAspect 注入到 Spring 容器中
* @Aspect 表示当前类是一个切面
* @EnableAspectJAutoProxy 开启切面的自动代理
*/
@Component
@Aspect
@EnableAspectJAutoProxy
public class LogAspect {
/**
* 统一定义的切点
*/
@Pointcut("execution(* com.qfedu.demo.service.*.*(..))")
public void pc() {
}
// @Before("execution(* com.qfedu.demo.service.*.*(..))")
@Before("pc()")
public void before(JoinPoint jp) {
System.out.println(jp.getSignature().getName() + " 方法开始执行啦...");
}
// @After("execution(* com.qfedu.demo.service.*.*(..))")
@After("pc()")
public void after(JoinPoint jp) {
System.out.println(jp.getSignature().getName() + " 方法执行结束啦...");
}
@AfterReturning(value = "pc()", returning = "r")
public void returning(JoinPoint jp, Object r) {
System.out.println(jp.getSignature().getName() + " 方法的返回值是 " + r);
}
@AfterThrowing(value = "execution(* com.qfedu.demo.service.*.*(..))", throwing = "e")
public void throwing(JoinPoint jp, Exception e) {
System.out.println(jp.getSignature().getName() + " 方法抛出 " + e.getMessage() + " 异常");
}
@Around("execution(* com.qfedu.demo.service.*.*(..))")
public Object around(ProceedingJoinPoint pjp) {
try {
Object proceed = pjp.proceed();
return proceed;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
}
测试
public class Demo01 {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
Calculator calculator = ctx.getBean(Calculator.class);
System.out.println("calculator.add(3, 4) = " + calculator.add(3, 4));
}
}
bean 是什么?
bean 就是spring工厂(容器)中的类,spring工厂中只能有类,不能有接口。需要注入到spring工厂中的类可能是普通类,spring工厂就直接new,也可能是接口,spring工厂通过动态代理生成该接口的实现类
事务
概念:事务是逻辑上的一组操作,要么都执行,要么都不执行。
ACID 四大特性
原子性(Atomicity): 一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
一致性(Consistency): 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。
隔离性(Isolation): 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括未提 交读(Read uncommitted)、提交读(read committed)、可重复读(repeatable read)和序列化(Serializable)。
持久性(Durability): 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
事务的属性
事务的传播性
事务传播行为是为了解决业务层方法之间互相调用的事务问题。 当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。传播性是解决两个以上的事务共存,怎么处理的问题
7 种传播性,重点看propagation_required(默认的) propagation_requires_new propagation_nested
实战例子
导入依赖
<dependency>
<groupId>org.springframework</groupId>
<!-- spring的基础环境要用-->
<artifactId>spring-context</artifactId>
<version>5.3.18</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<!-- 要用事务,要用jdbcTemplate -->
<artifactId>spring-jdbc</artifactId>
<version>5.3.18</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<!-- 单元测试要用-->
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<!-- 数据库连接池要用-->
<artifactId>druid</artifactId>
<version>1.2.9</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<!-- 数据库连接要用-->
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<!-- aop要用下面两个-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.9.1</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
</dependency>
<!--slf4j 用于日志的三个依赖 前面设置版本 <properties> <slf4j.varsion>1.7.36</slf4j.varsion></properties>-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
<scope>runtime</scope>
</dependency>
加日志的配置
log4j.properties
# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
logback.xml
config包下的配置类 JavaConfig 配置数据库连接,使用jdbcTemplate
@Configuration
@ComponentScan(basePackages = "com.qfedu.demo")
@PropertySource("classpath:db.properties")
@EnableTransactionManagement
public class JavaConfig {
@Value("${db.username}")
String username;
@Value("${db.password}")
String password;
@Value("${db.url}")
String url;
@Bean
DataSource dataSource() {
DruidDataSource ds = new DruidDataSource();
ds.setUrl(url);
ds.setPassword(password);
ds.setUsername(username);
return ds;
}
@Bean
PlatformTransactionManager transactionManager() {
DataSourceTransactionManager manager = new DataSourceTransactionManager();
manager.setDataSource(dataSource());
return manager;
}
}
a事务中的方法调用了b事务中的方法,事务怎么算,传播性是当前事务被别人调用才能有效果,决定事务怎么做,所以只有B类加了传播性的值
开启一个独立的事务必须建立一个新的jdbc连接
@Component
public class A {
@Autowired
B b;
@Transactional
public void aMethod {
//do something
sout("amethod");
b.bMethod();
}
}
另一个类
@Component
public class B {
@Transactional(propagation=propagation.xxx)
public void bMethod {
//do something
sout("bmethod");
}
}
传播性为propagation_required(默认的),如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
传播性为propagation_requires_new,会建立一个新的独立的事务,如果当前存在事务,当前事务挂起,正常运行互不干扰,除非报错
传播性为propagation_nested,如果当前存在事务,则该事务是当前事务的子事务,属于同一个jdbc连接,且先执行当前事务,直到调用子事务的时候,当前事务挂起且在当前事务设置断点savepoint,执行子事务,执行结束在执行当前事务,最后在提交事务。子事务执行出现异常回滚不影响主事务的执行,主事务出现异常回滚会带着子事务回滚
事务超时:多少毫秒之内,事务还没提交,就自动回滚,可以避免死锁
增加改操作上的事务,都是非只读事务,查询如果加事务,那么就是只读事务
@Transactional 的作用范围
- 方法 :推荐将注解使用于方法上,不过需要注意的是:该注解只能应用到 public 方法上,否则不生效。
- 类 :如果这个注解使用在类上的话,表明该注解对该类中所有的 public 方法都生效。
- 接口 :不推荐在接口上使用。
@Transactional 的工作机制是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果 目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口,会使用 CGLIB 动态代理。 多提一嘴: createAopProxy() 方法 决定了是使用 JDK 还是 Cglib 来做动态代理
- @Transactional 注解只有作用到 public 方法上事务才生效,不推荐在接口上使用;
- 避免同一个类中调用 @Transactional 注解的方法,这样会导致事务失效;
- 正确的设置 @Transactional 的 rollbackFor 和 propagation 属性,否则事务可能会回滚失败
Spring 事务回顾
-
事务的隔离级别:read uncommitted(脏读、不可重复读、幻读);read committed(不可重复读、幻读),repeatable read(默认的,不存在问题),serializable(序列化的,一次只能执行一个事务)。
-
Spring 事务:
-
编程式事务:代码侵入,所以不推荐
- TransactionTemplate 来实现
- PlatformTransactionManager 来实现。
-
声明式事务:代码无侵入或者侵入少,推荐使用这种
-
XML 文件配置声明式事务:配置数据源、配置事务管理器、配置事务属性、配置 AOP
-
Java 配置声明式事务:配置数据源、配置事务管理器、开启事务注解、在目标方法上添加事务注解
@EnableTransactionManagement
-
-
事务失效
事务底层基于动态代理的
Myisam 不支持事务
同一个类调用@Transactional 注解的方法
在非 public 方法上使用事务