探秘SpringAop
概述:
一 aop使用
二 aop原理
三 aop运用
四 aop实战
五 总结
1.面向切面的编程
2.Spring中的aop
3.小结
面向切面的编程就是一个编程范式,不是一种编程语言,解决特定的问题,不能解决所有问题.是对oop的补充,不是替代.
其他编程方式:
面向过程
面向对象
函数式编程
事件驱动式编程
面向切面的编程
aop的初衷:
dry:dont't repeat yourself
soc:separation of concerns 关注点分离
水平分离:展示层 ->服务层 -> 持久层
垂直分离:模块划分(订单,库存等)
切面分离:分离功能性需求与非功能性需求
好处:
集中处理某一关注点/横切逻辑
可以很方便的添加/删除 关注点
侵入性少,增强代码可读性及可维护性
使用场景:
权限控制 缓存控制 事务控制 审计日志 性能监控 分布式追踪 异常处理
支持aop的语言
java .net c/c++ ruby python php
二实战:利用aop改善既有代码
案例背景:
1.有一个产品管理的服务
2.产品添加/删除的操作只能管理员才能进行
3.普通实现 vs aop实现
技术:Springboot 1.4.3.release
pom.xml
org.springframework.boot
spring-boot-starter-parent
1.4.3.RELEASE
org.springframework.boot
spring-boot-starter-aop
项目架构
aop-guide
src
main
java
com.imooc.aop
domain
Product.java
security
AdminQnly.java
CurrentUserHolder.java
service
ProductService.java
AuthService.java
AopGuideApplication.java
resources
application.properties
test
java
com.imooc.aop
AopGuideApplicationTests.java
代码实现
1.
package com.imooc.aop.domain;
public class Product {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2.
package com.imooc.aop.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.imooc.aop.domain.Product;
import com.imooc.aop.security.AdminOnly;
@Service
public class ProductService {
/**
* 也可以拦截方法,真正的做到无侵入
*/
@Autowired
AuthService authService;
@AdminOnly
public void insert(Product product){
//authService.checkAcess();
System.out.println("insert product");
}
@AdminOnly
public void delete(Long id){
//authService.checkAcess();
System.out.println("delete product");
}
}
3.
package com.imooc.aop.service;
import org.springframework.stereotype.Service;
import com.imooc.aop.security.CurrentUserHolder;
@Service
public class AuthService {
public void checkAcess(){
String user = CurrentUserHolder.get();
if(!"admin".equals(user)){
throw new RuntimeException("oprator not allow");
}
}
}
4.
package com.imooc.aop.security;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AdminOnly {
}
5.
package com.imooc.aop.security;
public class CurrentUserHolder {
private static final ThreadLocal
holder = new ThreadLocal();
public static String get() {
return holder.get()==null?"":holder.get();
}
public static void set(String user) {
holder.set(user);
}
}
6.
package com.imooc.aop.security;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.imooc.aop.service.AuthService;
@Aspect
@Component
public class SecurityAspect {
@Autowired
AuthService authService;
//拦截标注有AdminOnly这种注解的方法
@Pointcut("@annotation(AdminOnly)")
public void adminOnly(){
}
@Before("adminOnly()")
public void check(){
authService.checkAcess();
}
}
7.
package com.imooc.aop.tesst;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.imooc.aop.security.CurrentUserHolder;
import com.imooc.aop.service.ProductService;
@RunWith(SpringRunner.class)
@SrpingBootTest
public class AopGuideApplicationTests {
/**
* 这种方式,代码有侵入性,如果使用aop的方式,可以把代码给分离开来.
*/
@Autowired
ProductService productService;
@Test(expected=Exception.class)
public void annoInsertTest(){
CurrentUserHolder.set("tom");
productService.delete(1L);
}
@Test
public void adminInsertTest(){
CurrentUserHolder.set("admin");
productService.delete(1L);
}
}
上节内容:
什么是面向切面编程,为何用及好处
概念:1.切面2织入
aop分类及应用场景
本节:
Springaop 使用
xml
注解
pointcut
expression
关于切面的注解:
AspectJ
@Aspect
@Pointcut
@Advice
切面示例:
@Aspect
@Component
public class SecurityAspect {
@Autowired
AuthService authService;
//拦截标注有AdminOnly这种注解的方法
@Pointcut("@annotation(AdminOnly)")
public void adminOnly(){
}
@Before("adminOnly()")
public void check(){
authService.checkAcess();
}
}
Pointcut
express
5种advice
切面表达式介绍:expression
指示器:designators
匹配方法
execution()
匹配注解 :
@target()
@args()
@within()
@annotation()
匹配包/类型
within()
匹配对象
this()
bean()
target()
匹配参数
args()
wildcards(通配符):
* 匹配任意数量的字符
..匹配任意的子包或参数
+ 匹配指定类及其子类
operators(运算符):
&& 与
|| 或
! 非
within表达式
@Aspect
@Component
public class PackageOrTypeAspect {
@Autowired
AuthService authService;
//拦截匹配ProductService类里头的所有方法
//@Pointcut("within(com.imooc.service.ProductService)")
//拦截匹配的com.imooc包下及子包下类里头的所有方法
@Pointcut("within(com.imooc.service..*)")
public void matchType(){
}
@Before("matchType()")
public void check(){
authService.checkAcess();
}
}
对象匹配
@Aspect
@Component
public class ObjectAspect {
@Autowired
AuthService authService;
//拦截匹配的实现IDao接口的目标对象(而非aop代理后的对象)的方法,这里就是DemoDao的方法
@Pointcut("this(com.imooc.log.Loggable)")
public void matchCondition(){
}
//拦截匹配aop对象的目标对象为指定类型的方法,就是DemoDao的aop代理对象的方法
@Pointcut("target(com.imooc.log.Loggable)")
public void matchCondition(){
}
//拦截匹配的所有以service结尾的bean里头的方法
@Pointcut("bean(LogService)")
public void matchCondition(){
}
@Before("matchCondition()")
public void check(){
authService.checkAcess();
}
}
参数匹配
@Aspect
@Component
public class ArgsAspect {
@Autowired
AuthService authService;
//拦截匹配的任何一个只有Long参数的方法与com.imooc.service包下所有的类里头中方法
//@Pointcut("args(Long) && within(com.imooc.service.*)")
//拦截匹配的任何一个只有Long,String参数的方法与com.imooc.service包下所有的类里头中方法
//@Pointcut("args(Long,String) && within(com.imooc.service.*)")
//拦截匹配的任何以find开头而且只有一个Long参数的方法
//@Pointcut("execution(* *..find *(Long)")
//拦截匹配的任何以find开头而且第一个Long参数的方法
//@Pointcut("execution(* *..find *(Long,..)")
//拦截匹配的第一个参数为Long型的方法
//@Pointcut("args((Long,..)")
public void matchArgs(){
}
@Before("matchArgs()")
public void check(){
authService.checkAcess();
}
}
注解匹配
@Aspect
@Component
public class AnnotationAspect {
@Autowired
AuthService authService;
//匹配方法标注有AdminOnly的注解的方法
@Pointcut("@annotation(com.imooc.aop.security.AdminOnly)")
//匹配方法标注有Beta类底下的方法,要求的annotation的retentionPolicy级别为class
@Pointcut("@within(com.imooc.aop.security.Beta)")
//匹配方法标注有Repository的类底下的方法,要求的annotation的retentionPolicy级别为runtime
@Pointcut("@target(com.imooc.aop.security.Repository)")
//匹配传入的参数类标注有Repository注解的方法
@Pointcut("@args(com.imooc.aop.security.Repository)")
public void matchAnno(){
}
@Before("matchAnno()")
public void check(){
authService.checkAcess();
}
}
execution表达式
/**
* //匹配任何公共方法
@Pointcut("execution(public * com.imooc.service.*.*(..))")
//匹配com.imooc包及子包下Service类中无参方法
@Pointcut("execution(* com.imooc..*Service.*())")
//匹配com.imooc包及子包下Service类中的任何只有一个参数的方法
@Pointcut("execution(* com.imooc..*Service.*(*))")
//匹配com.imooc包及子包下任何类的任何方法
@Pointcut("execution(* com.imooc..*.*(..))")
//匹配com.imooc包及子包下返回值为String的任何方法
@Pointcut("execution(String com.imooc..*.*(..))")
//匹配异常
execution(public * com.imooc.service.*.*(..) throws java.lang.IllegalAccessException)
* Created by cat on 2017-02-19.
*/
@Aspect
@Component
public class ExecutionAspectConfig {
// @Pointcut("execution(public * com.imooc.service..*Service.*(..) throws java.lang.IllegalAccessException)")
// public void matchCondition(){}
//
// @Before("matchCondition()")
// public void before(){
// System.out.println("");
// System.out.println("###before");
// }
}
5种advice注解
1.@Before 前置通知
2.@After(finally) 后置通知,方法执行完之后
3.@AfterReturning 返回通知,成功执行之后
4.@AfterThrowing 异常通知,抛出异常之后
5.@Around 环绕通知
@Aspect
@Component
public class AdviceAspect {
@Pointcut("@within(com.imooc.demo.security.NeedSecured)")
public void annoTargetVsWithinDemo(){}
@Before("annoTargetVsWithinDemo && within(com.imooc..*)")
public void demo(){
System.out.println("##aop by within");
throw new RuntimeException("do not proceed");
}
@After("annoTargetVsWithinDemo && within(com.imooc..*)")
public void afterDemo(){
System.out.println("##aop by within");
}
@AfterReturning("annoTargetVsWithinDemo && within(com.imooc..*)")
public void afterReturningDemo(){
System.out.println("##aop by within");
}
@AfterThrowing("annoTargetVsWithinDemo && within(com.imooc..*)")
public void afterThrowingDemo(){
System.out.println("##aop by within");
}
public Object AfterThrowing(ProceedingJoinPoint pjp)throws Throwable{
System.out.println("#Before");
try{
Object result = pjp.proceed(pjp.getArgs());
System.out.println("#return");
return result;
}catch(Throwable throwable){
System.out.println("#ex");
throw throwable;
}finally{
System.out.println("#after");
}
}
}
/**
* @Before("matchAnno()")
* @After("matchAnno())") //相当于finally
* @AfterReturning("matchException()")
* @AfterThrowing("matchException()")
* @Around("matchException()")
* @Before(value = "matchLongArg() && args(productId)")
* public void beforeWithArgs(Long productId)
* @AfterReturning(value = "matchReturn()",returning = "returnValue")
* public void getReulst(Object returnValue)
* Created by cat on 2017-02-19.
*/
@Aspect
@Component
public class AdviceAspectConfig {
/******pointcut********/
@Pointcut("@annotation(com.imooc.anno.AdminOnly) && within(com.imooc..*)")
public void matchAnno(){}
@Pointcut("execution(* *..find*(Long)) && within(com.imooc..*) ")
public void matchLongArg(){}
@Pointcut("execution(public * com.imooc.service..*Service.*(..) throws java.lang.IllegalAccessException) && within(com.imooc..*)")
public void matchException(){}
@Pointcut("execution(String com.imooc..*.*(..)) && within(com.imooc..*)")
public void matchReturn(){}
/******advice********/
@Before("matchLongArg() && args(productId)")
public void before(Long productId){
System.out.println("###before,get args:"+productId);
}
// @Around("matchException()")
// public java.lang.Object after(ProceedingJoinPoint joinPoint){
// System.out.println("###before");
// java.lang.Object result = null;
// try{
// result = joinPoint.proceed(joinPoint.getArgs());
// System.out.println("###after returning");
// }catch (Throwable e){
// System.out.println("###ex");
// //throw
// e.printStackTrace();
// }finally {
// System.out.println("###finally");
// }
// return result;
// }
}
实现原理
原理概述:织入的时机
1.编译期(AspectJ)
2.类加载时(AspectJ 5+)
3.运行时(Spring aop)
运行时织入是如何实现的:
静态代理 到 动态代理
动态代理:基于接口代理的实现 和 基于继承代理的实现
调用者,通过 proxy对象 间接与 Target 对象打交道
public interface Subject {
void request();
void hello();
}
public class RealSubject implements Subject{
@Override
public void request() {
System.out.println("real subject execute request");
}
@Override
public void hello() {
System.out.println("hello");
}
}
public class Proxy implements Subject{
private RealSubject realSubject;
public Proxy(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
System.out.println("before");
try{
realSubject.request();
}catch (Exception e){
System.out.println("ex:"+e.getMessage());
throw e;
}finally {
System.out.println("after");
}
}
@Override
public void hello() {
realSubject.hello();
}
}
public class Client {
public static void main(String[] args){
Subject subject = new Proxy(new RealSubject());
subject.request();
}
}
静态代理 以及 动态代理
缺点:代理的方法越多,重复的逻辑代码就越多
动态代理:基于接口 基于继承 实现动态代理
实现代表: jdk代理 cglib代理
jdk代理要点:
1.通过java.lang.reflect.Proxy 去生成动态代理类
2.代理类实现织入的逻辑需要实现InvocationHandler接口
3.只能基于接口进行动态代理
public class JdkProxySubject implements InvocationHandler{
private RealSubject realSubject;
//强引用,通过构造器把他注入进来
public JdkProxySubject(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before");
Object result = null;
try{
result = method.invoke(realSubject,args);
}catch (Exception e){
System.out.println("ex:"+e.getMessage());
throw e;
}finally {
System.out.println("after");
}
return result;
}
}
public class Client {
public static void main(String[] args){
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
Subject subject = (Subject) Proxy.newProxyInstance(Client.class.getClassLoader(),new Class[]{Subject.class},new JdkProxySubject(new RealSubject()));
subject.hello();
}
}
cglib代理
是基于继承的方式实现的动态代理
public class DemoMethodInterceptor implements MethodInterceptor{
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("before in cglib");
Object result = null;
try{
result = proxy.invokeSuper(obj, args);
}catch (Exception e){
System.out.println("get ex:"+e.getMessage());
throw e;
}finally {
System.out.println("after in cglib");
}
return result;
}
}
public class Client {
public static void main(String[] args){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealSubject.class);
enhancer.setCallback(new DemoMethodInterceptor());
Subject subject = (Subject) enhancer.create();
subject.hello();
}
}
Spring如何创建代理bean的
如果目标对象实现了接口,默认采用jdk动态代理,如若强制使用cglib代理,就采用cglib动态代理
如果目标对象没有实现接口,默认采用cglib动态代理.
多个aop叠加
public abstract class Handler {
private Handler sucessor;
public Handler getSucessor() {
return sucessor;
}
public void setSucessor(Handler sucessor) {
this.sucessor = sucessor;
}
public void execute(){
handleProcess();
if(sucessor != null){
sucessor.execute();
}
}
protected abstract void handleProcess();
}
public abstract class ChainHandler {
public void execute(Chain chain){
handleProcess();
chain.proceed();
}
protected abstract void handleProcess();
}
public class Client {
static class HandlerA extends Handler{
@Override
protected void handleProcess() {
System.out.println("handle by a");
}
}
static class HandlerB extends Handler{
@Override
protected void handleProcess() {
System.out.println("handle by b");
}
}
static class HandlerC extends Handler{
@Override
protected void handleProcess() {
System.out.println("handle by c");
}
}
public static void main(String[] args){
Handler handlerA = new HandlerA();
Handler handlerB = new HandlerB();
Handler handlerC = new HandlerC();
handlerA.setSucessor(handlerB);
handlerB.setSucessor(handlerC);
handlerA.execute();
}
}
public class Chain {
private List handlers;
private int index = 0;
public Chain(List handlers) {
this.handlers = handlers;
}
public void proceed(){
if(index >= handlers.size()){
return ;
}
handlers.get(index++).execute(this);
}
}
public class ChainClient {
static class ChainHandlerA extends ChainHandler{
@Override
protected void handleProcess() {
System.out.println("handle by chain a");
}
}
static class ChainHandlerB extends ChainHandler{
@Override
protected void handleProcess() {
System.out.println("handle by chain b");
}
}
static class ChainHandlerC extends ChainHandler{
@Override
protected void handleProcess() {
System.out.println("handle by chain c");
}
}
public static void main(String[] args){
List handlers = Arrays.asList(
new ChainHandlerA(),
new ChainHandlerB(),
new ChainHandlerC()
);
Chain chain = new Chain(handlers);
chain.proceed();
}
}