####spring容器的三大特点:控制反转,切面编程(Aspect of Program)与事务管理
####这次写了个spring切面编程的demo,spring对切面编程的支持主要在对象的方法调用层次.用最简单的手段实现最多的功能,这是spring的设计哲学.因此spring支持最完善的也是对于方法的拦截.
####spring进行切面编程的主要手段是生成代理.在代理对象执行目标对象所拦截方法前后或者周围提供各种功能.
####切面编程比较多的应用在日志收集和事物管理和权限检查等等场景.将一些横切逻辑单独成可以管理的模块进行统一管理,可以灵活的定义在一类动作执行前后做一些相关的事情.
####spring生成代理的方式,主要使用jdk动态代理和CGLIB动态代理,如果需要代理的对象实现了某个接口,则采用采用jdk提供的默认代理机制,生成的对象也实现该接口并并拥有一个目标对象的引用,这里类似于生成了静态代理类模式的结构.
####如果没有实现某个接口,spring就采用cglib的方式生成代理,它的原理也不难理解,采用为目标类生成子类的方式,然后再重写目标类的目标方法,然后在对应的类周围添加逻辑,这样子类就完成了类似于代理的功能,但是这种代理的缺陷是子类无法重写父类的final函数.
####代理可以分为静态代理和动态代理,aop大都是采用动态代理的机制,在Java中属于类加载机制中的类初始化阶段,对应的引用替换成代理对象,也可以叫做Java里动态绑定机制,这样虽然相比静态代理开销多了一些,但是增加的灵活性是更有价值的.
接下来,我们用spring注解方式验证jdk与cglib两种方法:
最近写反射发现一个问题,使用apache common的MethodUtils方法获取不到对应方法的注解。
Method[] methods = MethodUtils.getMethodsWithAnnotation(bean.getClass(), A.class);
发现这个是由于Spring的bean被代理成CGlib的对象,自然拿不到被代理类的注解,因此最好使用AnnotationUtils.这里面封装了很多逻辑。
// 定义目标对象实现的接口
interface Server {
void doBusiness(String params);
}
// 目标对象,需要被代理的对象
@Component
class TomcatServer implements Server {
public void doBusiness(String params) {
System.out.println("call set name ,value : " + params);
}
}
我们定义切点拦截器
@Component
@Aspect
class MyInterceptor {
// 通知方法设置
@Pointcut("execution(* micro.test.spring.TomcatServer.*(..))")
public void pointCut(){}
// 在切点方法调用之前
@Before("pointCut() && args(name)")
public void doBeforeMethod(String name) {
System.out.println("args name is " + name);
System.out.println("before aspect do something");
}
// 方法之后
@After("pointCut() && args(name)")
public void doAfterMethod(String name) {
System.out.println("args name is " + name);
System.out.println("after aspect do something");
}
@AfterReturning("pointCut() && args(name)")
public void doAfterReturn(String name) {
System.out.println("params is " + name);
System.out.println("do something after return");
}
@AfterThrowing("pointCut() && args(name)")
public void doAfterThrow(String name) {
System.out.println("params is " + name);
System.out.println("do something after throw ...");
}
@Around("pointCut()")
public void doSomethingAroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("do something around method execute");
System.out.println("do something before proxy method call");
proceedingJoinPoint.proceed();
System.out.println("do something after proxy method call");
}
}
最开始我测试一直报异常error at ::0 formal unbound in pointcut.后来才发现是注解参数没写对,通知函数如果有参数必须在注解中添加 args内容,否则会织入抛出异常.
####好了,我们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" 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-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!-- 开启aspect代理 -->
<aop:aspectj-autoproxy />
<!-- 开启注解 -->
<context:annotation-config />
<!-- 扫描包 -->
<context:component-scan base-package="micro.test.spring" />
</beans>
####测试
// 加载spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("*.xml");
Server tomcatServer = (Server)context.getBean("tomcatServer");
tomcatServer.doBusiness("test params");
System.out.println(tomcatServer.getClass());
测试结果:
do something around method execute
do something before proxy method call
args name is test params
before aspect do something
call set name ,value : test params
do something after proxy method call
args name is test params
after aspect do something
params is test params
do something after return
class micro.test.spring.$Proxy12
最后一行打印出来的代理表明获取的是jdk生成的代理对象.
###如果目标对象没有实现接口,改写如下:
@Component
class TomcatServer {
public void doBusiness(String params) {
System.out.println("call set name ,value : " + params);
}
}
测试代码:
// 加载spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("*.xml");
TomcatServer tomcatServer = (TomcatServer) context.getBean("tomcatServer");
tomcatServer.doBusiness("test params");
System.out.println(tomcatServer.getClass());
打印出来的结果:
do something around method execute
do something before proxy method call
args name is test params
before aspect do something
call set name ,value : test params
do something after proxy method call
args name is test params
after aspect do something
params is test params
do something after return
class micro.test.spring.TomcatServer$$EnhancerBySpringCGLIB$$c6cb7663
最后一行可以看到spring采用了cglib的方式生成代理对象.
最后补充下maven配置:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.1.7.RELEASE</spring.version>
<slf4j.version>1.7.2</slf4j.version>
<log4j.version>1.2.16</log4j.version>
<junit.version>4.11</junit.version>
<jsonlib.version>2.4</jsonlib.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20160810</version>
</dependency>