狂敲2.6w字详解Spring

目录

Spring入门

概述

Spring的基本应用

1.IoC

2.Spring的核心容器(IoC容器)

3.Spring入门程序

4.DI

5.依赖注入示例(以setter方法注入的方式为例)

Spring中的Bean

Bean的配置

Bean的实例化

构造器实例化

案例演示

Bean的作用域

示例

Bean的生命周期

Bean的装配(注入)方式

XML配置文件装配

注解装配

Java配置类装配

自动装配

SpringAOP

AOP的核心概念

动态代理

JDK动态代理

CGLIB代理

AOP的实现

AspectJ开发

元素及其子元素

AspectJ的注解的详细介绍

基于XML的声明式AspecJ

基于注解的声明式AspecJ

Spring的数据库开发

Spring的数据开发

Spring JDBC

Spring JdbcTemplate

Spring的事务管理

基于XML方式的声明式事务管理

基于Annotation方式的声明式事务管理


Spring入门

概述

Spring框架由Rod Johnson组织和开发的一款分层的JavaEE企业级开源框架。

Spring的基本应用

1.IoC

IoC(Inversion of Control)控制反转

使用对象时,由主动new产生对象转换为由“外部”提供对象,在此过程中对象创建的控制权由程序转移到外部,此思想称为控制反转。

Spring技术对IoC思想进行了实现

Spring提供了一个容器,称为IoC容器,用来充当IoC思想的“外部”

IoC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IoC容器中统称为Bean

2.Spring的核心容器(IoC容器)

Spring框架的主要功能就是通过其核心容器实现的。Spring框架提供了两种核心容器,分别是BeanFactory和ApplicationContext。

BeanFactory

BeanFactory是基础的IoC容器,它提供了完整的IoC服务支持。简单来说,BeanFactory就是一个管理Bean的工厂,它主要负责初始化各种Bean,并调用它们的生命周期方法。

BeanFactory接口提供了集合实现类,最常用的就是XmlBeanFactory,该类会根据XML配置文件中的定义来装配Bean。

在创建BeanFactory实例时,需要提供Spring所管理容器的详细配置信息,这些信息通常采用XML文件的形式来管理,加载配置信息的语法如下:

BeanFactory beanFactory=new XmlBeanFactory(new FileSystemResource("F:/applicationContext.xml"));

这种加载方式在实际开发中并不多用,在此了解即可。

ApplicationContext

ApplicationContext是BeanFactory的子接口,也被称为应用上下文,是另一种常用的Spring核心容器。它不仅包含BeanFactory的所有功能,还添加了对国际化、资源访问、事件传播等方面的支持。

创建ApplicationContext接口实例,通常有两种方法,具体如下。

//1.通过ClassPathXmlApplicationContext创建,ClassPathXmlApplicationContext会从类路径classPath寻找指定的XML配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLoactino);
//2.通过FileSystemXmlApplication创建,FileSystemXmlApplicationContext会从指定的文件系统路径(绝对路径)中寻找指定的XML配置文件
ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLoactino);

通常在Java项目中,会采用ClassPathXmlApplicationContext类来实例化ApplicationContext容器的方式。而在Web项目中,ApplicaionContext容器的实例化工作会交由Web服务器来完成。

创建Spring容器后,就可以获取Spring容器中的Bean。Spring获取Bean的实例通常采用以下两种方法。

Object getBean(String name):根据容器中Bean的id或name来获取指定的Bean,获取后需要进行强制类型转换;

<T> T getBean(Class<T> requiredType):更具类的类型来获取Bean的实例。由于此方法为泛型方法,因此在获取到Bean之后不需要在进行强制类型转换。

3.Spring入门程序

1)在Idea中创建一个maven项目,然后在pom.xml文件中导入所需要的包;

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.23</version>
        </dependency>

2)在src目录下创建一个com.ioc包,并在包中创建接口UserDao,然后在接口中定义一个say()方法;

package com.ioc;

public interface UserDao {
    public void say();
}

3)在com.ioc包下,创建UserDao接口的实现类UserDaoImpl,该类需要重写接口中的say()方法;

package com.ioc;

public class UserDaoImpl implements UserDao{
    @Override
    public void say() {
        System.out.println("UserDao say hello world");
    }
}

4)在resources目录下,创建applicationContext.xml文件,并在配置文件中创建一个id为userDao的Bean;

<?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">
    <!--将指定类配置给Spring,让Spring创建其对象的实例-->
    <bean id="userDao" class="com.ioc.UserDaoImpl"/>
</beans>

注意:第2-5行为Spring的约束配置,第7行代码表示在Spring容器中创建一个id为userDao的Bean实例,其中class属性用于指定需要实例化Bean的类。

