本博文也是属于学习总结性博文,主要来学习Spring+Hibernate的声明式事务。为此,我们从分三步进行:
- Spring + Hibernate : Aop相关概念与动态代理;
- Spring + Hibernate : 声明式事务;
- AOP 概念
Aspect Oriented Programming 面向横切面的编程:
横切性关注点: 是一种遍布在系统各个角落的、与我们的业务逻辑相结合的不是太紧密的、独立的服务,例如安全性检查、事务等。
而我们的AOP主要是处理这些横切面关注点;
- Cross Cutting concern :
- 横切性关注点,是一种遍布在系统流程之中的独立服务;
- Aspect :
- 对这些横切性关注点的模块化,也就是将这些横切性关注点封装成一个类;
- Advice :
- 对这些横切性关注点的具体实现,及其使用方式,在业务流程方法调用之前、之后和抛出异常等使用方式。
- Jointpoint :
- 连接点,对Spring来说就是方法调用,也可以理解成Advice执行的时机。Jointpoint 分为方法调用和属性修改,对Spring来说它只支持方法调用;
Pointcut :
- 对Advice应用范围进行限定,定义了Pointcut应用到那些Jointpoint上;
Weave :
- 将Advice应用到目标对象上的过程,成为织入;
TargetObject :
- 目标对象,Advice 应用的对象;
Proxy :
- Spring AOP 默认使用的代理时JDK 的动态代理,我们也可以使用CGLIB代理,两者的区别: JDK动态代理只能对实现了接口的累进行代理,而CGLIB可以对所有的类进行代理,因为它使用了继承的方式来实现代理;
Introduction :
- 引入,动态地为类添加方法;
- 通过如下简单示例对静态代理,动态代理和AOP进行比较说明,
项目结构:
2.1 静态代理,com.staticProxy.manager
interface UserManager:
package com.staticProxy.manager;
//只有两个简单的方法 添加用户和查找用户;
public interface UserManager {
public void addUser(String name,String password);
public Object findUser(String id);
}
Class UserManagerImpl:
package com.staticProxy.manager;
public class UserManagerImpl implements UserManager {
@Override
public void addUser(String name, String password) {
System.out.println("UserManagerImpl.add()"+ name+" "+password);
}
@Override
public Object findUser(String id) {
System.out.println("UserManagerImpl.findUser()"+id);
return "User1";
}
}
假如现在的业务发生改变,我们在添加用户和查找用户时,都要进行安全性检查,如何解决这个问题;奔着对扩展开放,对修改关闭的原则,我们怎么修改,引入静态代理模式:
Class UserManagerImplProxy
package com.staticProxy.manager;
public class UserManagerImplProxy implements UserManager {
//使用静态代理的关键是拿到代理目标的引用,
//在此我们通过构造函数的方式拿到;也可以setter getter;
private UserManager userManager;
//通过构造函数拿到被代理对象的引用;
public UserManagerImplProxy(UserManager userManager){
this.userManager = userManager;
}
@Override
public void addUser(String name, String password) {
//增加安全性检查操作;
checkSecurity();
userManager.addUser(name, password);
}
@Override
public Object findUser(String id) {
//增加安全性检查操作
checkSecurity();
userManager.findUser(id);
return null;
}
//将安全性检查的操作封装成一个方法;
private void checkSecurity(){
System.out.println("---checkSecurity()---");
}
}
测试静态代理方法
public class ProxyTest extends TestCase {
public void testStaticProxy(){
//将被代理对象的引用传入;
UserManager userManager = new UserManagerImplProxy(new UserManagerImpl());
userManager.addUser("zhangsan", "123");
userManager.findUser("abc");
}
}
分析:
1. 在代理对象中,我们得为每个方法手动的添加安全性检查代码 checkSecurity()
,如果代理类中的方法很多,这就会很麻烦;
2. 如果UserManager有多个实现呢,我们需要为每个实现创建一个代理对象,并在每个方法上加安全性检查操作
3. 在调用时,
UserManager manager = new UserManagerImpl();
变成了:
UserManager manager = new UserManagerImplProxy(new UserManagerImpl());
引入了工厂字眼,代码复杂了;
如何为实现了某些接口的类动态的创建一个代理,并且在代用这些实现方法时,自动的加上安全性检查代码呢?
2.2 动态代理-JDK的动态代理 com.dynamicProxy.manager
interface UserManager 和 UserManagerImpl和以前一样
package com.dynamicProxy.manager;
public interface UserManager {
public void addUser(String name,String password);
public Object findUser(String id);
}
package com.dynamicProxy.manager;
public class UserManagerImpl implements UserManager {
@Override
public void addUser(String name, String password) {
System.out.println("UserManagerImpl.add()"+ name+" "+password);
}
@Override
public Object findUser(String id) {
System.out.println("UserManagerImpl.findUser()"+id);
return "User2";
}
}
动态代理类:SecurityHandler
package com.dynamicProxy.manager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//JDK动态代理必须实现InvocationHandler接口重写invoke方法
public class SecurityHandler implements InvocationHandler {
//step 1: 拿到要被代理的目标对象 targetObject
private Object targetObject;
//setp 2: 生成代理对象;
// 在生成代理对象的同时,我们将被代理的目标对象转入,当然有多种传入方式;
//三个参数:ClassLoader loder: 类加载器,使用目标对象的类加载器;
//Class<?>[] interfs:目标对象实现的接口,如果这个目标有五个接口,会为每个接口创建一个代理;
//InvocationHander h: 上两步已经将代理对象生产,那么我们在调用代理的方法时,代理对象会自动调用InvocationHander的invoke方法;
public Object createDynamicProxy(Object targetObject){
this.targetObject = targetObject;
//使用Proxy生成代理;
Object dynamicProxy = Proxy.newProxyInstance(
targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(),
this
);
return dynamicProxy;
}
//调用代理的每个方法都会自动转到InvocationHandler的invoke方法,因为在
//代理对象中有InvocationHandler的引用this
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//安全性检查代码,会动态为每个方法加上;
checkSecurity();
//实际调用目标对象的方法;
//obj: 要调用的目标对象
// args: 目标对象的参数,这个由代理自动传过来;
Object result = method.invoke(targetObject, args);
return result;
}
private void checkSecurity(){
System.out.println("-----------checkSecurity()-danymicProxy------------");
}
}
测试类:
import com.dynamicProxy.manager.SecurityHandler;
import com.staticProxy.manager.UserManager;
import com.staticProxy.manager.UserManagerImpl;
import com.staticProxy.manager.UserManagerImplProxy;
import junit.framework.TestCase;
public class ProxyTest extends TestCase {
public void testDynamicProxy(){
UserManager userManager = (UserManager) new SecurityHandler().createDynamicProxy(UserManagerImpl());
userManager.addUser("LiSi", "12306");
userManager.findUser("001");
}
}
Spring AOP对此进行了更好的封装,
2.3 AOP声明式服务:AspectJ 的annotation注解方式,
interface UserManager 和 UserManagerImpl和以前一样
class SecurityAspectAnno
package com.aspect.manager;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
// Step 1: 将横切性关注点封装成一个类;
//声明这是一个Aspcet
@Aspect
public class SecurityAspectAnno {
//Step 2: 定义Advice应用的范围:Pointcut
//定义一个Pointcut,范围用execution(正则表达),和名称addUserManager()
@Pointcut("execution(* com.aspect.manager.UserManager.*(..))")
private void addUserManager(){};
// Step 3: 定义Advice;
//定义advice,并关联到相应的Pointcut订阅的Jointpoint上;
@Before("addUserManager()")
private void checkSecurity(){
System.out.println("-----------checkSecurity()-aspectJ------------");
}
}
最后告诉Spring, 我们用了aspectj的annotation
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans">
<!-- 开启AspectJ的annotation -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<bean id="userManagerImpl1" class="com.aspect.manager.UserManagerImpl"/>
<bean id="securityAspectAnno" class="com.aspect.manager.SecurityAspectAnno"/>
</beans>
2.3 AOP声明式服务:XML配置文件,
class SecurityAspectCfg
package com.aspect.manager;
//简简单单的不需要配置任何注释东西了;
public class SecurityAspectCfg {
private void checkSecurityCfg(){
System.out.println("---checkSecurity()-Cfg---");
}
}
我们在配置文件中声明Aspect相关内容;
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<bean id="userManagerImpl2" class="com.aspect.manager.UserManagerImpl"/>
<bean id="securityAspectCfg" class="com.aspect.manager.SecurityAspectCfg"/>
<aop:config>
<aop:aspect id="checkAspect" ref="securityAspectCfg">
<aop:pointcut expression="execution(* com.aspect.manager.UserManager.*(..))" id="addAddMethod"/>
<aop:before method="checkSecurityCfg" pointcut-ref="addAddMethod"/>
</aop:aspect>
</aop:config>
</beans>
测试类:
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.aspect.manager.UserManager;
import junit.framework.TestCase;
public class AspectJTest extends TestCase{
private BeanFactory factory;
protected void setUp() throws Exception {
factory = new ClassPathXmlApplicationContext("applicationContext-aspectj.xml");
}
protected void tearDown() throws Exception {
}
public void testAspectJAnno(){
UserManager userManager = (UserManager) factory.getBean("userManagerImpl2");
userManager.addUser("zz","23");
userManager.findUser("121");
}
}
That’s all, thank you for your consideration. And, welcome any commonet.