SpringAOP
1、代理模式
代理模式:就是给被代理对象提供一个代理对象,代理对象能够进行进行一些常用的业务操作,并应用被代理对象中的方法,从而保证被代理对象内容的简洁和实用。代理模式一般分为静态代理和动态代理两种。
1.1、静态代理
以下是以事务管理为例对静态代理解决的问题进行详细的阐述。
-
问题1:事务是开在dao上面的,所以如果在service中调用多个dao的方法,如果在这些方法执行过程中报错,事务没法回滚?
解决办法:对于我们的应用,事务都应该开在service的方法上面。那么具体的操作就要注意以下两点。
1,sessionFactory.getCurrentSession:得到当前线程中的session;
2,把session的开启和事务的开启,事务的提交从dao中移到service方法中;
-
问题2:存在一个事务跨层的问题,但是如果直接把事务管理的代码写在service中,会造成service代码污染
解决办法:最好的方式就是把事务的代码从service中抽取出来,使用代理模式,再加到目标代码中,代码如下:
DAOImpl的代码:
public class EmployeeDAOImpl implements IEmployeeDAO{ @Override public void save() { System.out.println("Session.save");//DAOImpl的实现 } }
ServiceImpl的代码:
public class EmployeeServiceImpl implements IEmployeeService { @Autowired IEmployeeDAO employeedao ; @Override public void save() { employeedao.save(); //调用DAO存的方法 int i = 1/0;//业务逻辑出错,判断是否回滚 } }
TranscationServiceImpl代理类的代码
public class EmployeeTranscationServiceImpl implements IEmployeeService{ private IEmployeeService target;//目标对象 public EmployeeTranscationServiceImpl(IEmployeeService service) {//用构造方法传入被代理对象 this.target = service; } @Override public void save() { try { System.out.println("sessionfactory.getCurrentSession");//拿到DAO层的session System.out.println("session.getTranscation().begin()");//开启事务 target.save(); System.out.println("session.getTranscation().commit()");//提交事务 }catch (Exception e) { System.out.println("session.getTranscation().rollback()");//事务回滚 } } }
配置文件:
<bean id="employeedao" class="com.it520.dynProxy.EmployeeDAOImpl"></bean> <bean id="employeeservice" class="com.it520.dynProxy.EmployeeServiceImpl" ></bean> <bean id="employeeservicetranscation" class="com.it520.dynProxy.EmployeeTranscationServiceImpl" > <!-- 构造函数将被代理对象注入 --> <constructor-arg ref="employeeservice"></constructor-arg> </bean>
运行结果:
sessionfactory.getCurrentSession session.getTranscation().begin()//代理对象的事务开启 Session.save//调DAO层的实现 session.getTranscation().rollback()//Service层出错,事务回滚
1.2、动态代理
1.2.1,有接口的对象的动态代理
JDK中提供了针对有接口对象的动态代理实现方式;
(1)客户端调用save方法,执行被InvocationHanalder拦截;
(2)在invoke方法中执行添加的额外的逻辑;
(3)调用m.invoke(target,args)执行到了真实的目标对象上的对应方法;
(4)真实对象返回执行结果到InvocationHandler里面;
(5)执行完成其他逻辑,把结果返回给客户端;
实现流程
(1)使用JDK的动态代理,必须要保证代理的真实对象是有接口的;接口实现类如下:
public class EmployeeServiceImpl implements IEmployeeService {
@Override
public void save(Employee e) {
System.out.println("dao.seve(employee)");
int i = 1/0;
}
}
(2)需要实现一个InvocationHandler,并实现invoke方法;
a、需要给InvocationHanlder的实现类提供真实对象;
b、在invoke方法中执行自己的额外添加的业务逻辑;案例的其他业务逻辑时事务TranscationManager类中的方法;
c、在编写完额外逻辑之后,调用method.invoke(target,args)执行真实对象上的对应方法;
d、得到真实方法执行的结果;
e、处理结果,并把结果返回给客户端;
TranscationManager类代码:
public class TranscationManager {
public void begin() {
System.out.println("session.getTranscation().begin()");
}
public void commit() {
System.out.println("session.getTranscation().commit()");
}
public void rollback() {
System.out.println("session.getTranscation().rollback()");
}
}
代理类的代码:
public class TranscationInvocationHandler implements InvocationHandler {
private TranscationManager txManager;//额外业务逻辑的类
private Object target;//给InvocationHanlder的实现类提供真实对象;
public TranscationInvocationHandler(TranscationManager txManager,Object target) {
this.target=target;
this.txManager=txManager;
}
@Override
//proxy:代理出来的对象;method:这次调用的方法;args:调用方法传入的参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
txManager.begin();//额外添加的业务逻辑
try {
Object ret = method.invoke(target, args);//执行真实对象上的对应方法;得到真实方法执行的结果
txManager.commit();
return ret;//处理结果,并把结果返回给客户端
}catch(Exception e) {
txManager.rollback();
}
return null;
}
}
(3)在客户端调用Proxy.newProxyInstance方法:
a、需要传入目标对象的classloader;
b、需要传入目标对象的接口类型;
c、需要传入一个自定义的InvocationHandler的实例;
@RunWith(value = SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class DynProxyTest {
@Autowired
private IEmployeeService service;
@Autowired
private TranscationManager txManager;
@Test
public void test() {
/* a、service.getClass().getClassLoader()需要传入目标对象的classloader;
b、new Class[] {IEmployeeService.class}需要传入目标对象的接口类型;
c、new TranscationInvocationHandler(txManager, service))需要传入一个自定义的InvocationHandler的实例*/;
IEmployeeService o = (IEmployeeService)Proxy.newProxyInstance(service.getClass().getClassLoader(),
new Class[] {IEmployeeService.class}, new TranscationInvocationHandler(txManager, service));
Employee e = new Employee();
o.save(e);
}
}
执行结果:
session.getTranscation().begin()
dao.seve(employee)
session.getTranscation().rollback()
1.2.2,没有接口的对象的动态代理
使用继承的方式完成动态代理;javassist/cglib
使用CGLIB完成没有接口的对象的动态代理如下:
被代理对象的代码:
public class Somebean {
public void print(int i) {
System.out.println("somebean");
}
}
代理对象的代码:
public class LogInvocationHandler implements InvocationHandler {
private Object target;
public LogInvocationHandler(Object target) {
this.target=target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("操作时间"+new Date()+"当前调用了"+method+"方法传入的参数是"+Arrays.toString(args));
return method.invoke(target, args);
}
}
客户端的测试类:
public class CglibTest {
@Test
public void test() {
Somebean target = new Somebean();
Enhancer e = new Enhancer();
e.setClassLoader(target.getClass().getClassLoader());//传入目标对象的classloader
e.setSuperclass(Somebean.class);//传入目标对象类型
e.setCallback(new LogInvocationHandler(target));//传入一个自定义的InvocationHandler的实例
Somebean o = (Somebean)e.create();
o.print();
}
}
结果:
操作时间Sun Jul 05 22:21:09 CST 2020当前调用了public void spring.cglib.Somebean.print(int)方法传入的参数是[2]
somebean
2、SpringAOP
AOP就是一种更高级的动态代理的使用;Aspect Oritention Programming(面向切面编程)
切入点:要加入业务逻辑的点(在哪些类的哪些方法上面;)
通知:通知包含两个方面,1,代表方法的执行时间,2,在这个时间上面要做什么事情;
切面:一个切入点+一个通知=一个切面(在什么地方,在什么时候,做什么事情);
织入:把一个切面应用到真实对象上面的过程,就叫做织入;
2.1、使用Spring的AOP:
在Java中,没有语言能够准确的描述切入点;所以,有一个AspectJ,这是一种语言,提供了用于描述切入点的语言;
定义切入点的表达式
如果可以是任意的值,使用*就可以了;带?的表示可省略的的部分
例如:
aop包中,所有的service的所有的方法;
execution(* com._520it.spring.aop.Service.(…));
day2中所有service的save方法;
execution(* com._520it.spring…*Service.save(…))
2.1.1、使用XML的配置方式
配置文件如下
<bean id="service" class="spring.aop.EmployeeServiceImpl"></bean>
<bean id="txManager" class="spring.aop.TranscationManager"></bean>
<!-- 配置AOP -->
<aop:config>
<!-- 配置一个切入点
expression:这个切入点的表达式
id:这个切入点的名字 -->
<aop:pointcut expression="execution(* spring.aop.*Service.*(..))" id="pc"/>
<!-- 配置一个切面
代表这个切面定义中,所有做的事情都有txManager这个对象的方法提供 -->
<aop:aspect ref="txManager">
<aop:before method="begin" pointcut-ref="pc"/>
<aop:after-returning method="commit" pointcut-ref="pc"/>
<aop:after-throwing method="rollback" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>
TranscationManager代码:
public class TranscationManager {
public void begin() {
System.out.println("session.getTranscation().begin()");
}
public void after() {
System.out.println("session.getTranscation().after()");
}
public void commit() {
System.out.println("session.getTranscation().commit()");
}
public void rollback() {
System.out.println("session.getTranscation().rollback()");
}
}
测试代码:
@RunWith(value = SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class AOPTest {
@Autowired
private IEmployeeService service;
@Autowired
private IDepartmentService dservice;
@Autowired
private TranscationManager txManager;
@Test
public void test() {
service.save(new Employee());//这个方法无错误
}
@Test
public void test1() {
dservice.save(new Department());//这个方法有错误
}
运行结果:
session.getTranscation().begin()
dao.seve(employee)
session.getTranscation().commit()
session.getTranscation().begin()
session.getTranscation().rollback()
2.1.2、使用注解的方式
(1)配置文件中开启切点的自动注入和注解扫描
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<context:component-scan base-package="spring.annaop"></context:component-scan>
(2)在代理对象上添加注解
@Aspect//这个注解相对<aop:aspect ref="txManager">
@Component
public class TranscationManager {
@Pointcut("execution(* spring.annaop.Somebean.*(..))")//定义一个方法,用@Pointcut注解来配置切点
public void pc() {}
@Before(value = "pc()")//通过注解@Before相当于xml配置中的aop:before,value参数引入切点
public void begin() {
System.out.println("session.getTranscation().begin()");
}
@After("pc()")//通过注解@After相当于xml配置中的aop:afte,value参数引入切点
public void after() {
System.out.println("session.getTranscation().after()");
}
@AfterReturning("pc()")//通过注解@AfterReturning相当于xml配置中的aop:after-returning,value参数引入切点
public void commit() {
System.out.println("session.getTranscation().commit()");
}
@AfterThrowing("pc()")//通过注解@AfterThrowing相当于xml配置中的aop:after-throwing,value参数引入切点
public void rollback(Throwable ex) {
System.out.println("session.getTranscation().rollback()"+ex.getMessage());
}
(3)SomeBean类和测试类代码:
@Component
public class Somebean {
public void print() {
System.out.println("somebean");
}
}
@RunWith(value = SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class AOPTest {
@Autowired
private Somebean bean;
@Test
public void test() {
bean.print();
}
}
2.1.3、Spring中AOP的通知类型
aop:before:前置通知;
aop:after-returning:后置通知;
aop:after:最终通知;
aop:after-throwing:异常通知;
这四个通知的执行方式可参考以下代码,前面例子也提到过,不做赘述。
try {
System.out.println("before");
Object ret = pjp.proceed();
System.out.println("after-returning");
return ret;
} catch (Throwable ex) {
System.out.println("after-throwing");
}finally {
System.out.println("after");
}
下面具体讲一下一个整合了以上四个通知的通知:
aop:around:环绕通知;
Xml配置中的实现方式如下:
<aop:around method="around" arg-names="pjp" pointcut-ref="pc"/><!-- arg-names:around方法的参数名字 -->
代理类中关于around方法的代码如下:
public Object around(ProceedingJoinPoint pjp) {
try {
System.out.println("session.getTranscation().begin()");
Object ret = pjp.proceed();
System.out.println("session.getTranscation().commit()");
return ret;
} catch (Throwable ex) {
System.out.println("session.getTranscation().rollback()");
}finally {
System.out.println("session.getTranscation().after()");
}
return null;
}
使用注解的方式就是在around方法上添加@Around注解,value值是切点;
@Around(value = "pc()")
public Object around(ProceedingJoinPoint pjp) {}
2.2、SpringAOP的执行流程
1,解析xml;
2,实例化所有的bean;
3,解析aop:config;
(1)解析aop:aspect,得到aspect引用的对象;txManager
(2)解析aop:aspect里面的每一个切面;
a、得到该aspect对应的pointcut-ref;
b、得到pointcut-ref对应的pointcut的表达式;
(3)使用表达式中用于匹配类型的表达式;
(4)使用该表达式去和spring里面配置的所有的bean的类型进行匹配;
a、如果匹配不上,不管;
b、如果匹配上了,该对象作为spring动态代理的目标对象;
1,如果该对象实现了接口,使用JDK的动态代理包装;
2,如果该对象没有实现接口,使用cglib包装;
3,得到配置的拦截时机+逻辑提供类(txManager)的对应方法(从method解析)+pointcut表达式中方法的匹配模式创建一个拦截器
4,在把该拦截器使用对应的动态代理机制代理成代理对象;
的实例;
5,替换spring容器中的对应bean的实例;