5)com.ioc包下,创建测试类TestIoC,并在类中编写main方法。在main方法中需要初始化Spring容器,并加载配置文件,然后通过Spring容器获取userDao实例(即Java对象),最后调用say()方法;

package com.ioc;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestIoC {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao =(UserDao)applicationContext.getBean("userDao");
        userDao.say();
    }
}

程序执行后,控制台输出结果如下

可以看出控制台已经成功输出了UserDaoImpl类中的输出语句。但在步骤5中我们并没有用new关键字来创建UserDao接口的实现类对象,而是通过Spring容器来获取的实现类对象,这就是SpringIoC容器的工作机制。

4.DI

DI(Dependency Injection)依赖注入

在容器中建立bean与bean之间依赖关系的整个过程,称之为依赖注入

依赖注入的作用就是在使用Spring框架创建对象时,动态地将其所依赖的对象注入Bean组件中,其实现方式有两种,一种是setter方法注入,另一种是构造放发注入,具体如下:

属性setter方法注入:指Spring容器使用setter方法注入被依赖的实例。通过调用无参构造器或无参静态工厂方法实例化Bean后,调用该Bean的setter方法后,调用该Bean的setter方法,即可基于setter方法依赖注入

构造方法注入:指Spring容器使用构造方法注入被依赖的实例。基于构造方法的依赖注入通过调用带参的构造方法来实现,每个参数代表着一个依赖

5.依赖注入示例(以setter方法注入的方式为例)

1)在com.ioc包中,创建接口UserService,在接口中编写一个方法say()方法

package com.ioc;

public interface UserService {
    public void say();
}

2)在com.ioc包中,创建UserService接口的实现类UserServiceImpl,在类中声明userDao属性,并添加属性的setter方法

package com.ioc;

public class UserServiceImpl implements UserService{
    //声明UserDao属性
    private UserDao userDao;
    //添加UserDao属性的setter方法,用于实现依赖注入
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    @Override
    public void say() {
    //调用userDao中的say()方法,并执行输出语句
        this.userDao.say();
        System.out.println("UserService say hello world!");
    }
}

3)在配置文件applicationContext.xml中,创建一个id为userService的Bean,该Bean用于实例化UserServiceImpl类的信息,并将userDao的实例注入到userService中

<?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="userDao" class="com.ioc.UserDaoImpl"/>
    <bean id="userService" class="com.ioc.UserServiceImpl">
        <!--将id为userDao的Bean实例注入到userService实例中-->
        <property name="userDao" ref="userDao"/>
    </bean>
</beans>

上述代码中,<property>是<bean>元素的子元素,它用于调用Bean实例中的setUserDao()方法完成属性的赋值,从而实现依赖注入。name属性表示Bean实例中的相应属性名,ref属性用于指定属性值表示参照哪一个bean

4)com.ioc包下,创建测试类TestDI。来对程序进行测试

package com.ioc;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestDI {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService =(UserService)applicationContext.getBean("userService");
        userService.say();
    }
}

5)执行程序后,控制台输入结果如下

从控制台输出结果可以看出,使用Spring容器通过UserService实现类中的say()方法,调用了UserDao实现类中的say方法,并输出了结果。这就是Spring容器属性setter方法注入的方式,也是实际开发中最常用的一种方式。

Spring中的Bean

Bean的配置

Spring可以被看作一个大型工厂,这个工厂的作用就是生产和管理Spring容器中的Bean。如果想要在项目中使用这个工厂,就需要开发者对Spring的配置文件进行配置。

Spring容器支持XML和Properties两种格式的配置文件,在实际开发中,最常使用的就是XML格式的装配方式。这种配置方式通过XML文件来注册并管理Bean之间的依赖关系。

在Spring中,XML配置文件的根元素是<beans>,<beans>中包含了多个<bean>子元素,每个<bean>子元素定义了一个Bean,并描述该Bean如何被装配到Spring容器中。

<bean>元素的常用属性及子元素
属性或子元素 描述
id Bean的唯一标识符,Spring容器对Bean配置、管理都是通过该属性完成
name Spring容器同样可以通过此属性对容器中的Bean进行配置和管理,name属性中可以为Bean指定多个名称,每个名称之间用逗号、分号或者空格隔开
class 该属性指定了Bean实例的具体实现类,它必须是一个完整的类名(类的全限定名)
scope 用来指定Bean实力的作用域,其属性值有:singgleton(单例)、prototype(原型)、request、session、global Session、application和websocket。默认值为singleton
property <Bean>元素的子元素,用于调用Bean实例中的setter方法完成属性赋值,从而完成依赖注入。该属性的name属性指定Bean实例中的相应属性名,ref或value属性用于指定参数值
ref <property>等元素的属性或子元素,可以用于指定对Bean工厂某个Bean实例的引用
value <property>等元素的属性或子元素,可以用于直接指定一个常量值
list 用于封装List或数组类型的依赖注入
set 用于封装set类型属性的依赖注入
map 用于封装map类型属性的依赖注入
entry

