目录
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容器中。
属性或子元素 | 描述 |
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属性来指定的,该属性的值如下表所示
作用域名称 | |
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配置文件的根元素 |
|
<aop:aspect> | 用于定义一个切面 |
|
<aop:pointcut> | 用于定义一个切入点 |
|
<aop:advisor> | 用于定义一个通知器 |
|
<aop:after-returnding> | 用于定义一个返回通知 |
|
<aop:before> | 用于定义一个前置通知 |
|
<aop:after> | 用于定义一个最终通知 |
|
<aop:around> | 用于定义一个环绕通知 |
|
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方式的声明式事务管理,都可以满足我们的需求,具体使用哪种方式取决于个人喜好和项目需求。