Java – Spring Framework 快查

XML 方式管理 Bean

创建无参构造Bean

适用于创建没有参数的Bean对象

<bean id="" class="" />
id 为bean的名称
class 为 bean 的全路径

 

创建有参构造Bean

若想构造一个带有构造参数的Bean对象

    <bean id="" class="">
        <constructor-arg name="" value=""></constructor-arg>
        <constructor-arg index="" value=""></constructor-arg>
        <constructor-arg value=""></constructor-arg>
    </bean>

ref 为 引用其它bean的对象
value 为直接填基本类型的值

定义参数方式:
 1.如果直接填写 value ,则参数从左到右依决注入
 2.如果使用 name 则值会注入到指定名称的参数中
 3.如果使用 index 则从左到右,从0开始,依次选择下标的参数注入值

 

注入成员数据Bean

如果类中包含了成员变量,且该成员变量带有set方法时

    <bean id="" class="">
        <property name="" value=""></property>
        <property name="" ref=""></property>
    </bean>

注意:该name对应成员方法的setXXX中的XXX名称,除去首字母大写的单词

 

静态工厂构造Bean

静态工厂,实际上是指一个类中,直接通过静态方法获取到该类的对象,而不需要使用new

如以下代码:

public class Person {
    public static Person p = new Person();
    
    public static test getPerson(){
        return Person.p;
    }
}

要使用这种方式获取Bean时

<bean id="person" class="cn.unsoft.Person" factory-method="getPerson"/>
使用 factory-method 来定义静态获取Bean的方法

 

 

非静态工厂构造Bean

非静态工厂指的是,调用某实例工厂中的方法,返回的是其它类的对象

如下代码:

public class PersonBuilder {

    private static Person person = new Person();
    public Person bulidPerson(){
        return person;
    }
}

 

 

我们实际上是要获取Person对象,但要获取Person对象需要在PersonBuilder类中获得,可以使用以下方式

    <bean id="personBuilder" class="cn.unsoft.PersonBuilder"/>
      创建实例工厂的Bean
    <bean id="person" factory-bean="personBuilder" factory-method="bulidPerson"/>
     再在实例工厂的Bean中调用方法获取其它Bean对象

 

 

FactoryBean

FactoryBean 实际上是Spring提供的一种创建Bean的接口,我们需要在类中 impl 实现 FactoryBean 接口,则该类就是一个创造 Bean 的类。

FactoryBean 接口需要实现 3 个方法,分别指定该Bean的对象,该Bean 的类型,和是否设置单例模式

public class PersonFactoryBean implements FactoryBean<Person> {

    @Override
    public Person getObject() throws Exception {
        Person person = new Person();
        // person 的其它操作可在这里进行
        return person;
    }

    @Override
    public Class<?> getObjectType() {
        // 如果不知道该Bean 的类型可以为 null 如果知道可以指定
        return Person.class;
    }

    @Override
    public boolean isSingleton() {
        // 默认为 true 单例
        return FactoryBean.super.isSingleton();
    }
}

FactoryBean 的意义在于,当这个Bean对象创建完成时,是需要经过相对复杂的数据整合后才能使用的,如Mybatis的主要对象SqlSession 对象,要获得此对象,需要初始化非常多的对象后加工生成,不能直接new 的时候,就可以使用FactoryBean作加工,后再提交给Spring

 

<bean id="person2" class="cn.unsoft.PersonFactoryBean" />

Spring 会自动检测类是否是FactoryBean

 

 

P.S:FactoryBean 和 BeanFactory 的区别

BeanFactory 是 Spring 框架的基础,其作为一个顶级接口定义了容器的基本行为,例如管理 bean 的生命周期、配置文件的加载和解析、bean 的装配和依赖注入等。BeanFactory 接口提供了访问 bean 的方式,例如 getBean() 方法获取指定的 bean 实例。它可以从不同的来源(例如 Mysql 数据库、XML 文件、Java 配置类等)获取 bean 定义,并将其转换为 bean 实例。同时,BeanFactory 还包含很多子类(例如,ApplicationContext 接口)提供了额外的强大功能。

通俗的讲,BeanFactory 是一个Spring Bean大容器的接口,它定义了这个容器是如何工作的,其下衍生出

ClassPathXmlApplicationContext

AnnotationConfigApplicationContext

WebApplicationContext

FileSystemXmlApplicationContext

四大容器实现类,是整个Spring 容器的顶级接口

一般情况下,整合第三方框架,都是通过定义FactoryBean实现

 

而FactoryBean 仅仅是定义如何创建一个Bean的接口。

 

Bean的周期性方法

Spring 容器能管理Bean对象,自然也会对Bean的创建、销毁带有方法,我们可以自定义该Bean在创建完成后,和准备销毁前的方法。

如下代码:

public class Student {
    public void init(){
        System.out.println("对象创建后的执行方法");
    }
    
    public void destroy(){
        System.out.println("对象即将销毁前执行的方法");
    }
}