<map>元素的子元素,用于设置一个键值对。其key属性指定字符串类型的键值,ref或value子元素指定其值,也可以通过value-ref或value属性指定其值

Bean的实例化

在面向对象的程序中,要想使用某个对象,就需要先实例化这个对象。同样的在Spring中,要想使用容器中的某个Bean,也需要实例化Bean。实例化Bean有三种方式,分别是构造器实例化、静态工厂实例化和实例工厂实例化,由于最常用的就是构造器实例化,所以下面就以构造器实例化为例展开叙述

构造器实例化

构造器实例化是指Spring容器通过Bean对应类中默认的无参构造方法来实例化Bean。

案例演示

1)在com.constructor包下创建Bean1类,并且在无参构造方法中写一句话,用来判断该无参方法是否被调用

package com.constructor;

public class Bean1 {
    public Bean1(){
        System.out.println("Bean的构造方法");
    }
}

2)在applicationContext.xml配置文件中定义一个id为bean1的Bean,并通过class属性指定其对应的实现类

<?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="bean1" class="com.constructor.Bean1"/>
</beans>

3)在com.constructor包中,创建测试类InstanceTest,来测试构造器是否能实例化Bean

package com.constructor;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class InstanceTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        Bean1 bean1 =(Bean1) applicationContext.getBean("bean1");
        System.out.println(bean1);
    }
}

执行程序后,运行结果如下

 可以看出,Spring容器已经成功实例化Bean,并输出了结果

Bean的作用域

在Spring的配置文件中,Bean的作用域是通过<bean>元素的scope属性来指定的,该属性的值如下表所示

Bean的作用域
作用域名称
singleton(单例) 使用singleton定义的Bean在Spring容器中只有一个实例,也就是说无论有多少个Bean引用它,始终指向的都是同一个对象
prototyoe(原型) 每次通过Spring容器获取的prototype定义的Bean时,容器都将创建一个新的Bean实例
request 在一次HTTP请求中,容器会返回该Bean的同一个实例。对不同的HTTP请求。对不同的HTTP请求则会产生一个新的Bean,而且该Bean仅在当前
session 再一次HTTP Session中,容器会返回该Bean的同一个实例。对不同的HTTP请求则会产生一个新的Bean,而且该Bean仅在当前HTTP Session内有效
globalSession 在一个全局的HTTP Session中,容器会返回该Bean的同一个实例。仅在portlet上下文是有效
application 为每个ServletContext对象创建一个实例。仅在web相关的ApplicationContext中生效。
websocket 为每个websocket对象创建一个实例。仅在web相关的ApplicationContext中生效。

在表中的7种作用域中,singleton和prototype是最常用的两种,下面用例子说明singleton和prototype二者的区别

示例

1)在com包下创建名为User的类

package com;

public class User {
    private String name;
    private String age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

}

2)在applicationContext.xml配置文件中定义一个id为user的Bean,然后通过class属性指定其对应的实现类,最后设置scope的值为singleton

<?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="user" class="com.User" scope="singleton"/>
</beans>

3)在com包下创建UserTest类

package com;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user =(User) applicationContext.getBean("user");
        User user2 =(User) applicationContext.getBean("user");
        System.out.println(user);
        System.out.println(user2);
    }
}

运行结果如下:

从输出结果可以看出,两次输出结果相同,这说明Spring容器值创建了一个Scope类示例。

注意:如果不设置scope="singleton",其输出结果也是一个实例,因为Spring容器默认的作用域就是singleton

4)修改步骤2中applicationContext.xml配置文件中id为user的Bean的scope的值为prototype,然后再次进行测试

<?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="user" class="com.User" scope="singleton"/>
</beans>

测试程序运行结果如下:

可以发现两次输出的Bean实例并不相同,这说明在prototype作用域下,创建了两个不同的Scope实例;

Bean的生命周期

Bean的生命周期就是Bean从创建到消亡的整个过程;

在Spring容器中,每个Bean的生命周期可以分为以下阶段:

1)实例化(Instantiation):当Spring容器启动时,根据配置文件或注解,创建Bean的实例。

2)属性赋值(Populate properties):将Bean实例的属性值或依赖注入到Bean实例中。

3)初始化前(Initialization):在Bean实例初始化前,调用BeanPostProcessor的postProcessBeforeInitialization方法,可以对Bean实例进行自定义的初始化操作。

4)初始化(Initialization):在Bean实例初始化时,调用Bean的初始化方法,例如实现InitializingBean接口的afterPropertiesSet方法或配置文件中指定的init-method方法。

