参考:探秘Spring AOP
参考代码:
面向切面编程(AOP)
概览、AOP使用、AOP原理、AOP开源运用。
是一种编程范式,不是编程语言
解决特定问题
是OOP的补充
1、AOP的作用:
DRY:Don't Repeat Yourself。提取公共的代码
SoC: Separation of Concerns
2、AOP的优点:
集中处理某一关注点/横切逻辑
可以很方便的添加/删除关注点
入侵性少,增强代码可读性及可维护性
3、AOP的应用场景:
权限控制——经常使用
缓存控制——
事务控制——
审计日志——
性能监控——
分布式追踪——
异常处理——可以用:@ControllerAdvice+@ExceptionHandler
4、支持AOP的编程语言:几乎所有
要求:
1、产品管理的服务
2、产品添加/删除的操作只能管理员才能进行
3、普通实现 VS AOP实现
@Pointcut("@annotation(AdminOnly)")
public void adminOnly(){
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AdminOnly{
}
XML配置->Pointcut expression
注解方式->Pointcut expression
1、主要注解
@Aspect
@Pointcut
@Advice
2、切面表达式组成(Pointcut expression)——3个部分:
匹配包/类型
//匹配ProductService类里头的所有方法
@Pointcut("within(com.imooc.service.ProductService)")
public void matchType(){}
//匹配com.imooc包及子包下所有类的方法
@Pointcut("within(com.imooc..*)")
public void matchPackage(){}
//匹配AOP对象的目标对象为指定类型的方法,即DemoDao的aop代理对象的方法
@Pointcut("this(com.imooc.DemoDao)")
pubic void thisDemo(){}
//匹配实现IDao接口的目标对象(而不是aop代理后的对象)的方法
@Pointcut("target(com.imooc.IDao)")
public void targetDemo(){}
//this 可以拦截 DeclareParents(Introduction)
//target 不拦截 DeclareParents(Introduction)
//匹配所有以Service结尾的bean里头的方法
@Pointcut("bean(*Service)")
public void beanDemo(){}
//匹配任何以find开头而且只有一个Long参数的方法
@Pointcut("execution(* *..find*(Long))")
public void argsDemo1(){}
//匹配任何以find开头的而且第一个参数为Long型的方法
@Pointcut("execution(* *..find*(Long,..))")
public void argsDemo2(){}
//匹配任何只有一个Long参数的方法
@Pointcut("within(com.imooc..*) && args(Long)")
public void argsDemo3(){}
//匹配第一个参数为Long型的方法
@Pointcut("within(com.imooc..*) && args(Long,..)")
public void argsDemo4(){}
//匹配方法标注有AdminOnly的注解的方法
@Pointcut("@annotation(com.imooc.anno.AdminOnly) && within(com.imooc..*)")
public void annoDemo(){}
//匹配标注有NeedSecured的类底下的方法 //class级别
@Pointcut("@within(com.imooc.anno.NeedSecured) && within(com.imooc..*)")
public void annoWithinDemo(){}
//匹配标注有NeedSecured的类及其子类的方法 //runtime级别在spring context的环境下,二者没有区别
@Pointcut("@target(com.imooc.anno.NeedSecured) && within(com.imooc..*)")
public void annoTargetDemo(){}
//匹配传入的参数类标注有Repository注解的方法
@Pointcut("@args(com.imooc.anno.NeedSecured) && within(com.imooc..*)")
public void annoArgsDemo(){}
格式:execution(
modifier-pattern?
ret-type-pattern
declaring-type-pattern?
name-pattern(param-pattern)
throws-pattern?
)
//匹配任何公共方法
@Pointcut("execution(publiccom.imooc.service.*.*(..))")
public void matchCondition1(){}
//匹配com.imooc包及子包下Service类中无参方法
@Pointcut("execution(* com.imooc..*Service.*())")
public void matchCondition2(){}
//匹配com.imooc包及子包下Service类中的任何只有一个参数的方法
@Pointcut("execution(* com.imooc..*Service.*(*))")
public void matchCondition3(){}
//匹配com.imooc包及子包下任何类的任何方法
@Pointcut("execution(* com.imooc..*.*(..))")
public void matchCondition4(){}
//匹配com.imooc包及子包下返回值为String的任何方法
@Pointcut("execution(String com.imooc..*.*(..))")
public void matchCondition5(){}
//匹配异常
@Pointcut("execution(publiccom.imooc.service.*.*(..) throws java.lang.IllegalAccessException)")
public void matchCondition6(){}
@Before,前置通知
@After(finally),后置通知,方法执行完之后
@AfterReturning,返回结果通知,成功执行后
@AfterThrowing,异常通知,抛出异常之后(如果有@Around注解时使用try-catch,@AfterThrowing无效)
@Around,环绕通知。
@Before("matchLongArg() && args(productId)")
public void before(Long productId){
this.logger.info("###before,get args:"+productId+"======================ADVICE");
}
@After("matchLongArg() && within(com.imooc..*)")
public void afterMatch(){
this.logger.info("###after =========================================ADVICE");
this.logger.info("");
}
@AfterReturning(returning = "object",pointcut = "matchReturn() && within(com.imooc..*)")
public void afterReturningMatch(Object object){
this.logger.info("###afterReturning,result={} ================ =ADVICE",object);
}
@AfterThrowing("matchException()")
public void afterThrowing(){
this.logger.info("###afterThrowing========================= =ADVICE-afterThrowing");
}
@Around("matchException()")
public java.lang.Object after(ProceedingJoinPoint joinPoint){
this.logger.info("###before===================================ADVICE-Around");
java.lang.Object result = null;
try{
result = joinPoint.proceed(joinPoint.getArgs());
this.logger.info("###after returning===================================ADVICE-Around");
}catch (Throwable e){
this.logger.info("###ex===================================ADVICE-Around");
//throw
e.printStackTrace();
}finally {
this.logger.info("###finally===================================ADVICE-Around");
}
return result;
}
JDK静态代理
1、接口:Subject.java
public interface Subject {
void request();
}
2、具体实现类:RealSubject.java
public class RealSubject implements Subject {
private final Logger logger = LoggerFactory.getLogger(RealSubject.class);
@Override
public void request() {
this.logger.info("real subject execute request Method");
}
}
3、代理类:Proxy.java
public class Proxy implements Subject{
private final Logger logger = LoggerFactory.getLogger(Proxy.class);
private RealSubject realSubject;
public Proxy(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
this.logger.info("before");
try {
realSubject.request();
} catch (Exception e) {
// e.printStackTrace();
this.logger.info("ex:"+e.getMessage());
}finally {
this.logger.info("after");
}
}
}
4、客户端(测试类)
@Test
public void contextLoads() {
Subject subject = new Proxy(new RealSubject());
subject.request();
}
1、静态代理的缺点
如果有100个类,就需要有100个代理,即使这些代理实现一样的动作。这时就需要动态代理。
2、动态代理的两类实现:基于接口代理、基于继承代理
3、2类实现的代表:JDK代理与Cglib代理。
基于接口代理
1、接口:Subject.java
2、具体实现类:RealSubject.java
3、代理类:JdkProxySubject.java
public class JdkProxySubject implements InvocationHandler{
private final Logger logger = LoggerFactory.getLogger(JdkProxySubject.class);
private RealSubject realSubject;
public JdkProxySubject(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
this.logger.info("before");
Object result = null;
try {
result = method.invoke(realSubject,args);
} catch (Exception e) {
this.logger.info("ex:"+e.getMessage());
throw e;
} finally {
this.logger.info("after");
}
return result;
}
}
4、客户端/测试类:
@Test
public void testJdkProxySubject(){
Subject subject = (Subject) java.lang.reflect.Proxy.newProxyInstance(
ProxyDemoApplicationTests.class.getClassLoader()
,new Class[]{Subject.class}
,new JdkProxySubject(new RealSubject()));
subject.request();
}
5、动态代理与静态代理相比:
如果接口Subject.java添加一个抽象方法,则具体实现类RealSubject.java必须要实现。
同时静态代理类也必须要实现,然后才能在客户端使用。
但是动态代理不需要实现,就可以在客户端使用。
6、JDK代理源码解析
Proxy.newProxyInstance
getProxyClass0,ProxyClassFactory, ProxyGenerator
newInstance
1)Client.java
public class Client {
public static void main(String[] args){
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
Subject subject = (Subject) java.lang.reflect.Proxy.newProxyInstance(Client.class.getClassLoader()
,new Class[]{Subject.class}
,new JdkProxySubject(new RealSubject()));
subject.request();
subject.hello();
}
}
运行,出现文件:com.sun.proxy.$Proxy0.class
基于继承代理
1、接口:Subject.java
2、具体实现类:DemoMethodInterceptor.java
3、代理类:JdkProxySubject.java
public class DemoMethodInterceptor implements MethodInterceptor{
private final Logger logger = LoggerFactory.getLogger(DemoMethodInterceptor.class);
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
this.logger.info("before=======================in cglib");
Object result = null;
try {
result = proxy.invokeSuper(obj, args);
} catch (Exception e) {
this.logger.info("get ex:"+e.getMessage());
throw e;
} finally {
this.logger.info("after====================in cglib");
}
return result;
}
}
4、客户端/测试类
@Test
public void testCglibProxy(){
Enhancer enhancer = new Enhancer();
//生成指定类对象的子类,也就是重写类中的业务函数。
enhancer.setSuperclass(RealSubject.class);
//这里是回调函数,加入intercept()函数
enhancer.setCallback(new DemoMethodInterceptor());
//创建这个子类对象
Subject subject = (Subject) enhancer.create();
subject.request();
subject.hello();
}
5、JDK与Cglib代理对比
JDK | Cglib |
---|---|
只能针对有接口的类的接口方法进行动态代理 | 基于继承来实现代理 |
无法对static、final类进行代理 | |
无法对private,staitc方法进行代理 |
1、Spring如何创建代理bean
2、DefaultSopProxyFactory.java
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
public DefaultAopProxyFactory() {
}
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
return new JdkDynamicAopProxy(config);
} else {
Class> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
} else {
return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
}
}
}
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class>[] ifcs = config.getProxiedInterfaces();
return ifcs.length == 0 || ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0]);
}
}
3、实现的情况:
如果目标对象实现了接口,则默认采用JDK动态代理
如果目标对象没有实现接口,则采用Cglib进行动态代理
如果目标对象实现了接口,且强制cglib代理,则使用cglib代理。
4、强制cglib代理
@SpringBootApplication
@EnableAspectJAutoProxy(proxyTargetClass = true) /*强制使用cglib代理*/
public class ProxyDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ProxyDemoApplication.class, args);
}
}
1、责任链模式
2、Handler.java
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 class Client {
private final Logger logger = LoggerFactory.getLogger(Client.class);
static class HandlerA extends Handler{
private final Logger logger = LoggerFactory.getLogger(HandlerA.class);
@Override
protected void handleProcess() {
logger.info("handle by A");
}
}
static class HandlerB extends Handler{
private final Logger logger = LoggerFactory.getLogger(HandlerB.class);
@Override
protected void handleProcess() {
logger.info("handle by B");
}
}
static class HandlerC extends Handler{
private final Logger logger = LoggerFactory.getLogger(HandlerC.class);
@Override
protected void handleProcess() {
logger.info("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();
}
}
3、改进版责任链模式
Chain.java,构造器的参数为列表,有游标。
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);
}
}
ChainHandler.java
public abstract class ChainHandler {
public void execute(Chain chain){
handlerProcess();
chain.proceed();
}
protected abstract void handlerProcess();
}
Client.java
public class Client {
static class ChainHandlerA extends ChainHandler{
private final Logger logger = LoggerFactory.getLogger(ChainHandlerA.class);
@Override
protected void handlerProcess() {
logger.info("handle by A");
}
}
static class ChainHandlerB extends ChainHandler{
private final Logger logger = LoggerFactory.getLogger(ChainHandlerB.class);
@Override
protected void handlerProcess() {
logger.info("handle by B");
}
}
static class ChainHandlerC extends ChainHandler{
private final Logger logger = LoggerFactory.getLogger(ChainHandlerC.class);
@Override
protected void handlerProcess() {
logger.info("handle by C");
}
}
public static void main(String[] args){
List handlers = Arrays.asList(
new ChainHandlerA(),
new ChainHandlerB(),
new ChainHandlerC()
);
Chain chain = new Chain(handlers);
chain.proceed();
}
}
4、Spring中使用的代码 ReflectiveMethodInvocation.java 中的方法proceed()
@Transactional事务
@PreAuthorize安全
@Cacheable缓存
1、@Transactional事务
@Column(unique = true)
private String name;
这个注解确实不启作用,运行测试代码时没有报错。
2、安全校验@PreAuthorize
MethodSecurityInterceptor
PreInvocationAuthorizationAdviceVoter
ExpressionBasedPreInvocationAdvice
3、缓存@Cacheable
AnnotationCacheAspect
CacheInterceptor
CacheAspectSupport
记录产品修改的操作记录
什么人在什么时间修改了哪些产品的哪些字段修改为什么值。
1、实现思路(分析)
利用aspect去拦截增删改方法
利用反射获取对象的新旧值
利用@Around的advice去记录操作记录
2、领域模型
小结:
反射获取新旧值
@Around的advice去记录修改记录
利用注解去增加中文字段名。
保存修改记录到mongoDB
1、AOP无法拦截内部调用。
MenuService.java
@Component
public class MenuService {
@Cacheable(cacheNames = {"menu"})
public List getMenuList(){
System.out.println("");
System.out.println("mock:get from db");
return Arrays.asList("article","comment","admin");
}
public List getRecommends(){
return this.getMenuList();
// MenuService proxy = ApplicationContextHolder.getContext().getBean(MenuService.class);
// return proxy.getMenuList();
}
}
测试类
@Test
public void testInnerCall(){
System.out.println("call:"+menuService.getRecommends());
System.out.println("call:"+menuService.getRecommends());
}
执行结果,发现并没有受到缓存的AOP拦截,第2次获取数据没有从缓存中获取。
15:13:20.499 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.imooc.CacheDemoApplicationTests]
mock:get from db
call:[article, comment, admin]
mock:get from db
call:[article, comment, admin]
15:13:20.523 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.imooc.CacheDemoApplicationTests]
2、解决方法。
ApplicationContextHolder.java
@Component
public class ApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext ctx;
public static ApplicationContext getContext() {
return ctx;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ctx = applicationContext;
}
}
MenuService.java
public List getRecommends(){
MenuService proxy = ApplicationContextHolder.getContext().getBean(MenuService.class);
return proxy.getMenuList();
}