类中我们可以定义创建后的方法,和销毁前的方法,然后我们可以在bean定义中设定

 

<bean id="student" class="cn.unsoft.Student" init-method="init" destroy-method="destroy" />

注意:定义周期方法,命名随意,但不能有返回值,不能有参数,且为 public 权限

 

 

Bean 创建是否单例

Spring 默认管理的Bean中为单例,即 getBean 方法获取的对象总是同一个对象

我们可以在 Bean 定义中自定义该对象是否为单例

<bean id="student" class="cn.unsoft.Student" scope="singleton" />
singleton 为单例,默认值
prototype 为多例
如果是在WebApplicationContext环境下还会有另外两个作用域(但不常用):
request 为请求范围内有效的实例
session 为会话范围内有效的实例

若设定为 prototype 后,每一次的 getBean 都不为同一个对象,而是新创建的对象。

 

xml 中读取外部文件

有时候我们希望读取外部文件中的数据到xml文件中,我们可以使用context约束中的标签

<context:property-placeholder location="classpath:xxx.properties" />

读取 properties 文件中的字段,可以使用 ${xxx} 来获得

 

XML 管理注解

配置扫描包路径

当类中使用了注解标记了Bean组件时,需要配置spring的扫描包位置,让spring对某个包中带有指定注解的类进行ioc处理。

<context:component-scan base-package="cn.unsoft.ioc"></context:component-scan>

其中可以设置排除扫描包,可指定注解、自定义、正则匹配等

 

<context:component-scan base-package="cn.unsoft.ioc">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

也可以设置指定包

 

    <context:component-scan base-package="cn.unsoft.ioc" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

加上指定注解的标签不是只扫描该注解,因为本身context:component-scan就是扫描所有包了,要让context:include-filter 生效,可以在context:component-scan中加上use-default-filters="false"

即表示context:component-scan的扫描包功能暂时失效

 

 

 

 

注解方式管理Bean

定义Bean类

Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。

@Component("beanName")

该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。

@Repository("beanName")

该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

@Service("beanName")

该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

@Controller("beanName")