5)初始化后(Initialization):在Bean实例初始化后,调用BeanPostProcessor的postProcessAfterInitialization方法,可以对Bean实例进行自定义的后处理操作。

6)销毁前(Destruction):在Bean实例销毁前,调用实现DisposableBean接口的destroy方法或配置文件中指定的destroy-method方法。

7)销毁(Destruction):在Bean实例销毁时,释放Bean实例占用的资源。

下面是一个简单的Spring Bean生命周期图:


在Spring容器中,Bean的生命周期由容器管理,我们可以通过实现BeanPostProcessor和InitializingBean、DisposableBean接口或使用配置文件中的init-method和destroy-method方法来自定义Bean的初始化和销毁操作。

Bean的装配(注入)方式

XML配置文件装配

通过在XML配置文件中定义Bean的属性和依赖关系,Spring容器会根据配置文件中的信息来创建和管理Bean实例。例如:

   <bean id="userService" class="com.example.UserService">
       <property name="userDao" ref="userDao"/>
   </bean>

   <bean id="userDao" class="com.example.UserDao"/>

在以上示例中,定义了一个名为userService的Bean,它依赖于一个名为userDao的Bean。Spring容器会先创建userDao的实例,然后将它注入到userService的属性中。

注解装配

通过在Bean类或配置类中使用注解来定义Bean的属性和依赖关系,Spring容器会根据注解信息来创建和管理Bean实例。例如:

    @Service   
    public class UserService {
       @Autowired       
       private UserDao userDao;
        //...
    }
    @Repository   
    public class UserDao {
        //...
    }

在以上示例中,使用@Service和@Repository注解来定义Bean,使用@Autowired注解来注入依赖关系。Spring容器会自动扫描带有注解的类,并创建和管理Bean实例。

Java配置类装配

通过在Java配置类中定义Bean的属性和依赖关系,Spring容器会根据配置类中的信息来创建和管理Bean实例。例如:

@Configuration   
public class AppConfig {
       @Bean       
       public UserService userService() {
           UserService userService = new UserService();
           userService.setUserDao(userDao());
           return userService;
       }
   
       @Bean       
       public UserDao userDao() {
           return new UserDao();
       }
   }

在以上示例中,使用@Configuration和@Bean注解来定义Bean和依赖关系。Spring容器会根据配置类中的信息来创建和管理Bean实例。

自动装配

Spring容器会根据Bean的类型或名称自动注入依赖关系。例如:

@Service   
public class UserService {
       @Autowired       
       private UserDao userDao;
       // ...
   }
   
@Repository   
public class UserDao {
       // ...
   }

在以上示例中,使用@Autowired注解来自动注入依赖关系。Spring容器会根据类型或名称自动查找符合条件的Bean,并将它们注入到属性中。
总之,在Spring中,Bean的装配方式有多种,可以根据实际情况选择最合适的方式来创建和管理Bean实例。

SpringAOP

        AOP 的全称是 Aspect-Oriented Prograrming,即面向切面编程(也称面向方面编程)。它是面向对象编程(OOP)的一种补充,目前已成为一种比较成熟的编程方式。
        在传统的业务处理代码中,通常都会进行事务处理、日志记录等操作。虽然使用 OOP 可以通过组合或者继承的方式来达到代码的重用,但如果要实现某个功能(如日志记录),同样的代码仍然会分散到各个方法中。这样,如果想要关闭某个功能,或者对其进行修改,就必须要修改所有的相关方法。这不但增加了开发人员的工作量,而且提高了代码的出错率。
        为了解决这一问题,AOP 思想随之产生。AOP 采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。这种采用横向抽取机制的方式,采用传统的 OOP 思想显然是无法办到的,因为 OOP 只能实现父子关系的纵向的重用。里然AOP-是一种新的编程思想但却不是OOP的替代品,他只是OOP的延伸和补充。

AOP的核心概念

1)切面(Aspect):切面是一个模块化的横切关注点,它包含了通知和切点。通知定义了在何时、何地执行代码,而切点定义了在哪些地方执行代码。

举例:在一个web应用中,日志记录、安全控制、事务处理等都是横跨多个对象的通用功能,这些功能被称为切面,可以通过AOP来实现。

2)连接点(Joinpoint):连接点是在应用执行过程中能够插入切面的点,比如方法调用、异常处理等。

举例:在一个web应用中,当用户点击某个按钮时,就会触发一个连接点,可以在这个连接点上插入切面来实现日志记录、安全控制等功能。

3)切点(Pointcut):切点是一组连接点的集合,用来定义切面在哪些连接点上执行。

举例:在一个web应用中,可以定义一个切点,表示所有的服务层方法都是切点,然后在这个切点上插入切面来实现事务处理等功能。

4)通知(Advice):通知是在切面中定义的具体行为,包括前置通知、后置通知、返回通知、异常通知和环绕通知等。

