Spring5基本知识

本篇论述的内容

  • 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的事务传播机制都工作在同一个事务当中。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值