该注解通常作用在控制层(如SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

@RestController

 

定义Bean对象是否单例

@Scope()

可设置该Bean对象是否为单例,可以设置以下两种值

@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

当使用WebApplicationContext时还包含以下两种

@Scope(WebApplicationContext.SCOPE_REQUEST)
@Scope(WebApplicationContext.SCOPE_SESSION)

作用与XML中的scope属性一致。

 

周期方法

@PostConstruct

该注解和XML中的 init-method 的用处是一样的

    @PostConstruct
    public void init(){}

@PreDestroy

该注解和XML中的destory-method 的用处是一样的

    @PreDestroy
    public void destroy(){}

注意:@PostConstruct 和 @PreDestroy 注册使用要引入 javax-annotation-api 包

 

依赖数据注入DI

@Autowired

在成员变量上直接标记@Autowired注解即可,不需要提供setXxx()方法。

    @Autowired
    private XxxService xxxService;

放在成员变量上:直接对该成员变量进行自动装配

放在构造函数上:会对构造函数中的参数进行自动装配数据

放在方法上:对方法上传入的参数进行自动装配

属性:

required = false 如果找不到对象可以允许不装配,不推荐使用,默认为 true

装配原则:

1.如果找到对应类型的Bean且该类型只有一个(包括接口)时,以类型进行装配。

2.如果找到多个类型的Bean时,会以成员变量的名称作为id在Ioc中查找相同名字id的Bean对象。

3.如果找到多个类型的Bean时,且成员变量的名称在Ioc容器中找不到对应的时候,@Autowired 会报多个对象的错误,配合 @Qualifier 注解指定注入IoC中那个id的Bean对象

    @Autowired(required = false)
    @Qualifier("xxxService")
    private XxxService xxxService;

@Qualifier 是与 @Autowired 一起用于定义名称的

 

@Resource

@Resource 相当于@Qualifier 与 @Autowired 一起用,它能像 @Autowired 那样根据类型注入,当定义名称时,就相当于@Qualifier 与 @Autowired 一起用,简化@Qualifier 与 @Autowired的使用

@Resource 在后面版本的jdk中已分离到其它包中,如果要使用,需要引入 jakarta.annotation-api 包

    @Resource(name = "xxxService")
    private XxxService xxxService;

 

@Inject

@Inject 和 @Autowired 功能一致,但没有required = false的选项。

该注解需要引入 javax.inject 包,如需要定义注入的Bean名称可以使用 @Named() 来定义Bean的名称。

 

@Value

@Value 用于注入普通数据类型的注解

    @Value("张三")
    private String name;

@Value 的作用更多是为了用在引入外部文档数据到成员变量中,如想把外部properties文件中的参数引入到成员变量中时,可以使用。

    @Value("${jdbc.username:root}") // root 为默认值,找不到时会被注入
    private String username;

@Value 支持默认值,当没有找到时,可以在${}中加入 : 来指定默认值

 

配置项配置

@Configuration

使用 @Configuration 注解将一个普通的类标记为 Spring 的配置类。

@Configuration
public class Config {
}

@Configuration 可以等价于创建了一个xml配置文件

 

 

@Bean

用于定义 <bean> 标签的注解,它能让第三方对象以类配置的形式加载进Ioc容器中。

@Bean 应用在方法中,该方法的返回对象将成为IoC中的对象,默认对象id为方法名称,也可以在@Bean 里添加特定的名称

@Configuration
@PropertySource(value = "classpath:jdbc.properties")
public class Config {

    @Bean("dataSource")
    public DataSource DruidDataSource(
            @Value("jdbc.url") String url,
            @Value("jdbc.driver") String driver,
            @Value("jdbc.username") String username,
            @Value("jdbc.password") String password
    ){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(url);
        dataSource.setDriverClassName(driver);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}

属性:

initMethod 定义这个Bean的初始化后的方法名,与@PostConstruct作用一样,如果Bean类中包含了@PostConstruct则不需要配置。

destroyMethod 定义这个Bean的销毁前的方法名,与@PreDestroy作用一样,如果Bean类中包含了@PreDestroy则不需要配置。

关于是否单例设置:可以在@Bean下加上@Scope注解

 

@ComponentScan

@ComponentScan 用于替代 <context:component-scan> 标签实现包扫描功能

@Configuration
@ComponentScan(basePackages = {"cn.unsoft",""})
public class Config {
}

同时可以使用 @ComponentScans 配置多个 @ComponentScan 包

 

@Configuration
@ComponentScans({@ComponentScan("cn.unsoft"),@ComponentScan("")})
public class Config {
}

通常@ComponentScan就够了。

 

 

@PropertySource

@PropertySource 是代替 <context:property-placeholder> 用于加载 properties 配置文件的注解

@Configuration
@PropertySource(value = "classpath:jdbc.properties")
public class Config {
}

同时也可以使用 @PropertySources 来定义多个 @PropertySource

使用后可以在 @Value 中使用 ${} 来获取配置数据

 

@Import

@Import 注解允许从另一个配置类加载@Bean定义和配置项

@Configuration
@Import({a.class,b.class})
public class Config {
}

@Import 引入的类要求同是配置类,通常可以用于区分多个配置,如数据库连接配置可以分为一个配置类

 

测试专用

针对每次测试时都需要重新new一个新的ApplicationContext

Spring提供一个注解可以直接帮我们创建容器注解@SpringJUnitConfig

@SpringJUnitConfig(value = {a.class},locations = {""})
public class SpringTest {
    @Test
    public void test(){

    }
}

注意:要引入这个注解需要先引入 junit-jupiter-api包与 spring-test 包 

 

其它注解

@Lazy

定义该注解的类,不会在IoC启动时初始化,而是在使用到这个类的对象时,才会进行构建。

 

@Primary

定义该注解的类,会定义为该类对象为首选的对象

 

Spring AOP 面向切面

方法代理概念

二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。

静态代理

静态代理指的是开发者自己去创建代理类,手动写代理方法。

代理代码如下:

public class CalculatorStaticProxy implements Calculator {
    
    // 将被代理的目标对象声明为成员变量
    private Calculator target;
    
    public CalculatorStaticProxy(Calculator target) {
        this.target = target;
    }
    
    @Override
    public int add(int i, int j) {
    
        // 附加功能由代理类中的代理方法来实现
        System.out.println("参数是:" + i + "," + j);
    
        // 通过目标对象来实现核心业务逻辑
        int addResult = target.add(i, j);
    
        System.out.println("方法内部 result = " + result);
    
        return addResult;
    }
}

1.创建一个接口,用于约束目标类的方法,如接口中增加与目标类一样的方法add

 

2.创建一个实现该接口的类,引入目标类的对象,并实现接口方法add,在执行方法add时,除了调用执行目标类的add方法外,可在接口方法add中加入各种处理目标类add方法的操作代码。

这就是静态代理,静态代理比较麻烦,因为每一个目标类的方法不同,就要创建一个与之对应方法名的接口,每个接口只能用在特定的目标类中,所以静态代理方法不推荐使用。

 

动态代理

上一章节我们知道静态代理其实就是创建一个同名的接口和实现,目标类作为对象并对其方法进行调用加工。而动态代理即是通过动态生成这样的接口和实现方案。

动态代理可分为JDK官方提出的方案【JDK动态代理】和第三方动态代理方案【CGLib】,其中CGLib方案已集成在Spring框架中。

JDK动态代理:

JDK动态代理采用了反射功能,反射功能能让每一个类的每一个方法都能动态获取,就不需要手动创建特定的方法名了。

具体代码如下:

public class ProxyFactory {

    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxy(){

        /**
         * newProxyInstance():创建一个代理实例
         * 其中有三个参数:
         * 1、classLoader:加载动态生成的代理类的类加载器
         * 2、interfaces:目标对象实现的所有接口的class对象所组成的数组
         * 3、invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接口中的抽象方法
         */
        ClassLoader classLoader = target.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                /**
                 * proxy:代理对象
                 * method:代理对象需要实现的方法,即其中需要重写的方法
                 * args:method所对应方法的参数
                 */
                Object result = null;
                try {
                    System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args));
                    result = method.invoke(target, args);
                    System.out.println("[动态代理][日志] "+method.getName()+",结果:"+ result);
                } catch (Exception e) {
                    e.printStackTrace();
                    System.out.println("[动态代理][日志] "+method.getName()+",异常:"+e.getMessage());
                } finally {
                    System.out.println("[动态代理][日志] "+method.getName()+",方法执行完毕");
                }
                return result;
            }
        };

        return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
    }
}