举例:在一个web应用中,可以定义一个前置通知,在服务层方法执行前记录日志,也可以定义一个异常通知,在服务层方法抛出异常时发送警报。

5)切面优先级(Aspect Ordering):如果多个切面都对同一个连接点进行了处理,那么切面的执行顺序就是切面优先级的问题。

举例:在一个web应用中,可以定义多个切面,比如日志切面、安全切面、事务切面等,这些切面可能会对同一个服务层方法进行处理,此时需要定义切面的优先级,以确定各个切面的执行顺序。

6)织入(Weaving):织入是将切面应用到目标对象并创建新的代理对象的过程。

动态代理

动态代理是在程序运行时生成代理类的方式,可以动态地为目标对象创建代理对象,从而实现对目标对象的代理操作。Java中的动态代理主要有两种方式:JDK动态代理和CGLIB代理。

JDK动态代理

JDK动态代理是基于接口的代理方式,在运行时动态地创建代理对象。JDK动态代理需要目标对象实现一个或多个接口,代理对象也会实现这些接口,并将方法的调用委托给目标对象。

示例代码:

public interface HelloService {
    void sayHello(String name);
}

public class HelloServiceImpl implements HelloService {
    @Override
    public void sayHello(String name) {
        System.out.println("Hello, " + name);
    }
}

public class HelloServiceProxy implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("After method " + method.getName());
        return result;
    }
}

public class Main {
    public static void main(String[] args) {
        HelloService helloService = new HelloServiceImpl();
        HelloServiceProxy proxy = new HelloServiceProxy(helloService);
        HelloService helloServiceProxy = (HelloService) Proxy.newProxyInstance(
                helloService.getClass().getClassLoader(),
                helloService.getClass().getInterfaces(),
                proxy);
        helloServiceProxy.sayHello("World");
    }
}

CGLIB代理

CGLIB代理是基于类的代理方式,在运行时动态地创建目标类的子类作为代理对象。CGLIB代理不需要目标对象实现接口,代理对象是目标对象的子类,可以直接调用目标对象的方法。

示例代码:

public class HelloService {
    public void sayHello(String name) {
        System.out.println("Hello, " + name);
    }
}

public class HelloServiceInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before method " + method.getName());
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("After method " + method.getName());
        return result;
    }
}

public class Main {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(HelloService.class);
        enhancer.setCallback(new HelloServiceInterceptor());
        HelloService helloServiceProxy = (HelloService) enhancer.create();
        helloServiceProxy.sayHello("World");
    }
}

AOP的实现

AOP的实现主要有两种方式:基于代理和基于字节码增强。基于代理的AOP实现是在运行时动态地创建代理对象,将通知织入到目标对象的方法中。基于字节码增强的AOP实现是在编译时修改字节码,将通知织入到目标对象的方法中。

示例代码:

public interface HelloService {
    void sayHello(String name);
}

public class HelloServiceImpl implements HelloService {
    @Override
    public void sayHello(String name) {
        System.out.println("Hello, " + name);
    }
}

public class LogAspect {
    public void before() {
        System.out.println("Before method");
    }

    public void after() {
        System.out.println("After method");
    }
}

public class LogAspectJ {
    public void before(JoinPoint joinPoint) {
        System.out.println("Before method " + joinPoint.getSignature().getName());
    }

    public void after(JoinPoint joinPoint) {
        System.out.println("After method " + joinPoint.getSignature().getName());
    }
}

public class HelloServiceProxy implements InvocationHandler {
    private Object target;
    private Object aspect;

    public HelloServiceProxy(Object target, Object aspect) {
        this.target = target;
        this.aspect = aspect;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Method before = aspect.getClass().getMethod("before");
        before.invoke(aspect);
        Object result = method.invoke(target, args);
        Method after = aspect.getClass().getMethod("after");
        after.invoke(aspect);
        return result;
    }
}

public class Main {
    public static void main(String[] args) {
        HelloService helloService = new HelloServiceImpl();
        LogAspect aspect = new LogAspect();
        HelloServiceProxy proxy = new HelloServiceProxy(helloService, aspect);
        HelloService helloServiceProxy = (HelloService) Proxy.newProxyInstance(
                helloService.getClass().getClassLoader(),
                helloService.getClass().getInterfaces(),
                proxy);
        helloServiceProxy.sayHello("World");
    }
}

AspectJ开发

 AspectJ是一个基于Java语言的AOP框架,支持两种开发方式:基于XML和基于注解。

基于XML的开发方式需要使用AspectJ的编译器进行编译,而基于注解的开发方式可以直接使用Java编译器进行编译。

<aop:config>元素及其子元素

