spring 入门 (二)
标准三连
AOP 是什么?
- AOP(Aspect Oriented Programming),即面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
- AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型
- 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
- AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码
- 经典应用:事务管理、性能监视、安全检查、缓存 、日志等
- Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码
- AspectJ是一个基于Java语言的AOP框架,Spring2.0开始,Spring AOP引入对Aspect的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入
实现原理
a. aop底层将采用代理机制进行实现。
b. 接口 + 实现类 :spring采用 jdk 的动态代理Proxy。
c. 实现类:spring 采用 cglib字节码增强。
为什么要使用 AOP?
提供开发效率。降低维护成本。重复的代码只些一次,统一维护。
AOP术语
1.target:目标类,需要被代理的类。例如:UserService
2.Joinpoint(连接点):所谓连接点是指那些可能被拦截到的方法。例如:所有的方法
3.PointCut 切入点:已经被增强的连接点。例如:addUser()
4.advice 通知/增强,增强代码。例如:after、before
5. Weaving(织入):是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程.
6.proxy 代理类
7. Aspect(切面): 是切入点pointcut和通知advice的结合
一个线是一个特殊的面。
一个切入点和一个通知,组成成一个特殊的面。
怎么使用?
- 导入jar包 (加入 Spring-context 依赖 一共会导入6个 Spring jar包) 还要 导入 aspectjweaver
- 如果目标对象有实现接口,spring 的 aop 会使用 jdk 代理;
- 如果目标对象没有实现接口, spring 的aop 会使用 cglib cglib 子类代理(目标对象不能为final)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
- 添加配置文件
<!--配置切面类 自定一个 切面类/通知类/工具类 -->
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.aop"/>
<bean id="loggerAspect" class="com.aop.aspect.LoggerAspect"/>
<aop:config>
<!--配置切入点 expression->切入点表达式 -->
<aop:pointcut id="logger" expression="execution(* com.aop.aspect.Demo01.*(..) )"/>
<!--配置通知 -> 引用切面类-->
<aop:aspect ref="loggerAspect">
<!--配置通知方法 -> 引用切入点->
<aop:before pointcut-ref="logger" method="before"/>
<aop:after-returning method="returning" pointcut-ref="logger"/>
<aop:after-throwing method="afterThorwing" pointcut-ref="logger"/>
<aop:after method="after" pointcut-ref="logger"/>-->
<aop:around method="around" pointcut-ref="logger"/>
</aop:aspect>
</aop:config>
- 切面类 >> 用于增强目标对象的功能
public class LoggerAspect {
public void before() {
System.out.println("before");
}
public void afterThorwing() {
System.out.println("throwing");
}
public void after() {
System.out.println("after");
}
public void returning() {
System.out.println("returning");
}
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
try {
System.out.println("before");
Object proceed = joinPoint.proceed();
System.out.println("returning");
return proceed;
} catch (Throwable throwable) {
System.out.println("afterThrowing");
throwable.printStackTrace();
return null;
} finally {
System.out.println("after");
}
}
}
- 目标类
@Component
public class Demo01 {
public void save(){
System.out.println("保存");
}
}
- 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class AOPTest {
@Autowired
Demo01 demo01;
@Test
public void aopTest(){
demo01.save();
}
}
AOP 通知类型
前置通知: 在执行目标对象方法之前执行
后置通知: 在执行目标对象方法之后执行(异常不执行)
异常通知: 在执行目标对象方法发生异常时候执行
最终通知: 执行完目标对象方法后 执行的方法就是最终通知(异常也会执行)
环绕通知: 环绕目标对象方法执行(相当于前面四种标价结合)[推荐使用]
aop:config标签
作用:声明 aop 配置。
子标签
aop:poincut
作用:配置切入点表达式
属性 | 作用 |
---|---|
id | 唯一标识 |
expression | 切入点表达式,扫描符合条件的类的方法 |
aop:poincut 的子标签
aop:aspect
作用:配置切面。
属性 | 作用 |
---|---|
ref | 引用切面类 |
aop:aspect 的子标签
作用:配置通知
aop:before && aop:after-returning && aop:after-throwing && aop:after && aop:around
属性 | 作用 |
---|---|
pointcut-ref | 引用切入点 |
method | 要执行的方法名 |
切入点表达式
切入点表达式:
作用:生成代理对象。自动对符合切入点表达式规则的类生成代理对象
需求1:最常用(对service生成代理对象)
execution(* com.itheima.service.impl.*ServiceImpl.*(..))
需求2:最全的写法
execution(public void com.itheima.service.impl.UserServiceImpl.save())
execution(public void com.itheima.service.impl.UserServiceImpl.save(int)) 方法必须有一个参数为int类型
需求3:省略访问修饰符,返回值为任意类型
execution(* com.itheima.service.impl.UserServiceImpl.save())
需求4:包名用*,标识支持任意包; 类名也用*,方法也用*
execution(* com.*.*.*.*.*()) 从后往前看,最后一个*是方法;倒数第二个是类。
需求5:省略子包
execution(* com..*.*())
需求6:拦截所有方法,最简单语法
execution(* *(..))
需求7:拦截所有save开头的方法
execution(* save*(..))
需求8:拦截所有save开头的方法,参数任意
execution(* save*(*)) 参数任意,但必须要有参数
execution(* save*(..)) 参数任意,参数可有可无
需求9:拦截所有的save方法或者update方法
execution(* save(..)) or execution(* update(..))
execution(* save(..)) || execution(* update(..))
需求10:不拦截update()方法
!execution(* update(..))
not execution(* update(..)) 注意:前面有空格
需求11:拦截容器中的指定的bean
bean(userService)
bean(*Service) 对IOC容器中所有以Service结尾的类生成代理。
注解实现
注解 | 使用位置 |
---|---|
@Component | 切面类,目标类 |
@Aspect | 切面类 |
@Pointcut | 切入点吗,随便写个方法,上面加上这个注解,注解里 写上 切入点表达式 |
@before | 写在切面方法上,前置通知 |
@after | 写在切面方法上,最终通知 |
@after-returning | 写在切面方法上,后置通知 |
@after-throwing | 写在切面方法上,异常通知 |
@around | 写在切面方法上,环绕通知 |
@Order | 设置切面类的优先级别,数字越小优先级别越高 |
切面类
@Component
@Aspect
public class LoggerAspect {
@Pointcut("execution(* com.aop.aspect.Demo01.*(..) )")
public void printcut(){};
@Before("printcut()")
public void before() {
System.out.println("before");
}
@AfterThrowing("printcut()")
public void afterThorwing() {
System.out.println("throwing");
}
@After("printcut()")
public void after() {
System.out.println("after");
}
@AfterReturning("printcut()")
public void afterRunning() {
System.out.println("afterRunning");
}
@Around("printcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
try {
System.out.println("before");
Object proceed = joinPoint.proceed();
System.out.println("afterRunning");
return proceed;
} catch (Throwable throwable) {
System.out.println("afterThrowing");
throwable.printStackTrace();
return null;
} finally {
System.out.println("after");
}
}
}
开启切面注解配置
<!--开启注解配置-->
<aop:aspectj-autoproxy/>