1.通过创建一个动态代理类 ProxyFactory,其构造函数将传入目标类对象,按照这个目标类对象去生成一个代理对象(getProxy方法)。

2.getProxy 方法原理,是通过获取目标类的反射,从而获取该类的方法名称

3.通过实现动态代接口 InvocationHandler 的方法 invoke 来操作目标类的方法被执行的生命周期中做增强。

4.最后把被 InvocationHandler 加工后的目标类子类返回出去,该返回值是目标类的子类对象,里面的方法都被添加上了代理操作,通过调用返回的子类对象方法,就可以执行带有代理操作的方法代码。

测试:

public void testDynamicProxy(){
    ProxyFactory factory = new ProxyFactory(new CalculatorLogImpl());
    Calculator proxy = (Calculator) factory.getProxy();
    proxy.div(1,0);
    //proxy.div(1,1);
}

 

CGLib:

CGLib 与 JDK 官方的动态代理不一样,CGLib 使用的是基于对目标类生成它的子类,并在它的子类上做代理操作。

public class Person {
    public String doWork(){
        System.out.println("doWork");
        return "ok";
    }
}

public class PersonProxy extends Person {
    @Override
    public String doWork() {
        String s = null;
        try {
            System.out.println("相当于@Before");
            s = super.doWork();
            System.out.println("相当于@After-Returning");
        } catch (Exception e) {
            System.out.println("相当于@After-Throwing");
        } finally {
            System.out.println("相当于@After");
        }
        return s;
    }
}

 

在Spring中JDK与CGLib的区别

JDK动态代理原理:前提是目标类是一个实现类,对上有接口,那么JDK会对这个接口另外生成一个一样的实现类来获得实现方法,而生成的实现类会调用目标类获得结果之余,顺便加上一些增强的操作,所以在Spring中使用JDK动态代理后,使用@Autowired获得对象时,需要获取目标类的接口而非目标类,因为JDK动态代理后的代理类归接口下面而不归目标类下面,若@Autowired获得的对象是目标类类型会报错

CGLib动态代理原理:CBLib 会基于目标类生成一个继承目标类的子类,并在子类中重写所有目标类的方法,在子类的方法中加入增强代码。所以在Spring中使用CGLib动态代理后,使用@Autowired获得对象时可以直接获取目标类的类型,因为CGLib生成的是目标类的子类。

 

面向切面的概念

切面:

切面是指通过动态代理,把每一个方法的【执行前】、【返回后】、【异常后】、【执行后】生命周期作一个阶段。

【执行前】:也叫【前置通知】,即表示方法准备执行之前的阶段会被触发。

【返回后】:也叫【返回通知】,即表示方法代码执行完成,且已返回结果的阶段会被触发。

【异常后】:也叫【异常通知】,即表示方法代码在执行过程中出现错误异常的阶段会被触发。

【执行后】:也叫【后置通知】,即表示方法代码已经完成各项操作,已经退出方法执行的阶段,这个阶段不管方法执行是否出现异常,都会被触发。

连接点:

是指一个类中的各种方法,这些方法未被插入代理之前就是连接点,插入代理之后的方法叫切入点

切入点:

切入点是指被选中的方法,如果一个类中有A,B,C三个方法,但是只希望B进行切面操作,则B方法即为切入,或者说通过匹配到适配的方法,也叫切入点。

织入:

织入就是把动态代理功能插入方法时的一个动作说法,和插入类似意思。

 

开始使用 Spring AOP 需知

Spring AOP 实际上在底层上使用的是动态代理的功能,在SpringAOP中,引入了JDK动态代理和CGLib动态代理,当目标类是接口实现时,Spring会自动使用JDK动态代理实现切面,当目标类没有接口实现时,则会使用CGLib动态代理

因为在Spring之前,也有使用AOP开发的思维,在Spring之前,使用的AOP框架是AspectJ,Spring为了兼容开发者对于AspectJ的使用习惯,依然采用了AspectJ的注解,但真正的AOP代理最终是由Spring去实现。