元素名 介绍 属性
<aop:config> 用于定义Spring AOP配置文件的根元素
  • proxy-target-class:指定是否使用CGLIB代理,默认为false,表示使用JDK动态代理。
  • expose-proxy:指定是否将代理对象暴露给AOP代理链上的下一个通知或者切面,默认为false。
<aop:aspect> 用于定义一个切面
  • id:切面的唯一标识符。
  • ref:切面的实现类的引用。
  • order:切面的执行顺序,默认为0,数字越小越先执行。
<aop:pointcut> 用于定义一个切入点
  • id:切入点的唯一标识符。
  • expression:切入点的表达式,用于匹配目标对象的方法。
<aop:advisor> 用于定义一个通知器
  • advice-ref:通知器的引用。
  • pointcut-ref:切入点的引用。
<aop:after-returnding> 用于定义一个返回通知
  • method:通知方法的名称。
  • pointcut:切入点的表达式,用于匹配目标对象的方法。
  • returning:通知方法的返回值参数名称。
<aop:before> 用于定义一个前置通知
  • method:通知方法的名称。
  • pointcut:切入点的表达式,用于匹配目标对象的方法。
<aop:after> 用于定义一个最终通知
  • method:通知方法的名称。
  • pointcut:切入点的表达式,用于匹配目标对象的方法。
<aop:around> 用于定义一个环绕通知
  • method:通知方法的名称。
  • pointcut:切入点的表达式,用于匹配目标对象的方法。

AspectJ的注解的详细介绍

注解名 介绍
@Aspect 用于定义一个切面,它需要和其他注解一起使用,例如@Before、@After等注解。
@Pointcut注解 用于定义一个切入点,它可以被其他注解引用,例如@Before、@After等注解。
@Before 用于定义一个前置通知,它需要指定切入点表达式。
@After 用于定义一个最终通知,它需要指定切入点表达式。
@AfterReturning 用于定义一个返回通知,它需要指定切入点表达式和返回值参数名称。
@Around 用于定义一个环绕通知,它需要指定切入点表达式。

示例:

@Aspect
public class LogAspect {
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void servicePointcut() {}

    @Before("servicePointcut()")
    public void before(JoinPoint joinPoint) {
        System.out.println("Before: " + joinPoint.getSignature().getName());
    }

    @AfterReturning(pointcut = "servicePointcut()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        System.out.println("AfterReturning: " + joinPoint.getSignature().getName() + ", result: " + result);
    }

    @After("servicePointcut()")
    public void after(JoinPoint joinPoint) {
        System.out.println("After: " + joinPoint.getSignature().getName());
    }

    @Around("servicePointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("Around before: " + proceedingJoinPoint.getSignature().getName());
        Object result = proceedingJoinPoint.proceed();
        System.out.println("Around after: " + proceedingJoinPoint.getSignature().getName());
        return result;
    }
}

在上面的示例中,定义了一个LogAspect切面,它包含了@Before、@AfterReturning、@After和@Around等注解,并且使用@Pointcut注解定义了一个切入点。在@Before、@AfterReturning、@After和@Around注解中,都使用了切入点表达式来指定需要拦截的方法。

基于XML的声明式AspecJ

在使用XML方式开发时,需要先定义一个切面类和切入点,然后在XML配置文件中进行配置。例如:

切面类:

public aspect LogAspect {
    private Logger logger = LoggerFactory.getLogger(LogAspect.class);

    pointcut logPointcut(): execution(* com.example.service.*.*(..));

    before(): logPointcut() {
        logger.info("方法执行前记录日志");
    }

    after(): logPointcut() {
        logger.info("方法执行后记录日志");
    }
}

在XML配置文件中配置切面和切入点:

<aop:config>
    <aop:aspect id="logAspect" ref="logAspect">
        <aop:pointcut id="logPointcut" expression="execution(* com.example.service.*.*(..))"/>
        <aop:before pointcut-ref="logPointcut" method="before"/>
        <aop:after pointcut-ref="logPointcut" method="after"/>
    </aop:aspect>
</aop:config>

基于注解的声明式AspecJ

在使用注解方式开发时,只需要在切面类中添加注解即可。例如:

@Aspect
@Component
public class LogAspect {
    private Logger logger = LoggerFactory.getLogger(LogAspect.class);

    @Pointcut("execution(* com.example.service.*.*(..))")
    public void logPointcut() {}

    @Before("logPointcut()")
    public void before() {
        logger.info("方法执行前记录日志");
    }

    @After("logPointcut()")
    public void after() {
        logger.info("方法执行后记录日志");
    }
}

在Spring配置文件中开启AspectJ自动代理:

<aop:aspectj-autoproxy/>

Spring的数据库开发

