本篇论述的内容
- spring历史;
- spring Ioc;
- spring自动装配;
- spring aop;
- spring 事务
注:本文内容来自b站狂神说java spring框架学习的笔记
一、spring简介
历史
- 2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。
- 2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。
- Rod Johnson是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。
Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术,官方下载地址 : https://repo.spring.io/libs-release-local/org/springframework/spring/,GitHub : https://github.com/spring-projects。
简介
参考博客:
spring是一个轻量级、非侵入式的开源免费框架(叫做容器,会更合适)。它的核心是控制反转(IOC)和面向切面(AOP),同时它对有对事务的支持,各种框架的支持,包罗万象。一句话概括就是:spring是一个轻量级的控制反转和面向切面的容器。
spring大约有20个组件,这些组件被分别被整合在核心容器(Core Cantainer)、AOP(Aspect oriented programming)、设备支持(Instrumentation)、数据访问与集成(Data access/integration)、Web、消息(Messaging)、Test 等6个模块中。模块结构图如下:
- 核心容器:spring核心容器是其他模块建立的基础,有spring-core、spring-beans、spring-contenxt、spring-context-support、spring-expression(spring表达语言)等模块组成。
- spring-core:提供了框架的基本组成不发,包括控制反转(inversion of control)和依赖注入(Dependency injection)功能。
- spring-bean:提供BeanFactory,是工厂模式的经典实现,Spring将管理的对象称为bean。
- spring-context:建立在core和bean之上,提供框架式的对象访问方式,是任何对象访问和配置的媒介,applicationcontext接口是context模块的焦点。
- spring-contetx-support:支持整合第三方库到spring应用程序上下文,特别是用于高速缓存(Ehchache、JCache)和任务调度(CommmonJ、Quartz)的支持。
- spring-expression:提供表达式语言支持运行时查询和操作对象图。
- AOP和设备支持:由spring-aop、spring-aspects、spring-instrument等三个模块组成。
- spring-aop:这是spring的另一核心模块,提供符合Aop要求的面向切面编程,以动态代理为基础,允许定义方法拦截器和切入点,将代码按功能分离,便于解耦。
- spring-aspects:提供AspectJ(一个功能强大的AOP框架)的集成。
- spring-instrument:是AOP的一个支援模块,提供类植入(instrument)支持和类加载器实现,在特定应用服务器中使用。主要作用是在JVM中,生成一个代理类,程序员通过代理类在运行时修改类的字节,从而改变一个类的功能,实现AOP功能。
- 数据访问与集成:由spring-jdbc、spring-orm、spring-jms和spring-tx等五个模块组成。
- spring-jdbc:提供jdbc的抽象层,消除繁琐的jdbc编码和数据库厂商特有的错误代码解析,用于简化jdbc。主要提供jdbc模板方式、关系数据库对象化方式、simplejdbc方式、事务化管理来简化jdbc编程,主要实现类是:jdbctemplate、simplejdbctemplate、nameparameterjdbctemplate。
- spring-orm:是orm框架支持模块,主要是集成hibernate,java persistence API(JPA)、java data objects(JDO)用于资源管理、数据访问(DAO)的实现和事务策略。
- spring-oxm:主要提供一个抽象层以支撑oxm(将java对象转为xml数据,或将xml数据映射成java对象),如:JAXB、Castor、xstream等。
- spring-jms:提供java信息传递服务,包括用于生产和使用消息的功能,提供spring-messaging模块的集成。
- Web:由spring-websocket、spring-webmvc、spring-web、portlet和spring-webflux等五个模块组成。
- spring-websocket:实现异步双工协议,实现websocket和sockJS,提供socket通信和web端推送功能。
- portlet:实现web模块功能的聚合,类似与servlet模块的功能,提供了portlet环境下的mvc实现。
- spring-webflux:是一个非堵塞函数式编程reactive web框架,可以用来建立异步的,非阻塞,事件驱动服务,且扩展性非常好。
- 消息(Messaging):即spring-messaging,该模块提供消息传递体系结构和协议的支持。
- Test:主要是为测试提供支持的,支持使用JUnit或TestPNG对spring组件进行单元测试和集成测试。
spring5各模块(jar包)之间的依赖关系如下:
总述: spring框架,提供了一种简易的开发方式,将避免哪些可能致使代码变得繁杂混乱的大量业务或工具对象。更通俗得说,就是通过spring来管理我们业务使用到的对象,包括对象的创建、销毁等,被spring管理的对象,通常叫做bean。这些被管理的对象和业务逻辑之间,sprig通过IOC(控制反转)架起桥梁,IOC可以说是spring最核心的桥梁。
二、spring Ioc
2.1、IOC(DI注入)容器(控制反转)的思想(解耦设计)
IOC的解释,参考博文1、参考博文2
我的理解是:对于有依赖关系的对象A和B(A依赖于B),没有IOC容器时,A需要使用到B,那就需要自己主动去创建。但是有了IOC容器时,A需要使用B,就由IOC容器给到B。因此,IOC这个第三者的出现,使得A获得B的方式从主动变为了被动,主动权变为了被动权,因此IOC也叫控制反转。那既然是控制反转,那么哪些东西被反转了呢?2004年,Martin Fowler给出了答案,即获得依赖对象的过程由自身管理变为了IOC容器主动注入,也就是说A获得B的相关依赖关系(这个依赖关系可能是B的对象、资源、常量数据等,根据A的需要来决定),由IOC容器去注入了,因此IOC也叫依赖注入(DI)。IOC,主要阐述的是获得依赖对象的方式,而DI主要阐述的是依赖关系的获取方式,两者表述的都是同一种解耦的思想:即通一个第三者IOC容器,将相互依赖的对象,变得没有关系了,这些对象和别的对象都无关了,对象只和IOC容器有关了,就完成耦合对象之间的解耦。(一个简单的比喻,就是本来如胶似漆在一起的情侣变成了异地恋啦,男女对象之间的恋爱关系,只能通过网线[IOC]来维系了,不通过网线,两者谁也干扰不到谁)。
简述推导:
假设现在有一个MVC分层架构的应用,通过controller层提供对外接口,通过service层提供具体实现,在service层有一个ApplyService服务接口,它有一个实现类AppyProServiceImpl。
没有spring
我们要在controller调用服务,如果没有spring,那么我们就需要自己创建(new)这个服务的实现类实例。
public class ApplyController {
private ApplyService service = new WindAppyServiceImpl();
@RequestMapping("/apply")
public String apply() {
//方法实现将图片保存到本机
return service.saveStudentImage();
}
}
在本地windows调试之后一切正常,现在需要提交代码到linux服务器上去测试。但是,因为windows和linux文件系统不同,需要将实现类WindAppyServiceImpl替换为LinuxAppyServiceImpl。该怎么办?只能去改代码:
public class ApplyController {
private ApplyService service = new LinuxAppyServiceImpl();
...
}
测试正常了,再将代码该回去,继续接下来的开发。这种方式下,程序的耦合度过高(高内聚,低耦合是软件设计一种思想)。
通过spring
注入bean,将ApplyService交给spring管理
<bean name="ApplyService" class="XXX.XXX.XXX.service.impl.WindAppyServiceImpl"/>
然后业务代码中service,由spring去注入即可,
public class ApplyController {
@Autowired
private ApplyService service;
@RequestMapping("/apply")
public String apply() {
//方法实现将图片保存到本机
return service.saveStudentImage();
}
}
服务器测试时,只需要修改配置文件,而无需改动controller层代码;
<bean name="WelcomeService" class="XXX.XXX.XXX.service.impl.MockWelcomeServiceImpl"/>
通过spring,实现了controller和service的解耦。当然,spring的功能不仅仅只是如此,它还能通过单例减少创建无用对象,通过延迟加载优化初始成本等。虽然,不通过spring,也可以实现这些功能,但是spring提供了更好的抽象接口方式对于这些功能进行封装,同时又能灵活配置,能更好的与第三方进行集成。因此使用spring,可以避免重复造轮子,有好的解决方案只需要使用spring进行集成即可。
上面的案例中将bean交给spring管理,是通过xml的显式配置。在spring中,bean的装配机制有三种,分别是:
- 在xml中显式配置;
- 在java中显式配置;
- 隐式的bean发现机制和自动装配。
接下来,主要论述隐式自动装配bean。
三、spring自动装配
spring的自动装配主要是通过两个角度来实现:
- 组件扫描:spring会自动发现应用上下文中所创建的bean(通过context.getBean);
- 自动装配:spring自动满足bean之间的依赖。
3.1 、组件扫描,xml配置
编写测试类:
public class Cat {
public void shout() {
System.out.println("miao~");
}
}
public class Dog {
public void shout() {
System.out.println("wang~");
}
}
public class User {
private Cat cat;
private Dog dog;
private String str;
}
autowire byName (按名称自动装配)
编写spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat" class="com.kuang.pojo.Cat"/>
<!-- 通过注入:autowire="byName" -->
<bean id="user" class="com.kuang.pojo.User" autowire="byName">
<property name="str" value="qinjiang"/>
</bean>
</beans>
测试类
public class MyTest {
@Test
public void testMethodAutowire() {
//通过xml创建上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//通过上下文获取对象
User user = (User) context.getBean("user");
//测试输出
user.getCat().shout();
user.getDog().shout();
}
}
通过byName自动装配的过程:
1、查找类中所有set方法名(如 setCat),获得去掉首字母小写的字符串,即cat;
2、然后去spring容器中,也就是在配置的xml中,寻找是否有此字符串id的对象;
3、如果有,则取出,如果没有,报空指针异常。
autowire byType(按类型自动装配)
修改配置文件,autowired=“byType”
<bean id="user" class="com.kuang.pojo.User" autowire="byType">
<property name="str" value="qinjiang"/>
</bean>
这种方式会取出所有类中管理的类型,去xml中对比类,如果配置的类名相同的bean多于一个,则就会报NoUniqueBeanDefinitionException异常。
3.2、自动装配,注解注入
现在开发中基本都使用注解自动装配bean,而非xml配置。只需要开启注解支持,使用@autowried注解即可。
- @Autowired是按类型自动转配的,不支持id匹配。
- 需要导入 spring-aop的包!
对于上面的测试,使用注解注入,只需要添加注解即可:
public class User {
@Autowired
private Cat cat;
@Autowired
private Dog dog;
private String str;
spring配置文件
<context:annotation-config/>
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat" class="com.kuang.pojo.Cat"/>
<bean id="user" class="com.kuang.pojo.User"/>
@Autowired,默认使用tye注入,若要使用name注入,则需要@Qualifier指定
@Autowired
@Qualifier(value = "cat") //匹配xml配置中的id
private Cat cat;
@Resource注解
- 如果有指定name属性,则按改属性byName查找;
- 没有指定,则使用默认byName进行装配;
- 如果以上方式都不成,则按照byType进行装配;
- 以上方式都找不到,报异常;
public class User {
//如果允许对象为null,设置required = false,默认为true
@Resource(name = "cat2")
private Cat cat;
@Resource
private Dog dog;
private String str;
}
beans.xml
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>
<bean id="cat2" class="com.kuang.pojo.Cat"/>
<bean id="user" class="com.kuang.pojo.User"/>
@Autowired与@Resource异同:
1、@Autowired与@Resource都可以用来装配bean;
2、@Autowired默认按类型装配(属于spring规范),@Resource(属于J2EE复返);
3、作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。
三、spring aop
在学习aop之前,需要先了解下代理模式,它是AOP的底层机制,代理模式分为动态代理和静态代理。
3.1、静态/动态代理
静态代理
代理角色分析:
- 抽象角色:一般使用接口或者抽象类来实现;
- 真实角色:被代理的角色;
- 代理角色:代理真实角色,代理真实角色后,一般会做一些附属操作;
- 客户:使用代理角色来进行一些操作
代码案例:
抽象角色,租房:
//租房
public interface Rent {
public void rent();
}
真实角色,房东:
//真实角色: 房东,房东要出租房子
public class Host implements Rent{
public void rent() {
System.out.println("房屋出租");
}
}
代理角色,房产中介:
public class Proxy implements Rent {
private Host host;
public Proxy() { }
public Proxy(Host host) {
this.host = host;
}
//租房
public void rent(){
seeHouse();
host.rent();
fare();
}
//看房,附加操作
public void seeHouse(){
System.out.println("带房客看房");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
客户:
//客户类,一般客户都会去找代理!
public class Client {
public static void main(String[] args) {
//房东要租房
Host host = new Host();
//中介帮助房东
Proxy proxy = new Proxy(host);
//你去找中介!
proxy.rent();
}
}
分析:本来是租客和房东之间租房关系,现在把租房这个关系交给了中介。那么,关系的双方都只需要去找代理中介,即可完成两者之间的租房操作,并且中介还可以进行一些附属操作。
静态代理的好处:
- 可以使得真实角色更纯粹,不再去关注一些公共的事情;
- 公共的业务由代理来完成,实现了业务的分工;
- 公共业务在发生扩展时,变得更加集中和方便;
缺点:
- 类变多了,工作量增加,降低了开发的效率;
既想要静态代理的好处,又要去除它的缺点,于是就有了动态代理;
动态代理
动态代理的角色和静态代理一样,只是它的代理类是动态的,而静态代理的代理类是每个关系固定写的。动态代理分为两类:一类是基于接口动态代理,一类是基于类的动态代理;
- 基于接口的动态代理:JDK动态代理;
- 基于类的动态代理:cglib;
- 现在使用较多的是javasist生成动态代理;
JDK动态代理
基于JDK的动态代理,需要知道JDK实现动态代理的两个对象:InvocationHandler(接口)、Proxy(类)。
还需要知道JDK是基于接口的动态代理,也就是不编写接口的实现类,而是在运行区间动态创建代理接口的实例。
首先先看下,JDK是如何在运行区间,动态创建接口实例的:
public class Main {
public static void main(String[] args) {
//定义一个InvocationHandler实例,它负责实现接口的方法调用
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if (method.getName().equals("morning")) {
System.out.println("Good morning, " + args[0]);
}
return null;
}
};
/* 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数(接口类的ClassLoader,
要实现的接口数组,处理接口方法调用的InvocationHandler实例)
*/
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), // 传入ClassLoader
new Class[] { Hello.class }, // 传入要实现的接口,至少需要传入一个接口进去
handler); // 传入处理调用方法的InvocationHandler
hello.morning("Bob"); //实列调用方法
}
}
interface Hello {
void morning(String name);
}
总结:通过 Proxy.newProxyInstance动态创建接口实现类,实现类通过实现了InvocationHandler 接口的实列去完成的接口调用。
动态代理的原理实际上就是JVM在运行期动态创建class字节码并加载的过程,JVM帮我们自动编写了一个上述类(不需要源码,可以直接生成字节码)。上述动态代理改写为静态实现类:
public class HelloDynamicProxy implements Hello {
InvocationHandler handler;
public HelloDynamicProxy(InvocationHandler handler) {
this.handler = handler;
}
public void morning(String name) {
handler.invoke(
this,
Hello.class.getMethod("morning", String.class),
new Object[] { name });
}
}
现在,再来编写一个动态代理的实现:
抽象关系(接口):
//抽象角色:租房
public interface Rent {
public void rent();
}
真实角色:房东
//真实角色: 房东,房东要出租房子
public class Host implements Rent{
public void rent() {
System.out.println("房屋出租");
}
}
运行期间,动态生成代理的角色;
invoke方法说明:
//实现此接口,即能重新invoke方法,完成方法的调用
public class ProxyInvocationHandler implements InvocationHandler {
private Rent rent; //接口对象
public void setRent(Rent rent) {
this.rent = rent;
}
//生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类关系
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),this);
}
// proxy : 代理类 method : 代理类的调用处理程序的方法对象。
// 处理代理实例上的方法调用并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHouse();
//核心:本质利用反射实现!
/*
反射会获取房租接口的所有方法。这是动态代理与静态代理的区别所在:静态代理在需要扩展抽象角色,
添加新的方法或新的真实角色时,都需要去修改真实角色和代理对象,而动态代理不需要。
*/
Object result = method.invoke(rent, args);//传入接口对象
fare();
return result;
}
/* ============代理自己的方法============ */
//看房
public void seeHouse(){
System.out.println("带房客看房");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
//租客
public class Client {
public static void main(String[] args) {
//真实角色
Host host = new Host();
//代理实例的调用处理程序
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setRent(host); //将真实角色放置进去!
Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类!
proxy.rent();
}
}
总结
对比案例的静态代理和动态代理,可以发现动态代理是在运行时,生成接口的实现类作为代理角色,这个类能调用接口的所有方法。因此,在修改抽象接口,或增加新的真实角色时,代理类都不需要改变。与静态代理,在扩展需要同时修改真实角色和代理角色相比,动态代理能够代理多个真实角色而不必修改代理角色(因为它是运行时动态生成的),且在扩展时,也不需要修改代理实现(因为是基于抽象角色接口实现的,能调用接口的所有方法);
AOP是:在纵向开关上,做的横向切入。在纵向开发时,在某个节点(一般是业务处理层级),进行横向扩展。
3.2 Spring AOP
AOP(Aspect Oriented Programming)面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护技术。AOP是Spring的一个重要内容,是函数式编程的一种衍生泛型。通过AOP可以对业务逻辑各个部分进行隔离,从而使得业务逻辑各个部分之间的耦合度降低,提高程序可用性。
通过Aop可以在业务逻辑前后,增加前置和后置操作。
AOP在spring中的作用
- 横切关注点:跨越程序多个模块的方法或功能。即,与业务逻辑无关,但是需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等…
- 切面(aspect):横切关注点,被模块化的特殊对象。它是一个类;(切面<=>类)
- 通知(advice):切面必须要完成的工作。即,它是类中的一个方法。
- 目标(target):被通知的对象。
- 代理(Proxy):向目标对象通知之后创建的对象。
- 切入点(poitcut):切面通知执行的“地点”的定义。
- 连接点(joinpoit):与切入点匹配的执行点。
Spring Aop中,通过通知(advice)定义横切逻辑,Spring中支持的五种类型Advice:
spring 中实现AOP的三种方式
spring是在不改变原有的代码情况下,增加横切关注点相关功能,将公共业务(日志、安全、缓存等)和领域业务结合起来,当执行领域业务时,将会把公共业务加入,实现公共业务的重复利用,领域业务更具纯粹,程序员更关注领域业务,其本质还是动态代理。使用AOP需要引入依赖包:
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
1、通过Spring API实现
业务程序:
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加用户");
}
@Override
public void delete() {
System.out.println("删除用户");
}
@Override
public void update() {
System.out.println("更新用户");
}
@Override
public void search() {
System.out.println("查询用户");
}
}
实现Spring API接口
//前置增强
public class Log implements MethodBeforeAdvice {
//method : 要执行的目标对象的方法
//objects : 被调用的方法的参数
//Object : 目标对象
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了");
}
}
//后置增强
public class AfterLog implements AfterReturningAdvice {
//returnValue 返回值
//method被调用的方法
//args 被调用的方法的对象的参数
//target 被调用的目标对象
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + target.getClass().getName()
+"的"+method.getName()+"方法,"
+"返回值:"+returnValue);
}
}
spring的文件中注册 , 并实现aop切入实现 , 注意导入约束 。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean id="userService" class="com.kuang.service.UserServiceImpl"/>
<bean id="log" class="com.kuang.log.Log"/>
<bean id="afterLog" class="com.kuang.log.AfterLog"/>
<!--aop的配置-->
<aop:config>
<!--切入点 expression:表达式匹配要执行的方法-->
<aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
<!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
2、自定义类实现AOP
业务类不变
编写一个自己的切入类
public class DiyPointcut {
public void before(){
System.out.println("---------方法执行前---------");
}
public void after(){
System.out.println("---------方法执行后---------");
}
}
spring中去配置
<!--第二种方式自定义实现-->
<!--注册bean-->
<bean id="diy" class="com.kuang.config.DiyPointcut"/>
<!--aop的配置-->
<aop:config>
<!--第二种方式:使用AOP的标签实现-->
<aop:aspect ref="diy">
<aop:pointcut id="diyPonitcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
<aop:before pointcut-ref="diyPonitcut" method="before"/>
<aop:after pointcut-ref="diyPonitcut" method="after"/>
</aop:aspect>
</aop:config>
3、实现注解实现AOP
业务类不变
编写注解实现增强类
package com.kuang.config;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class AnnotationPointcut {
@Before("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("---------方法执行前---------");
}
@After("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("---------方法执行后---------");
}
@Around("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
System.out.println("签名:"+jp.getSignature());
//执行目标方法proceed
Object proceed = jp.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}
在Spring配置文件中,注册bean,并增加支持注解的配置
<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="com.kuang.config.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>
说明:
通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了
<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class=“true”/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
springboot中自定义注解实现aop
参考博客,避免以后找不到了。在java中,自定义注解一般和拦截器或aop结合使用,使得代码看起来非常优雅。springboot中使用自定义注解:
1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2、定义一个注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
}
3、定义一个切面类
@Aspect // 1.表明这是一个切面类
@Component
public class MyLogAspect {
// 2. PointCut表示这是一个切点,@annotation表示这个切点切到一个注解上,后面带该注解的全类名
// 切面最主要的就是切点,所有的故事都围绕切点发生
// logPointCut()代表切点名称
@Pointcut("@annotation(me.zebin.demo.annotationdemo.aoplog.MyLog)")
public void logPointCut(){};
// 3. 环绕通知
@Around("logPointCut()")
public void logAround(ProceedingJoinPoint joinPoint){
// 获取方法名称
String methodName = joinPoint.getSignature().getName();
// 获取入参
Object[] param = joinPoint.getArgs();
StringBuilder sb = new StringBuilder();
for(Object o : param){
sb.append(o + "; ");
}
System.out.println("进入[" + methodName + "]方法,参数为:" + sb.toString());
// 继续执行方法
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println(methodName + "方法执行结束");
}
}
4、测试
@MyLog
@GetMapping("/sourceC/{source_name}")
public String sourceC(@PathVariable("source_name") String sourceName){
return "你正在访问sourceC资源";
}
四、事务
事务就是把一系列的动作当作一个独立的单元,这些动作要么全部成功,要么全部失败。用来保证数据完整性和一致性的。
事务的ACID属性:
- 原子性(atomicity):事务的是原子性操作,所有动作要么全部完成,要么全部失败。
- 一致性(consistency):数据和资源处于满足业务规则的一致性状态中。数据和资源要么都是成功前的状态,要么都是成功后的状态。
- 隔离性(islation):多个事务处理相同的数据,为了避免数据损坏,将每一个事务都隔离开来。
- 持久性(durability):事务一旦完成完成,数据和资源就被持久化到存储器中。无论系统发生什么错误,结果都不会受到影响。
spring中事务管理
spring在不同的事务管理API上定义了一个抽象层,使得开发任意不必了解事务的底层,就可以使用API进行事务管理。spring支持编程式事务管理和声明式事务管理。
编程式事务管理
- 将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。
缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码;
声明式事务管理
- 将事务代码从业务中分离处理,以声明方式实现事务管理。spring可以将事务管理作为横切关注点,通过aop方法模块化。
声明式事务一般比编程式事务好用。
无论是编程式事务和声明式事务,都需要spring事务管理器(核心事务管理抽象,管理封装了一组独立于)。
JDBC事务
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
配置事务通知
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--配置哪些方法使用什么样的事务,配置事务的传播特性-->
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="search*" propagation="REQUIRED"/>
<tx:method name="get" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
spring事务传播特性
事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间进行传播。spring支持7种事务传播行为:
- propagation_required:如果当前没有事务,就新建事务,如果已经存在一个事务中,加入到这个事务中,这是最常见的选择;
- propagation_supports:支持当前事务,如果当前没有事务,就以非事务方法执行;
- propagation_mandatory:使用当前事务,如果没有事务则抛出异常;
- propagation_required_new:新建事务,如果当前存在事务,则当前事务挂起;
- propagation_not_supported:以非事务方式操作,如果存在事务,就把当前事务挂起;
- propagation_nerver:以非事务方式执行操作,如果当前事务存在,则抛出异常;
- propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行于propagation_required类似的操作。
spring默认的事务传播方式是propagation_required,适合于大多数的情况。假设,多个Service的方法method都工作在事务环境下。存在调用链:Service1的method1—>Service2的method2—>Service3的method3,那么这三个服务类的三个方法通过spring的事务传播机制都工作在同一个事务当中。