要使用SpringAOP,需要引入 spring-aop 包,与 aspectjweaver 包,但因Maven的包传递,speing-aop 包已在 spring-context 包引入时自动引入了,因此只需要引入 aspectjweaver 包即可。

同时Spring为了统一引入,推出了 spring-aspects 包,可以只需引入 spring-aspects 包就不用引入 aspectjweaver 包了

 

Spring AOP 使用规则

1.要在Spring中使用AOP,我们要按照Spring的IoC规范来使用,要使用SpringAOP,则目标类必须在SpringIoC容器内,因此目标类必须有 @Component 注解

案例:

@Service
public class PersonServiceImpl implements PersonService {
    @Override
    public void doWork() {
        System.out.println("doWork");
    }
}

说明:上面的类是一个需要被AOP增强的类,该类需要加入到SpringIoC中。

 

 

2.AOP切面需要创建一个类,用于定义切入点,也就是说,你想在目标类方法中加入切面增强,必须创建一个类去定义增强方法,并使用 @Aspect 注解标记该类是用于做切面增强用的类,同时该类也必须存在于SpringIoC容器中,所以也必需有 @Component

案例:

@Component
@Aspect
public class PersonAspect {
    
    @Before("execution(* cn.unsoft.service.impl.*.*(..))")
    public void before(){}

    @AfterReturning("execution(* cn.unsoft.service.impl.*.*(..))")
    public void afterReturning(){}

    @AfterThrowing("execution(* cn.unsoft.service.impl.*.*(..))")
    public void afterThrowing(){}

    @After("execution(* cn.unsoft.service.impl.*.*(..))")
    public void after(){}
}

关于切入点说明可在下章节中找到。

 

 

3.需要启用动态代理,因此需要在配置类中标记 @EnableAspectJAutoProxy,否则Spring不会对切入点定义的类作为动态代理的增强类。

案例:

@Configuration
// 同时要记住,一定要把Aspect切面类的包也扫描了,否则不在IoC中也会失效
@ComponentScan("cn.unsoft.aspect") 
@EnableAspectJAutoProxy
public class SpringConfig {
}

注意:用于增强的类也要加到IoC容器中。

 

对应XML中则是开启 <aop:aspectj-autoproxy />

 

Spring AOP 定义

@Before

把该注解所标记的方法插入到目标类方法执行前,即标记这个的方法代码会在目标代码执行之前执行。

    @Before("execution(* cn.unsoft.service.impl.*.*(..))")
    public void before(){}

关于如何获取目标方法的信息可查看下章节。

 

 

@AfterReturning

把该注解所标记的方法插入到目标类方法执行完成并返回数据之后,该切入点可以获取目标方法的返回值。

    @AfterReturning("execution(* cn.unsoft.service.impl.*.*(..))")
    public void afterReturning(){}

属性:

 

returning:指定接收返回值的变量名称,在切入点方法中可以传入一个Object 变量,以接收目标方法执行完的结果,该变量名写到 returning 属性中

    @AfterReturning(value = "execution(* cn.unsoft.service.impl.*.*(..))",returning = "jieguo")
    public void afterReturning(JoinPoint joinPoint,Object jieguo){

    }

关于如何获取目标方法的信息可查看下章节。

 

@AfterThrowing

把该注解所标记的方法插入到目标类方法执行出错异常的地方,该切入点可以获取目标方法所抛出的异常信息

    @AfterThrowing("execution(* cn.unsoft.service.impl.*.*(..))")
    public void afterThrowing(){}

属性:

throwing:指定接收返回值的变量名称,在切入点方法中可以传入一个Object 变量,以接收目标方法执行完的结果,该变量名写到 throwing 属性中

    @AfterThrowing(value = "execution(* cn.unsoft.service.impl.*.*(..))",throwing = "throwable")
    public void afterThrowing(JoinPoint joinPoint,Throwable throwable){}

关于如何获取目标方法的信息可查看下章节。

 

 

@After

把该注解所标记的方法插入到目标类方法退出执行的阶段,该切入点只要声明了,就一定会被增强,不管目标方法是否出现异常。

    @After("execution(* cn.unsoft.service.impl.*.*(..))")
    public void after(){}

关于如何获取目标方法的信息可查看下章节。

 

@Around

@Around 表示环绕通知,是前后置返回值等通知的总和,提供自主控制目标方法的执行时机,可以自定义安排目标方法的执行。

与其它切入点方法不一样的地方在于,@Around 提供可执行目标方法的方法调用,其它切入点(即JoinPoint对象)并没有执行目标方法的能力。

 

特点:

1.环绕通知,与普通的通知不一样,环绕能知需要在通知中,自主做目标方法的执行

2.传入一个ProceedingJoinPoint对象,ProceedingJoinPoint对象可提供目标方法的执行方法。