Spring是一个流行的开源框架,提供了一组强大的数据访问技术,包括Spring JDBC和Spring JdbcTemplate。这些技术可以帮助开发人员更轻松地访问和操作数据库。下面我们来详细介绍一下Spring的数据开发以及Spring JDBC和Spring JdbcTemplate的常用方法。

Spring的数据开发

Spring的数据开发主要包括以下几个方面:

1)数据库连接管理

Spring提供了一个用于管理数据库连接的抽象层,可以让开发人员更方便地管理数据库连接。开发人员可以通过配置文件来指定数据库连接信息,Spring会自动创建和管理数据库连接池。

2)数据库事务管理

Spring提供了一个用于管理事务的抽象层,可以让开发人员更方便地管理数据库事务。开发人员可以通过配置文件来指定事务管理器,Spring会自动处理事务的提交和回滚。

3)数据库访问对象(DAO)

Spring提供了一个用于访问数据库的抽象层,可以让开发人员更方便地访问数据库。开发人员可以通过配置文件来指定数据访问对象,Spring会自动创建数据访问对象并管理其生命周期。

Spring JDBC

Spring JDBC是Spring框架提供的一种简单易用的JDBC封装技术,它可以帮助开发人员更方便地使用JDBC进行数据库操作。Spring JDBC封装了JDBC的一些繁琐的操作,提供了一些简单易用的API,可以让开发人员更快速地进行数据库操作。

Spring JDBC的常用方法包括:

1)获取数据库连接

使用Spring JDBC可以通过调用JdbcTemplate对象的getDataSource方法来获取数据库连接。

2)执行SQL语句

使用Spring JDBC可以通过调用JdbcTemplate对象的execute方法来执行SQL语句。execute方法可以接受一个SQL语句和一个SqlParameterSource对象作为参数,SqlParameterSource对象用于传递SQL语句中的参数。

3)执行查询操作

使用Spring JDBC可以通过调用JdbcTemplate对象的query方法来执行查询操作。query方法可以接受一个SQL语句、一个SqlParameterSource对象和一个RowMapper对象作为参数,RowMapper对象用于将查询结果映射到Java对象中。

4)执行更新操作

使用Spring JDBC可以通过调用JdbcTemplate对象的update方法来执行更新操作。update方法可以接受一个SQL语句和一个SqlParameterSource对象作为参数,SqlParameterSource对象用于传递SQL语句中的参数。

5)执行批量更新操作

使用Spring JDBC可以通过调用JdbcTemplate对象的batchUpdate方法来执行批量更新操作。batchUpdate方法可以接受一个SQL语句和一个SqlParameterSource数组作为参数,SqlParameterSource数组用于传递SQL语句中的多个参数。

下面是一个使用Spring JDBC进行数据库操作的示例代码:

public class UserDaoImpl implements UserDao {

    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public User findById(int id) {
        String sql = "SELECT * FROM user WHERE id = ?";
        SqlParameterSource params = new MapSqlParameterSource("id", id);
        User user = jdbcTemplate.queryForObject(sql, params, new UserRowMapper());
        return user;
    }

    public void save(User user) {
        String sql = "INSERT INTO user (name, age) VALUES (:name, :age)";
        SqlParameterSource params = new BeanPropertySqlParameterSource(user);
        jdbcTemplate.update(sql, params);
    }

    public void update(User user) {
        String sql = "UPDATE user SET name = :name, age = :age WHERE id = :id";
        SqlParameterSource params = new BeanPropertySqlParameterSource(user);
        jdbcTemplate.update(sql, params);
    }

    public void delete(int id) {
        String sql = "DELETE FROM user WHERE id = ?";
        SqlParameterSource params = new MapSqlParameterSource("id", id);
        jdbcTemplate.update(sql, params);
    }

    private static final class UserRowMapper implements RowMapper<User> {
        public User mapRow(ResultSet rs, int rowNum) throws SQLException {
            User user = new User();
            user.setId(rs.getInt("id"));
            user.setName(rs.getString("name"));
            user.setAge(rs.getInt("age"));
            return user;
        }
    }
}

Spring JdbcTemplate

Spring JdbcTemplate是Spring JDBC的一个子项目,它提供了更便捷的JDBC操作方式,并且可以自动处理JDBC中的异常和数据类型转换。Spring JdbcTemplate使用起来比Spring JDBC更加简单,而且性能也更好。

Spring JdbcTemplate的常用方法包括:

1)执行SQL语句

使用Spring JdbcTemplate可以通过调用JdbcTemplate对象的execute方法来执行SQL语句。execute方法可以接受一个SQL语句和一个PreparedStatementCallback对象作为参数,PreparedStatementCallback对象用于处理PreparedStatement对象。

2)执行查询操作

使用Spring JdbcTemplate可以通过调用JdbcTemplate对象的query方法来执行查询操作。query方法可以接受一个SQL语句、一个RowMapper对象和一个可变参数数组作为参数,可变参数数组用于传递SQL语句中的参数。

