1.代理模式
代理模式的适用场景在于,有时候我们想对一些实体类的方法进行增强,但是又不方便修改实体类内部对应的方法定义,此时我们可以通过模式,将增强的部分写在代理中。
1)静态代理
假设我们想要卖手机,于是我们可以如下编写实体类:
Person.java
public class Person {
public void sellPhone(){
System.out.println("卖手机");
}
}
测试:
public class TestStaticProxy {
public static void main(String[] args) {
Person person = new Person();
person.sellPhone();
}
}
现在我们发现在卖手机之前我们需要寻找下家,但是我们已经定义好了卖手机的方法,这时候我们可以通过静态代理的方式进行增强:
1)使用接口定义规范
public interface ISellPhone {
public void sellPhone();
}
2)定义方法
public class Person implements ISellPhone {
public void sellPhone(){
System.out.println("卖手机");
}
}
3)通过代理对方法进行增强
public class SellPhoneProxy implements ISellPhone {
private Person person = new Person();
public void sellPhone() {
System.out.println("寻找买家");
person.sellPhone();
}
}
4)我们直接使用代理就好了
public class TestStaticProxy {
public static void main(String[] args) {
ISellPhone iSellPhone = new SellPhoneProxy();
iSellPhone.sellPhone();
}
}
2)JDK动态代理
静态代理的缺陷在于,几乎对于每一个业务,都得通过增加一个代理类的方式来完成,即便他们的增强方式几乎一致;我们还可以使用动态代理的方式,直接增强方法而不需要编写很多的代理类。
我们增加一个手机售后的业务:
public interface ISellPhone {
public void sellPhone();
public void serviceAfterSelling();
}
Person.java
public class Person implements ISellPhone {
public void sellPhone(){
System.out.println("卖手机");
}
public void serviceAfterSelling() {
System.out.println("售后服务");
}
}
使用JDK的动态代理接口:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class SellPhoneProxy implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用前");
Object result = method.invoke(new Person(),args);
System.out.println("调用后");
return result;
}
}
看到包名我们也应该知道,动态代理是通过反射机制实现的。Object属性用于存储被代理的对象。
测试:
import java.lang.reflect.Proxy;
public class TestDynamicProxy {
public static void main(String[] args) {
Person person = new Person();
ISellPhone iSellPhone = (ISellPhone) Proxy.newProxyInstance(ISellPhone.class.getClassLoader(),new Class[]{ISellPhone.class},new SellPhoneProxy());
iSellPhone.serviceAfterSelling();
}
}
[注意]:不论是静态代理,还是动态代理,代理类和被代理类需要有共同的接口。
3)CGLib动态代理
当然,还可以使用继承的方法,让代理类继承被代理类,通过覆写方法的方式增强对应的方法进行代理(cglib)。
A. 导入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
B. 使用cglib动态代理
import java.lang.reflect.Method;
public class Client {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Person.class);
enhancer.setCallback(new InvocationHandler() {
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
System.out.println("执行前");
Object invoke = method.invoke(new Person(),objects);
System.out.println("执行后");
return invoke;
}
});
Person personAgent = (Person) enhancer.create();
personAgent.sellPhone();
System.out.println("============");
personAgent.serviceAfterSelling();
}
}
3.执行结果
2.AOP 面向切面编程
AOP(Aspect Oriented Programming)面向切面编程,在程序开发的过程中主要用来解决一些系统层面上的问题,比如日志、事务、权限等
Spring的AOP实际上也是对方法的代理增强,经过上面的分析,我们已经知道,基于接口的代理,使用的是JDK;没有接口的,则通过cglib使用继承的方式进行代理。
我们先来看一下OOP(Object Oriented Programming)面向对象编程,把一切东西都看成对象,Java就是一门OOP语言,类的扩展用继承的方式来实现,用Java构建的系统由于继承,呈现出一种纵向的关系。
然而继承也有缺点,继承虽然可以完成对父类方法的增强,但是每一个类的增强都需要一次继承,这会导致整个系统的类过多,不便于维护,尤其是类似于日志这种对于所有的方法,增强方式几乎一致的时候,如果通过继承来实现,工作量是在过于庞大。
在Sring中,定义了一种”横切“技术,通过一个切面横向插进方法内部进行增强,而不需要继承,整个系统呈现出一种横向关系,其底层还是通过代理实现的,具有切面的效果如下:
A.与AOP相关的术语名词:
1. 通知、增强处理(Advice)
写好的用于实现功能增强的代码或逻辑,就是上述所说的安全、事务、日志等。
2. 连接点(JoinPoint)
Spring允许插入通知的地方,包括每个方法的前、后、环绕(前后都有)、抛出异常时,Spring只支持对方法的连接点。
3. 切入点(PointCut)
就是我们实际上定义要使用的那些连接点,也就是真正将通知切入的那些连接点。
4. 切面(Aspect)
切面就是通知和切入点的结合。通知负责说明要做什么、在什么时候做,切入点说明了在什么地方做,共同构成了切面。
5. 引入(Introduction)
引入允许我们把切面引入目标类。
6. 目标(Target)
引入中所提到的目标类,也就是要被通知的对象,具有真正的业务逻辑,它无需关注被切入了什么,只需要关注自己的业务逻辑。
7. 织入(Weaving)
把切面应用到目标对象(把通知切入待到切点上),依此来构建新的代理对象的过程。
B.感受AOP的使用(注解):
1. 加入头文件
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
2. 导入织入的相关依赖
<!--引入织入相关的依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
3.增加两个待增强的方法
AdminService.java
package com.zt.Service;
import org.springframework.stereotype.Service;
@Service
public class AdminService {
public Integer getAdmin(){
System.out.println("-----getAdmin-----");
return 100;
}
}
UserService.java
package com.zt.Service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void getUser(){
System.out.println("-----getUser-----");
}
}
4. 编写切面类
package com.zt.Aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LogAdvice {
@Before("execution(* com.zt.Service.*.*(..))")
public void before(){
System.out.println("-----方法执行前 before -----");
}
@After("execution(* com.zt.Service.*.*(..))")
public void after(){
System.out.println("-----方法执行后 after -----");
}
@AfterReturning("execution(* com.zt.Service.*.*(..))")
public void afterReturning(){
System.out.println("-----方法执行返回后 afterReturning -----");
}
@Around("execution(* com.zt.Service.*.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("-----环绕前-----");
System.out.println("方法名:" + joinPoint.getSignature());
Object proceed = joinPoint.proceed();
System.out.println("-----环绕后-----");
System.out.println(proceed);
}
@AfterThrowing("execution(* com.zt.Service.*.*(..))")
public void afterThrow(){
System.out.println("-----有异常-----");
}
}
[注]:execution表达式,用于指明对哪些方法进行增强。
当然,我们也可以具体指定对哪个方法进行增强:execution(void com.zt.Service.UserService.getUser(..))
5. 由于我们使用切面类的方式,因此需要开启扫包,让Spring容器管理这个用于配置的Bean
<context:component-scan base-package="com.zt"/>
6. 开启AOP自动代理的注解支持
<aop:aspectj-autoproxy/>
7. 使用配置类、并进行测试
package com.zt.Config;
import com.zt.Service.AdminService;
import com.zt.Service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application.xml")
public class JavaConfig {
@Autowired
private UserService userService;
@Autowired
private AdminService adminService;
@Test
public void TestAop(){
userService.getUser();
System.out.println("-------------------------------------------");
adminService.getAdmin();
}
}
[注]:Spring只能对容器内的Bean的方法进行增强,在容器外实例化的对象并不会被增强,
C.AOP使用(配置文件方式)
1. 定义切面类
package com.zt.Aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
@Component
public class XMLAdvice {
public void before(){
System.out.println("-----方法执行前 before -----");
}
public void after(){
System.out.println("-----方法执行后 after -----");
}
public void afterReturning(){
System.out.println("-----方法执行返回后 afterReturning -----");
}
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("-----环绕前-----");
System.out.println("方法名:" + joinPoint.getSignature());
Object proceed = joinPoint.proceed();
System.out.println("-----环绕后-----");
System.out.println("方法执行结果的返回值:" + proceed);
}
public void afterThrow(){
System.out.println("-----有异常-----");
}
}
2. 在配置文件中,使用切入点,引用切面的方法进行织入
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.zt.Service.*.*(..))"/>
<aop:aspect ref="XMLAdvice">
<aop:before pointcut-ref="pointcut" method="before"/>
<aop:after pointcut-ref="pointcut" method="after"/>
</aop:aspect>
</aop:config>
<aop:pointcut>:用来定义切入点。
<aop:aspect>:用于引入切面类。
<aop:before>:定义执行方式为执行前,引入切点既可以用ref引用(pointcut-ref)已经存在的切入点,也可以直接用execution表达式定义切入点(pointcut)。method用来指定通知来自切面中的哪个方法,其他的执行方式类似。
<aop:aspect ref="XMLAdvice">
<aop:before pointcut="execution(* com.zt.Service.*.*(..))" method="before"/>
<aop:after pointcut="execution(* com.zt.Service.*.*(..))" method="after"/>
</aop:aspect>
D. AOP使用(直接使用通知,通过实现某些特定的接口)
LogBefore.java
package com.zt.Aop;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
public class LogBefore implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("-----before------" + method.getName() + "-----");
}
}
LogAfter.java
package com.zt.Aop;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
public class LogAfter implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("-----after-----" + method.getName() + "------");
}
}
在配置文件中引用:
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.zt.Service.*.*(..))"/>
<aop:advisor advice-ref="logBefore" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="logAfter" pointcut-ref="pointcut"/>
</aop:config>
[注]:使用@Component注解可以让Spring自动实例化并管理这个Bean,且默认命名为首字母小写,其它保持不动(驼峰命名法)。当然,为了可读性我们也可以指定注入后的bean id:
LogBefore.java
package com.zt.Aop;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component("Before")
public class LogBefore implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("-----before------" + method.getName() + "-----");
}
}
LogAfter.java
package com.zt.Aop;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component("After")
public class LogAfter implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("-----after-----" + method.getName() + "------");
}
}
在配置文件中引用:
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.zt.Service.*.*(..))"/>
<aop:advisor advice-ref="Before" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="After" pointcut-ref="pointcut"/>
</aop:config>