3.其返回值将作为目本方法执行的返回值。

    @Around("cn.unsoft.aspect.PointCutConfig.aPointCut()")
    public Object around(ProceedingJoinPoint joinPoint){
        Object proceed;
        Object[] args = joinPoint.getArgs();
        try {
            System.out.println("此处相当于@Before");
            proceed = joinPoint.proceed(args);
            System.out.println("此处相当于@AfterReturning");
        } catch (Throwable e) {
            System.out.println("此处相当于@AfterThrowing");
            throw new RuntimeException(e);
        } finally {
            System.out.println("此处相当于@After");
        }
        System.out.println("其返回值作为目标方法的返回值");
        return proceed;
    }

 

 

@PointCut

定义统一的切点表达式,该注解放在一个方法中,并定义切点表达式,后面需要使用切点表达式的切点注解可以直接调用方法名即可。

    @Pointcut("execution(* cn.unsoft.service.impl.*.*(..))")
    public void pointcut(){}

    @Before("pointcut()")
    public void before(JoinPoint joinPoint){
        Class<?> aClass = joinPoint.getTarget().getClass();
    }

不推荐使用上面的方法

推荐把所有的切点方法都统一提取到一个类中集中管理。

 

 

Spring AOP 获取目标方法信息

 

获取方法名

1.传入 JoinPoint 对象,JoinPoint包含目标方法的信息

    @Before("execution(* cn.unsoft.service.impl.*.*(..))")
    public void before(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
    }

 

获取参数

1.传入 JoinPoint 对象,JoinPoint包含目标方法的信息

    @Before("execution(* cn.unsoft.service.impl.*.*(..))")
    public void before(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
    }

 

获取访问修饰符

1.传入 JoinPoint 对象,JoinPoint包含目标方法的信息

    @Before("execution(* cn.unsoft.service.impl.*.*(..))")
    public void before(JoinPoint joinPoint){
        int modifiers = joinPoint.getTarget().getClass().getModifiers();
        String s = Modifier.toString(modifiers);
    }

访问修饰符返回的是int类型,它不同的数值代表不同的类型。可以使用 Modifier.toString 获得该数值对应的类型。

 

 

获取访问返回类型

1.传入 JoinPoint 对象,JoinPoint包含目标方法的信息

    @Before("execution(* cn.unsoft.service.impl.*.*(..))")
    public void before(JoinPoint joinPoint){
        int modifiers = joinPoint.getSignature().getModifiers();
        String s = Modifier.toString(modifiers);
    }

访问修饰符返回的是int类型,它不同的数值代表不同的类型。可以使用 Modifier.toString 获得该数值对应的类型。

 

获取类的信息

1.传入 JoinPoint 对象,JoinPoint包含目标方法的信息

    @Before("execution(* cn.unsoft.service.impl.*.*(..))")
    public void before(JoinPoint joinPoint){
        Class<?> aClass = joinPoint.getTarget().getClass();
    }

通过getClass()获取到这个方法的各种信息,包括所带有的注解等。

 

 

获取返回的结果

1.传入 一个用于接收结果的 Object 的对象,并在@AfterReturning中加入属性 returning,参数为接收对象的变量名称

    @AfterReturning(value = "execution(* cn.unsoft.service.impl.*.*(..))",returning = "jieguo")
    public void afterReturning(JoinPoint joinPoint,Object jieguo){}

注意:仅有在@AfterReturning的切入点中可以接收到结果

 

 

获取异常信息

1.传入 一个用于接收异常的 Throwable 的对象,并在@AfterThrowing中加入属性 throwing,参数为接收对象的变量名称

    @AfterThrowing(value = "execution(* cn.unsoft.service.impl.*.*(..))",throwing = "throwable")
    public void afterThrowing(JoinPoint joinPoint,Throwable throwable){}

 

 

切点表达式

第一位:execution() 固定开头

第二位:方法访问修饰符

public private 直接描述对应修饰符即可

如果不考虑,可填入 *

第三位:方法返回值

int String void 直接描述返回值类型

如果不考虑,可填入 *

 

execution(*) == 你只要考虑返回值 或者 不考虑访问修饰符 相当于全部不考虑了

第四位:指定包的地址

固定的包: cn.unsoft.api | service | dao
 单层的任意命名: cn.unsoft.*  = cn.unsoft.api  cn.unsoft.dao  * = 任意一层的任意命名
 任意层任意命名: com.. = cn.unsoft.api.erdaye com.a.a.a.a.a.a.a  ..任意层,任意命名 用在包上!
 注意: ..不能用作包开头   public int .. 错误语法  com..
 找到任何包下: *..

第五位:指定类名称

固定名称: UserService
任意类名: *
部分任意: com..service.impl.*Impl , cn.unsoft.service.impl.add* 表示以xxImpl结尾和add开头的类
任意包任意类: *..*

第六位:指定方法名称

 

语法和类名一致
固定名称: QueryAll
任意方法: * 
部分任意: com..service.impl.*Impl , cn.unsoft.service.impl.*Impl.*Method 表示以xxMethod结尾的方法
任意包任意类方法: *..*

第七位:方法参数