3)执行更新操作

使用Spring JdbcTemplate可以通过调用JdbcTemplate对象的update方法来执行更新操作。update方法可以接受一个SQL语句和一个可变参数数组作为参数,可变参数数组用于传递SQL语句中的参数。

4)执行批量更新操作

使用Spring JdbcTemplate可以通过调用JdbcTemplate对象的batchUpdate方法来执行批量更新操作。batchUpdate方法可以接受一个SQL语句、一个BatchPreparedStatementSetter对象和一个可变参数数组作为参数,BatchPreparedStatementSetter对象用于设置PreparedStatement对象的参数。

下面是一个使用Spring JdbcTemplate进行数据库操作的示例代码:

public class UserDaoImpl implements UserDao {

    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public User findById(int id) {
        String sql = "SELECT * FROM user WHERE id = ?";
        Object[] params = {id};
        User user = jdbcTemplate.queryForObject(sql, params, new UserRowMapper());
        return user;
    }

    public void save(User user) {
        String sql = "INSERT INTO user (name, age) VALUES (?, ?)";
        Object[] params = {user.getName(), user.getAge()};
        jdbcTemplate.update(sql, params);
    }

    public void update(User user) {
        String sql = "UPDATE user SET name = ?, age = ? WHERE id = ?";
        Object[] params = {user.getName(), user.getAge(), user.getId()};
        jdbcTemplate.update(sql, params);
    }

    public void delete(int id) {
        String sql = "DELETE FROM user WHERE id = ?";
        Object[] params = {id};
        jdbcTemplate.update(sql, params);
    }

    private static final class UserRowMapper implements RowMapper<User> {
        public User mapRow(ResultSet rs, int rowNum) throws SQLException {
            User user = new User();
            user.setId(rs.getInt("id"));
            user.setName(rs.getString("name"));
            user.setAge(rs.getInt("age"));
            return user;
        }
    }
}

Spring的事务管理

Spring的事务管理是Spring框架中的一个重要功能,它可以帮助我们管理应用程序中的事务,确保数据的完整性和一致性。Spring的事务管理支持编程式事务管理和声明式事务管理两种方式,其中声明式事务管理又分为基于XML方式和基于Annotation方式两种。

基于XML方式的声明式事务管理

在基于XML方式的声明式事务管理中,我们需要通过配置XML文件来实现事务管理。首先,我们需要在XML文件中配置TransactionManager和DataSource,然后通过AOP配置来实现事务管理。具体步骤如下:

1)配置TransactionManager和DataSource

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/test"/>
    <property name="username" value="root"/>
    <property name="password" value="password"/>
</bean>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

2)配置AOP

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="save"/>
        <tx:method name="update"/>
        <tx:method name="delete"/>
        <tx:method name="find*" read-only="true"/>
    </tx:attributes>
</tx:advice>

<aop:config>
    <aop:pointcut id="userServicePointcut" expression="execution(* com.example.service.UserService.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="userServicePointcut"/>
</aop:config>

在上面的代码中,我们通过tx:advice配置了一个名为txAdvice的事务通知,它的transaction-manager属性指定了我们之前配置的TransactionManager。然后,我们通过tx:attributes配置了一些方法的事务属性,比如save、update、delete方法需要进行事务管理,而find*方法不需要。最后,我们通过aop:config配置了一个名为userServicePointcut的切入点,它的expression属性指定了需要进行事务管理的方法所在的类和方法名,然后通过aop:advisor将txAdvice和userServicePointcut绑定在一起。

基于Annotation方式的声明式事务管理

在基于Annotation方式的声明式事务管理中,我们可以使用@Transactional注解来标记需要进行事务管理的方法。具体步骤如下:

1)配置TransactionManager和DataSource(同上)

2)在需要进行事务管理的方法上添加@Transactional注解

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    @Transactional
    public void save(User user) {
        userDao.save(user);
    }

    @Override
    @Transactional
    public void update(User user) {
        userDao.update(user);
    }

    @Override
    @Transactional
    public void delete(int id) {
        userDao.delete(id);
    }

    @Override
    @Transactional(readOnly = true)
    public User findById(int id) {
        return userDao.findById(id);
    }
}

在上面的代码中,我们在需要进行事务管理的方法上添加了@Transactional注解,其中readOnly属性表示该方法是只读的,不需要进行事务管理。

总之,Spring的事务管理非常重要,可以帮助我们管理应用程序中的事务,确保数据的完整性和一致性。无论是基于XML方式的声明式事务管理还是基于Annotation方式的声明式事务管理,都可以满足我们的需求,具体使用哪种方式取决于个人喜好和项目需求。

猜你喜欢

转载自blog.csdn.net/qq_61902168/article/details/131008379