AOP(面向切面编程)
Spring-AOP 是对 AOP框架之一。其他比如还有AspectJ
Aspect-Oriented-Programming(面向切面编程),一种编程思想。
切面:Aspect,由
切入点
和额外功能(增强)
组成。作用:提供了新的编程角度,不再只是考虑
类、对象
,而可以考虑切面
。切面和目标形成代理
,解决项目业务中额外功 能冗余的问题。
概念,切面,为什么aop,好处是什么
1. 业务中的问题
业务层中存在问题:两类逻辑=核心业务+额外功能,其中额外功能存在大量的代码冗余,
:使得项目维护存在极大隐患。
class UserServiceImpl implements UserService{
private UserDAO ud;
public void updateUser(User user){
System.out.println("事务管理功能");//额外功能 冗余
ud.update(user); //核心功能
}
public void inserUser(User user){
System.out.println("事务管理功能");//额外功能 冗余
ud.insertUser(user);//核心功能
}
public User queryUser(Integer id){
System.out.println("事务管理功能");//额外功能 冗余
ud.queryUser(id);//核心功能
}
}
2. 静态代理
代目标类打理了额外功能
目标类:UserServiceImpl ,即,被代理的类
代理类原则:要和原始的业务(target)实现同样的接口,保持功能一致。
代理类组成:额外功能(Advice) +目标(Target)解决了目标类的冗余问题,但自身却依然有冗余!!
class UserServiceProxy implements UserService{//代理类
UserService us=new UserServiceImpl();
public void updateUser(User user){
System.out.println("事务管理功能"); //代理类负责额外功能
us.updateUser(user); // 目标自己负责核心功能
}
public void inserUser(User user){
System.out.println("事务管理功能");//代理类负责额外功能
us.insertUser(user);// 目标自己负责核心功能
}
public User queryUser(Integer id){
System.out.println("事务管理功能");//代理类负责额外功能
us.queryUser(user);// 目标自己负责核心功能
}
}
// 创建代理对象,完成业务
UserService userService = new UserServiceProxy();
userService.insertUser(user);
3. 动态代理
通过动态字节码技术,在运行时动态生成代理( 反射 )。
则既不用维护代理类,有可以有代码打理额外功能。
动态代理的实现方案:
- jdk代理 ( jdk在反射包中提供的一套api ) 通过和目标实现相同的接口保证
- 功能一致
- cglib代理 ( 第三方cglib库中的一套api ) 通过继承目标保证功能一致
Spring的AOP章节,底层采纳了如上两种代理实现,并对动态代理提供了,简单的,可操作性强的决绝方案。
当项目中需要使用代理解决问题时,可以采用AOP章节的内容加以解决。
org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy()中spring做了jdk代理和cglib代理的选择。
4. AOP 编码流程
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<!-- spring-aspects 会传递导入
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
<scope>compile</scope>
</dependency>
而aspectweaver 即 AspectJ 的一个库,(可以github搜索“AspectJ”,查看在Github上的镜像)。
spring-AOP模块,并没有独立提供完整的AOP实现,而是集成了【AspectJ框架(AOP框架)】
spring-aspects就是负责集成AspectJ。
-->
<!-- AOP联盟,可省略,spring4.3.6中的 aop模块中添加了其中的api( 兼容了AOP联盟 )
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
-->
4.1 准备 Target
public class UserServiceImpl implements UserService{
private UserDAO userDAO;
// set/get...
@Override
public void updateUser(User user) {
System.out.println("update in service===============");
userDAO.updateUser(user);
}
@Override
public void insertUser(User user) {
System.out.println("insert in service===============");
userDAO.insertUser(user);
}
}
4.2 准备 Advice
public class MyBeforeAdvice implements MethodBeforeAdvice{
/**
* @param method 当前执行的方法
* @param args 当前执行的方法中的参数
* @param target 目标对象
* @throws Throwable
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("before~~~");
}
}
4.3 编织 Weave
所谓编织,即,将Target 和 Advice 组装 形成代理。
当然组装过程由spring管理,开发者只需要做出配置,告知spring需要组装谁即可
<!-- 声明 Target + Advice -->
<!-- 声明 Target -->
<bean id="userService" class="com.zhj.service.UserServiceImpl">
<!-- 为userDAO属性赋值,值为id=“userDAO”的组件 -->
<property name="userDAO" ref="userDAO"/>
</bean>
<!-- Advice -->
<bean id="myBefore" class="com.zhj.advice.MyBeforeAdvice"/>
<!-- 编织 配置 -->
<aop:config>
<!-- ref="引入MyAdvice" -->
<aop:aspect ref="myAdvice">
<!-- 切入点=pointcut
execution()表达式:描述切入位置
组成:修饰符 返回值 包 类 方法名 参数表
public Integer com.xx.xxx.AA.xxxXXX(int,String)
* com.service.UserServiceImpl.*(..):com.service包下UserServiceImpl类中,返回值修饰符任意,方法名任意,
参数表任意
* com.service.UserServiceImpl.queryUser(..):同上,只是方法名不是任意,而是 ”queryUser“
-->
<aop:pointcut id="pc" expression="execution(* com.service.UserServiceImpl.queryUser(..))"/>
<aop:advisor advice-ref="myBefore" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>
4.4 测试
UserService userService = (UserService)context.getBean("userService");
userService.insertUser(new User(...));
4.5 多种Advice
4.5.1 前置额外功能
public class MyBeforeAdvice implements MethodBeforeAdvice{
/**
* @param method 当前执行的方法
* @param args 当前执行的方法中的参数
* @param target 目标对象
* @throws Throwable
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("before~~~");
}
}
4.5.2 后置额外功能
public class MyAfterAdvice implements AfterReturningAdvice{
/**
*
* @param returnValue 目标业务方法返回值
* @param method 当前执行的业务方法对象
* @param args 方法的参数
* @param target 目标对象
* @throws Throwable
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("after~~~");
}
}
4.5.3 环绕额外功能
public class MyMethodInterceptor implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("begin~~");
Object ret = invocation.proceed();//执行目标业务方法
System.out.println("end~~");
return ret;//返回目标业务方法返回值
}
}
4.5.4 异常额外功能(了解)
public class MyThrows implements ThrowsAdvice{
//目标业务方法中抛出异常时,执行此方法。ex=抛出的异常对象
public void afterThrowing(Exception ex){
System.out.println(ex.getMessage()+"~~~");
}
}
4.5.5 最终额外功能(了解)
在核心之后执行( 即使核心中出现了异常,依然执行额外 )
4.5.5 编织
<!-- 声明 target+advice -->
<bean id="userService" class="xxxxx"></bean>
<bean id="myXXAdvice" class="xxxxx"></bean>
<aop:config>
<aop:pointcut id="pc" expression="execution(* com..UserService*.*(..))"/>
<aop:advisor advice-ref="advice的BeanId" pointcut-ref="pc"/>
</aop:config>
4.6 切入点表达式
4.6.1 execution
1> * com.service.UserServiceImpl.queryUser(..)
修饰符:任意
返回值:任意
包:com.service
类:UserServiceImpl
方法:queryUser
参数表:任意
2> * com.service.UserServiceImpl.*(..)
修饰符:任意
返回值:任意
包:com.service
类:UserServiceImpl
方法:所有,任意
参数表:任意
3> * com..UserServiceImpl.*(..)
修饰符:任意
返回值:任意
包:com包,及其子包
类:UserServiceImpl
方法:所有,任意
参数表:任意
4> * com.service.*.*(..)
修饰符:任意
返回值:任意
包:com.service
类:所有,任意
方法:所有,任意
参数表:任意
5> * *(..) 不建议
修饰符:任意
返回值:任意
包:任意
类:所有,任意
方法:所有,任意
参数表:任意
6> * com.service.UserServiceImpl.query*(..) 【技巧:批量切入】
修饰符:任意
返回值:任意
包:com.service
类:UserServiceImpl
方法:所有,任意
参数表:任意
*注意:尽量精确,避免不必要的切入
4.6.2 within
描述
包
和类
,类
中所有方法都切入
within(com.service.UserServiceImpl) 类中的所有方法
within(com..UserServiceImpl) com包和com子包下的类中的所有方法
<aop:pointcut id="pc" expression="within(com..UserServiceImpl)"/>
4.6.3 args
描述
参数表
,符合的方法都切入
args(int,String,com.entity.User) 参数表如此的方法
<aop:pointcut id="pc" expression="args(int,String,com.entity.User)"/>
4.6.4 联用
不同种类的表达式之间,可以使用逻辑运算:
and or not
<aop:pointcut id="pc" expression="execution(* com.zhj.service.UserServiceImpl.*(..)) and args(com.User)"/>
<aop:pointcut id="pc" expression="within(com.service.UserServiceImpl) or args(com.User)"/>
<aop:pointcut id="pc" expression="within(com.service.UserServiceImpl) and not args(com.User)"/>
5. JDK代理(与目标实现相同的接口)
// JDK动态代理
//目标
final UserService us = new UserServiceImpl();
//额外功能
InvocationHandler handler = new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("事务开启");
method.invoke(us, args);
System.out.println("事务结束");
return null;
}
};
//动态组建代理类
UserService proxy = (UserService)Proxy.newProxyInstance(Test.class.getClassLoader(),
us.getClass().getInterfaces(),
handler);
6.CGLib代理(与目标继承相同的类)
// CGLIB动态代理
//目标
final UserService us = new UserServiceImpl();
//额外功能
MethodInterceptor mi = new MethodInterceptor(){
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
throws Throwable {
System.out.println("### before invocation");
Object result = method.invoke(us, objects);
System.out.println("### end invocation");
return result;
}
}
Enhancer enhancer = new Enhancer();
// 设置需要代理的对象
enhancer.setSuperclass(us.getClass());
// 设置代理人
enhancer.setCallback(mi);
UserServiceImpl proxy = (UserServiceImpl)enhancer.create();
org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy()中spring做了jdk代理和cglib代理的选择。
- jdk代理 ( jdk在反射包中提供的一套api ) 通过和目标实现相同的接口保证
- 功能一致
- cglib代理 ( 第三方cglib库中的一套api ) 通过继承目标保证功能一致
spring 选着是看目标是否有接口 从而是否选着jdk
如何选着
如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理。所有该目标类型实现的接口都将被代理。若该目标对象没有实现任何接口,则创建一个CGLIB代理。
如果你希望强制使用CGLIB代理,(例如:希望代理目标对象的所有方法,而不只是实现自接口的方法)那也可以。但是需要考虑以下问题:
无法通知(advise)Final 方法,因为他们不能被覆写。
你需要将CGLIB 2二进制发行包放在classpath下面,与之相较JDK本身就提供了动态代理
强制使用CGLIB代理需要将 |aop:config| 的 proxy-target-class 属性设为true:
|aop:config proxy-target-class=“true”|
…
|/aop:config|
当需要使用CGLIB代理和@AspectJ自动代理支持,请按照如下的方式设置 |aop:aspectj-autoproxy| 的 proxy-target-class 属性:
|aop:aspectj-autoproxy proxy-target-class=“true”/|
而实际使用的过程中才会发现细节问题的差别,The devil is in the detail.JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。
CGLIB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的,性能比JDK强。
Spring是依靠什么来判断采用哪种代理策略来生成AOP代理呢?以下代码就是Spring的判断逻辑
//org.springframework.aop.framework.DefaultAopProxyFactory
//参数AdvisedSupport 是Spring AOP配置相关类
public AopProxy createAopProxy(AdvisedSupport advisedSupport)
throws AopConfigException {
//在此判断使用JDK动态代理还是CGLIB代理
if (advisedSupport.isOptimize() || advisedSupport.isProxyTargetClass()
|| hasNoUserSuppliedProxyInterfaces(advisedSupport)) {
if (!cglibAvailable) {
throw new AopConfigException(
"Cannot proxy target class because CGLIB2 is not available. "
+ "Add CGLIB to the class path or specify proxy interfaces.");
}
return CglibProxyFactory.createCglibProxy(advisedSupport);
} else {
return new JdkDynamicAopProxy(advisedSupport);
}
}
advisedSupport.isOptimize()与advisedSupport.isProxyTargetClass()默认返回都是false,所以在默认情况下目标对象有没有实现接口决定着Spring采取的策略,当然可以设置advisedSupport.isOptimize()或者advisedSupport.isProxyTargetClass()返回为true,这样无论目标对象有没有实现接口Spring都会选择使用CGLIB代理。
所以在默认情况下,如果一个目标对象如果实现了接口Spring则会选择JDK动态代理策略动态的创建一个接口实现类(动态代理类)来代理目标对象,可以通俗的理解这个动态代理类是目标对象的另外一个版本,所以这两者之间在强制转换的时候会抛出java.lang.ClassCastException。而所以在默认情况下,如果目标对象没有实现任何接口,Spring会选择CGLIB代理, 其生成的动态代理对象是目标类的子类。
上说的是默认情况下,也可以手动配置一些选项使Spring采用CGLIB代理。
org.springframework.transaction.interceptor.TransactionProxyFactoryBean是org.springframework.aop.framework. ProxyConfig的子类,所以可以参照ProxyConfig里的一些设置如下所示,将optimize和proxyTargetClass任意一个设置为true都可以强制Spring采用CGLIB代理。
如果当需要使用CGLIB代理和@AspectJ自动代理支持,请按照如下的方式设置 |aop:aspectj-autoproxy| 的 proxy-target-class 属性:
|aop:aspectj-autoproxy proxy-target-class=“true”/|
这样使用CGLIB代理也就不会出现前面提到的ClassCastException问题了,也可以在性能上有所提高,关键是对于代理对象是否继承接口可以统一使用。