具体值 (String, int) ,表示具体到只有  (String, int) 参数的方法
模糊值 (..): 表示任意参数 有 或者 没有 (..) 
模糊值 (String..): 表示第一个参数是字符串的方法 (String,..),后面有没有都可
模糊值 (..String): 表示最后一个参数是字符串 (..,String)
模糊值 (String..int): 表示字符串开头,int结尾 (String,..,int)
模糊值 (..int..): 表示包含int类型(..,int,..)

 

举例:

1.查询cn.unsoft包下的所有impl包下的所有方法

* cn.unsoft..impl.*(..)

2.查询cn.unsoft包下,访问修饰符是公有,返回估是int的全部方法

public int cn.unsoft.*(..)

3.查询cn.unsoft包下类中第一个参数是String的方法

* cn.unsoft.*(String..)

4.查询所有包下无参数的方法

* *..*()

5.查询cn.unsoft包下,所有Service开头类的私有返回值int的无参方法

private int cn.unsoft..Service*.*()

 

切面优先级

AOP就像图上那样是一个包裹的圈,方法执行是从一侧执行到另一侧,图上所示,切面B在切面A之外,因此切面B执行开始之前会优先于切面A执行,但是切面A执行完后置方法后,再执行切面B的后置方法。

 

@Order

指定一个优先级的值,值越小,优先级越高!越高的前置先执行,后置后执行

@Component
@Aspect
@Order(10)
public class PersonAspect { }

 

使用XML方式配置AOP

<!--   配置aop切面 -->
    <aop:config>
<!--       1.第一步,配置切点表达式,可以配置多个 -->
        <aop:pointcut id="pointcut" expression="execution(* cn.unsoft.service.impl.*.*(..))"/>

<!--       2.第二步,配置切入点,把适配的方法应用在什么增强切点代码上,设置该切入点类需要在IoC中 -->
<!--         可以设置优先级值 -->
        <aop:aspect ref="advice" order="5" id="advice">
            <aop:before method="before" pointcut-ref="pointcut"></aop:before>
            <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"></aop:after-returning>
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="throwable"></aop:after-throwing>
            <aop:after method="after" pointcut-ref="pointcut"></aop:after>
            <aop:around method="around" pointcut-ref="pointcut"></aop:around>
        </aop:aspect>
    </aop:config>

 

Spring 声明式事务

Spring为了更好的兼容多种数据库平台下的事务管理,特别开发了专门对不同数据源系统匹配的事务接口规范。

声明式事务对应依赖

- spring-tx: 包含声明式事务实现的基本规范(事务管理器规范接口和事务增强等等)
- spring-jdbc: 包含DataSource方式事务管理器实现类DataSourceTransactionManager
- spring-orm: 包含其他持久层框架的事务管理器实现类例如:Hibernate/Jpa等

Spring声明式事务对应事务管理器接口

 

其中我们平时使用的比较多的为Spring开发的JdbcTemplate,而它的事务接口实现类则是在spring-jdbc包中的org.springframework.jdbc.datasource.DataSourceTransactionManager,将来整合 JDBC方式、JdbcTemplate方式、Mybatis方式的事务实现!

DataSourceTransactionManager类中的主要方法:

- doBegin():开启事务
- doSuspend():挂起事务
- doResume():恢复挂起的事务
- doCommit():提交事务
- doRollback():回滚事务

 

配置声明式事务

要对数据源中实现事务管理,我们需要创建一个对应的事务管理实现类(如上面所说的,Spring为了兼容不同的数据源做事务,做了一个规范接口)存到IoC容器中,如果我们使用的是JdbcTemplate或Mybatis这一类的,则是使用 DataSourceTransactionManager 实现类。

    /**
     * 创建一个声明式事务对象实例
     * 本次使用的是适合 DataSource 的事务实现类
     */
    @Bean("transactionManager")
    public TransactionManager dataSourceTransactionManager(@Qualifier("dataSource") DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }

事务管理实则是一种AOP切面编程思想,它针对需要执行数据库操作的方法进行动态代理,如果数据库查询错误,则会抛出异常,事务管理对象会对异常进行回滚操作。

 

Spring默认不会开启事务注解的支持,所以需要在配置项中开启【事务注解支持】

对于事务管理使用的是AOP技术,也可以加上 【@EnableAspectJAutoProxy】注解(不加也行,但是当需要用AOP时就需要打开)

@Configuration
@EnableTransactionManagement
public class SpringConfig { 
    /**
     * 创建一个声明式事务对象实例
     * 本次使用的是适合 DataSource 的事务实现类
     */
    @Bean("transactionManager")
    public TransactionManager dataSourceTransactionManager(@Qualifier("dataSource") DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
 }

这样就开启了事务的注解支持,在需要事务管理时,可使用【@Transactional】注解,事务管理就会自动对该方法进行AOP动态代理处理。

