Spring学习
- 7 bean的生命周期
- 8 AOP面向切面编程
- 8.1 AOP相关术语
- 8.2 AOP使用
7 bean的生命周期
bean的生命周期主要为bean实例化、bean属性赋值、bean初始化、销毁bean,其中在实例化和初始化前后都使用后置处理器方法,而InstantiationAwareBeanPostProcessor 继承了BeanPostProcessor
可以看下这篇博客大致了解一下:
一文读懂 Spring Bean 的生命周期
bean的作用域
- 单例(Singleton):在整个应用中,只创建bean的一个实例
- 原型(Prototype):每次注入或者通过Spring的应用上下文获取的时候,都会创建一个新的bean实例
- 会话(Session):在Web应用中,为每个会话创建一个bean实例
- 请求(Rquest):在Web应用中,为每个请求创建一个bean实例
8 AOP面向切面编程
AOP(Aspect Oriented Programming)面向切面编程,利用 AOP 可以使得业务模块只需要关注核心业务的处理,不用关心其他的(如安全管理)。可以不通过修改源代码的方式,在主干功能里面添加新功能。
使用AOP相关的包:百度网盘
8.1 AOP相关术语
通知(Advice)
通知定义了何时使用切面,有以下五种类型的通知
- 前置通知(Before):在目标方法被调用之前调用通知功能
- 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么
- 返回通知(After-returning):在目标方法成功执行之后调用通知
- 异常通知(After-throwing):在目标方法抛出异常后调用通知
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
连接点(Join point)
连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
切点(Pointcut)
切点定义了何处使用切面。会匹配通知(Advice)所要织入的一个或多个连接点。我们通常使用
明确的类和方法名称,或利用正则表达式定义所匹配的类和方法名称来指定这些切点。
切面(Aspect)
切面是通知和切点的结合。通知和切点共同定义了切面的全部内容——它是什么,在何时和何处完成其功能。
引入(Introduction)
引入允许我们向现有的类添加新方法或属性。
目标对象(Target)
Target是织入 Advice 的目标对象
织入(Weaving)
织入就是把通知(Advice)添加到目标对象具体的连接点上的过程
8.2 AOP使用
切入点表达式语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) ),此外还可以对切入点进行限制,因为“&”在XML中有特殊含义,所以在Spring的XML配置里面描述切点时,我们可以使用and来代替“&&”。同样,or和not可以分别用来代替“||”和“!”
基于注解
创建配置类ConfigAop
package springstudy2;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
//创建ConfigAop配置类
@Configuration //@Configuration标记类作为配置类替换xml配置文件
@EnableAspectJAutoProxy(proxyTargetClass = true) //启用自动代理
@ComponentScan(basePackages = {"springstudy2"}) //开启注解扫描
public class ConfigAop {
}
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ComponentScan(basePackages = {“springstudy2”})
三个注解相当于以下代码
<?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:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd" default-lazy-init="false">
<!-- 开启注解扫描 -->
<context:component-scan base-package="springstudy2"></context:component-scan>
<!-- 开启 Aspect 生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
创建User类
package springstudy2;
import org.springframework.stereotype.Component;
//创建User对象
@Component
public class User {
public void add() {
//int a = 10 / 0;
System.out.println("调用add方法...");
}
public void test() {
System.out.println("调用test方法...");
}
}
创建代理 UserProxy
如果有多个代理,可以通过@Order注解指定优先级,数字越小,优先级越高
package springstudy2;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
//创建UserProxy对象
//增强的类
@Component
@Aspect //生成代理对象
@Order(1)//代理优先级,数字越小,优先级越高
public class UserProxy {
//相同切入点抽取
@Pointcut(value = "execution(* springstudy2.User.add(..))")
public void pointdemo() { //pointdemo()方法的内容并不重要,在这里它实际上应该是空的
}
//前置通知
//@Before 注解表示作为前置通知
@Before(value = "pointdemo()")
public void before() {
System.out.println("在目标方法被调用之前调用通知功能.........");
}
@Before(value = "execution(* springstudy2.User.test(..))")
public void before1() {
System.out.println("test 在目标方法被调用之前调用通知功能.........");
}
//后置通知
@After(value = "execution(* springstudy2.User.add(..))")
public void after() {
System.out.println("在目标方法完成之后调用通知.........");
}
//返回通知
@AfterReturning(value = "pointdemo()")
public void afterReturning() {
System.out.println("在目标方法成功执行之后调用通知.........");
}
//异常通知
@AfterThrowing(value = "execution(* springstudy2.User.add(..))")
public void afterThrowing() {
System.out.println("在目标方法抛出异常后调用通知.........");
}
//环绕通知
@Around(value = "execution(* springstudy2.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕之前,在被通知的方法调用之前.........");
//被增强的方法执行
proceedingJoinPoint.proceed();
System.out.println("环绕之后,在被通知的方法调用之后.........");
}
}
上述代码中,@afterReturning 和 @after 注解的执行顺序存在版本问题(有争议),据说从Spirng 5.2.7那一版开始,通知注解的执行顺序如下(不知道后面改没改。。。我的Spring5.3.9是这样):
- @Around注解方法的前半部分业务逻辑
- @Before注解方法的业务逻辑
- 目标方法的业务逻辑
- @AfterThrowing(若目标方法有异常,执行@AfterThrowing注解方法的业务逻辑)
- @AfterReturning(若目标方法无异常,执行@AfterReturning注解方法的业务逻辑)
- @After(不管目标方法有无异常,都会执行@After注解方法的业务逻辑)
- @Around注解方法的后半部分业务逻辑(@Around注解方法内的业务逻辑若对ProceedingJoinPoint.proceed()方法没做捕获异常处理,直接向上抛出异常,则不会执行Around注解方法的后半部分业务逻辑;若做了异常捕获处理,则会执行)
创建 PersonProxy 代理
package springstudy2;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
//创建PersonProxy对象
//增强的类
@Component
@Aspect //生成代理对象
@Order(2)//代理优先级,数字越小,优先级越高
public class PersonProxy {
@Before(value = "execution(* springstudy2.User.add(..))")
public void before() {
System.out.println("person 在目标方法被调用之前调用通知功能.........");
}
}
测试类Test
package springstudy2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ConfigAop.class);//ConfigAop为配置类
User user = context.getBean("user", User.class);
user.add();
user.test();
}
}
执行结果
基于XML
在src目录下创建bean3.xml文件
<?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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--创建对象-->
<bean id="user" class="springstudy2.User"></bean>
<bean id="userproxy" class="springstudy2.UserProxy"></bean>
<bean id="personproxy" class="springstudy2.PersonProxy"></bean>
<aop:config>
<!--切入点-->
<aop:pointcut id="p" expression="execution(* springstudy2.User.add(..))"/>
<!--配置切面-->
<aop:aspect ref="userproxy" order="1"> <!--通过 orde r确定优先级-->
<!--增强作用在具体的方法上-->
<!--method="before"中 before 是UserProxy类中的方法名-->
<aop:before method="before" pointcut-ref="p"/>
<aop:before method="before" pointcut="execution(* springstudy2.User.test(..))"/>
<aop:after method="after" pointcut-ref="p"/>
<aop:after-returning method="afterReturning" pointcut-ref="p"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="p"/>
<aop:around method="around" pointcut-ref="p"/>
</aop:aspect>
<aop:aspect ref="personproxy" order="2">
<!--增强作用在具体的方法上-->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
</beans>
修改Test类
package springstudy2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
User user = context.getBean("user", User.class);
user.add();
user.test();
}
}
基于XML的AOP执行结果
基于注解的AOP执行结果
注:与注解执行顺序不一致,原因是两者使用的AOP代理不同,在基于注解的AOP中,通知的执行顺序是确定的,而多个切面执行顺序由@Order注解来控制,当没有指定@Order注解时,Spring会按照切面类的类名进行排序,从字母顺序最小的切面开始执行,依次递增;在XML配置中,@around注解声明在@before注解前面,则@around注解先执行,否则,@before注解先执行
<aop:after method="after" pointcut-ref="p"/>
<aop:after-returning method="afterReturning" pointcut-ref="p"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="p"/>
<aop:around method="around" pointcut-ref="p"/>
<aop:before method="before" pointcut-ref="p"/>
<aop:before method="before" pointcut="execution(* springstudy2.User.test(..))"/>