    @Transactional
    public void changeInfo(){}

 

 

@Transactional 属性说明

声明位置:

1.应用在方法上:对该方法的事务生效

2.应用在类上:对该类上的所有方法的事务都生效

只读模式:

readOnly = true 当查询的数据中只包含了查询操作时,可以设置只读模式,这样会提交效率

Q&A: readOnly 存在的意义:

通常我们的事务都应该用在写、改和删上面,而查询做事务本身就矛盾了,查询不需要事务。但@Transactional是可以应用在类上面的,一旦应用在类上面,类的所有方法都带有事务功能,这时 readOnly 的意义就是可以单独对查询方法上做只读事务,提高效率。

超时时间

timeOut = 3 设置超时时间为3秒,超过3秒则回滚查询。

一般情况下,查询业务是没有超时时间的,不管查询时间多久,都会一直等待查询结果。但这样的体验不好。

可以对查询时间做一个时间限制,可以对过久的查询做回滚操作,以提高体验。

如果在类上设置了超时时间,在方法上没有设置了超时时间,则以方法为准。

事务的异常指定

@Transactional 是按照方法的异常抛出来判定是否回滚操作,但是@Transactional并不是所有的异常抛出都会回滚,默认是出现了 RuntimeException 异常才会做回滚操作,但是如果数据库写入错误出现IOException时,@Transactional是不会做回滚的。

我们可以设置更高的异常类,来函括更完整的回滚机制:

rollbackFor:

@Transactional(rollbackFor = Exception.class)

同时我们也可以设置当抛出指定异常时,不进行回滚的情况:

 

noRollbackFor:

@Transactional(noRollbackFor = NullPointerException.class)

 

 

事务的隔离级别

数据库事务的隔离级别是指在多个事务并发执行时,数据库系统为了保证数据一致性所遵循的规定。常见的隔离级别包括:

1. 读未提交(Read Uncommitted):事务可以读取未被提交的数据,容易产生脏读、不可重复读和幻读等问题。实现简单但不太安全,一般不用。
2. 读已提交(Read Committed):事务只能读取已经提交的数据,可以避免脏读问题,但可能引发不可重复读和幻读。
3. 可重复读(Repeatable Read):在一个事务中,相同的查询将返回相同的结果集,不管其他事务对数据做了什么修改。可以避免脏读和不可重复读,但仍有幻读的问题。
4. 串行化(Serializable):最高的隔离级别,完全禁止了并发,只允许一个事务执行完毕之后才能执行另一个事务。可以避免以上所有问题,但效率较低,不适用于高并发场景。

不同的隔离级别适用于不同的场景,需要根据实际业务需求进行选择和调整。

 

isolation:

Isolation.DEFAULT 按数据库本身的隔离级决定

Isolation.READ_UNCOMMITTED 读未提交

Isolation.READ_COMMITTED 读已提交

Isolation.REPEATABLE_READ 可重复读

Isolation.SERIALIZABLE 串行化

@Transactional(isolation = Isolation.DEFAULT)

 

 

事务的传播行为

当不同类之间存在互相调用方法,而方法之间又有各自的事务时,这个事务的处理该如何传播?

举例:当MethodA调用MethodB,同时MethodA和MethodB都设置了事务,当MethodB被MethodA调用时,MethodB该如何面对MethodA的事务问题?

propagation:

Propagation.REQUIRED 如果MethodA存在事务,则MethodB加入到MethoA事务中,如果MethodB出现了错误,那么MethodA也会回滚,如果MethodA没有事务,则MethodB自己开事务

Propagation.REQUIRES_NEW 不管MethodA是否有事务,MethodB都不受MethodA事务的影响,当MethodB出错时,并不影响MethodA的操作,MethodA并不会回滚,只回滚MethodB

Propagation.NESTED 如果MethodA存在事务,则MethodB在该事务中嵌套一个新事务,如果没有事务,则与Propagation.REQUIRED一样。

Propagation.SUPPORTS 如果MethodA存在事务,则MethodB加入到事务中,如果如果MethodA没有事务,则MethodB也没有事务执行

Propagation.NOT_SUPPORTED 如果MethodA存在事务,直接挂起,如果MethodA没有事务则执行

Propagation.MANDATORY MethodA必须有事务,否则抛出异常

Propagation.NEVER MethodA必须没有事务,否则抛出异常

@Transactional(propagation = Propagation.REQUIRED)

一般使用默认的 Propagation.REQUIRED 即可。

 

如果您喜欢本站,点击这儿不花一分钱捐赠本站

这些信息可能会帮助到你: 下载帮助 | 报毒说明 | 进站必看

修改版本安卓软件,加群提示为修改者自留,非本站信息,注意鉴别

THE END
分享
二维码
打赏
海报
Java – Spring Framework 快查
XML 方式管理 Bean 创建无参构造Bean 适用于创建没有参数的Bean对象 <bean id="" class="" /> id 为bean的名称 class 为 bean 的全路径   创建有参构造Bean 若想构造一个带有构……
<<上一篇
下一篇>>