设计模式——代理模式
学习目标:
掌握代理模式,理解Spring Aop并能运用学习内容:
1、 静态代理
2、JDK动态代理
3、CGLib动态代理
4、Spring Aop 基于xml
5、Spring Aop 基于注解
文章目录
一、代理模式的定义
定义:Provide a surrogate or placeholder for another object to control access to it.(为其他对象提供一种代理以控制对这个对象的访问。)
Subject抽象主题角色:
抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。
RealSubject具体主题角色:
也叫做被委托角色、被代理角色。它才是冤大头,是业务逻辑的具体执行者。
Proxy 代理主题角色:
也叫做委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。
UML 类图
二、静态代理的实现
1.创建SomeService接口
代码如下:
public interface SomeService {
public void doSome();
public void doOther();
}
2.创建Someservice的实现类
代码如下:
public class SomeServiceImpl implements SomeService {
@Override
public void doSome() {
System.out.println("执行业务方法doSome");
}
@Override
public void doOther() {
System.out.println("执行业务方法doOther");
}
}
3.创建事务类
代码如下:
//事务类
public class Transaction {
//开启事务
public static void doLog(){
System.out.println("非业务方法,开启事务,方法执行的时间" +new Date());
}
//提交事务
public static void doTrans(){
System.out.println("非业务方法,方法执行完毕,提交事务");
}
}
4.创建代理类
代码如下:
public class SomeServiceProxy implements SomeService {
//目标对象,真实类
SomeServiceImpl service = new SomeServiceImpl();
@Override
public void doSome() {
//调用事务方法
Transaction.doLog();
service.doSome();
Transaction.doTrans();
}
@Override
public void doOther() {
Transaction.doLog();
service.doOther();
Transaction.doTrans();
}
}
5.测试
代码如下:
public class StaticProxyTest {
@Test
public void test(){
//产生静态代理对象
SomeServiceProxy s1 = new SomeServiceProxy();
s1.doSome();
s1.doOther();
}
}
6.运行截图
三JDK动态代理
与静态类似,我们写的代理类由JDK帮我们写好了我们只需要调就可以了;需要我们注意的是JDK代理类必须要实现一个接口才能代理。
JDK代理类的实现
代码如下:
public class MyHandler implements InvocationHandler {
//目标对象
private Object target;
//SomeSeImpl类,初始化目标对象
public MyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//开启事务
SomeTools.doLog();
//反射对象
Object result = method.invoke(target,args);
//提交事务
SomeTools.doTrans();
return result;
}
}
测试
代码如下:
public class testProxy {
@Test
public void test1(){
//目标对象
SomeService target = new SomeServiceImpl();
//多态性创建拦截器handler对象
InvocationHandler handler = new MyHandler(target);
//三个参数的含义:目标的类加载器、目标实现的所有接口(反射对象)、拦截器handler对象、
SomeService proxy =(SomeService) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),handler);
//proxy不是真实对象,是代理后的对象
System.out.println("proxy =========" +proxy.getClass().getName());
proxy.doSome();
proxy.doOther();
}
}
运行截图
四、CGLib动态代理
GCLib采用底层的字节码技术给目标类,自动生成一个子类!子类重写父类的方法,使用子类对象调用方法的时候,本质调用子类的方法!
导入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
CGLib代理类的实现
//拦截器
public class MyCglibHandler implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] agrs, MethodProxy methodProxy) throws Throwable {
//开启事务
SomeTools.doLog();
Object result = methodProxy.invokeSuper(o,agrs);
//提交事务
SomeTools.doTrans();
return result;
}
}
#测试
代码如下:
public class TestCglib {
@Test
public void test(){
MyCglibHandler handler = new MyCglibHandler();
Enhancer enhancer = new Enhancer();
//根据父类生成子类
enhancer.setSuperclass(SomeServiceImpl.class);
//调用代理对象的方法
enhancer.setCallback(handler);
//生成子类代理类,o是SomeServiceImpl的子类对象,
SomeServiceImpl o =(SomeServiceImpl) enhancer.create();
o.doSome();
o.doOther();
}
}
运行截图
五、代理模式运用Spring AOP
AOP介绍
AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志打印、事务处理。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
1、AOPxml配置:
导入依赖:
<properties>
<spring.vension>5.3.6</spring.vension>
<junit.version>4.13.2</junit.version>
<log4j.version>1.2.14</log4j.version>
<lombok.version>RELEASE</lombok.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.vension}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.vension}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
先创建USerService接口
public interface UserService {
int addUser(User u);
int delete(int id);
}
USerService实现类
public class UserServiceImpl implements UserService {
@Override
public int addUser(User u) {
System.out.println("模拟DAO层的新增方法" + u);
return 1;
}
@Override
public int delete(int id) {
System.out.println("模拟DAO层的删除方法" + id);
return 1;
}
}
通知(增强)类
//通知(增强)类
public class MyAdvice {
public void before(){
System.out.println("前置通知(前置增强)...");
}
public void after(){
System.out.println("后置通知(后置增强)...");
}
}
applicationContext.xml
<!--被代理的目标对象 -->
<bean class="com.spring.service.impl.UserServiceImpl"/>
<!--配置通知对象 -->
<bean id="myAdvice" class="com.spring.advice.MyAdvice"/>
<!-- 配置将增强织入目标对象 -->
<aop:config>
<!--切入点:哪个类的哪个方法需要被代理-->
<aop:pointcut id="pc" expression="execution(* com.spring.service.impl.UserServiceImpl.addUser(..))"/>
<!--配置切面-->
<aop:aspect ref="myAdvice">
<!--前置通知-->
<aop:before method="before" pointcut-ref="pc"></aop:before>
<!--后置通知-->
<aop:after-returning method="after" pointcut-ref="pc"></aop:after-returning>
</aop:aspect>
</aop:config>
}
环绕通知、异常通知、最终通知的xml配置类似。
前置通知:目标方法运行之前调用
后置通知(如果出现异常不会调用):在目标方法运行之后调用
环绕通知:在目标方法之前和之后都调用
异常拦截通知:如果出现异常,就会调用
最终通知(无论是否出现 异常都会调用):在目标方法运行之后调用
测试
public class TestAopXml {
@Test
public void test(){
//启动Spring容器,加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//从Spring容器中获取对象
UserService u = context.getBean(UserService.class);
int count = u.addUser(new User(101, "法外狂徒张三"));
System.out.println(count);
}
}
运行截
2、注解配置
导入依赖
和XML配置导入的依赖差不多,加一个spring-test的依赖,方便测试用
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
创建配置类AppConfig
@Configuration
@ComponentScan("com.spring")
@EnableAspectJAutoProxy
public class AppConfig {
}
扫描当前的com.spring包下的所有类;
使用注解实现日志文件
@Component
@Aspect
public class LogAspect {
/*
第1个 * 表示方法的任意返回值
第2个 * 表示com.spring.dao包中的所有类
第3个 * 表示以add开头的所有方法
.. 括号中的两点,表示任意参数
*/
@Pointcut("execution(* com.spring.dao.*.add*(..))||"+
"execution(* com.spring.dao.*.delete*(..))||"+
"execution(* com.spring.dao.*.query*(..))")
public void pointCut(){}
@Before("pointCut()")
public void logStart(JoinPoint jp){
System.out.println(jp.getTarget());
Logger.info("加入日志");
}
@After("pointCut()")
public void lodEnd(JoinPoint jp){
Logger.info("方法调用结束加入日志");
}
}
@Aspect:作用是把当前类标识为一个切面供容器读取
@Pointcut:Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。
@Around:环绕增强,相当于MethodInterceptor
@AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行
@Before:标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有
@AfterThrowing:异常抛出增强,相当于ThrowsAdvice
@After: final增强,不管是抛出异常或者正常退出都会执行
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class TestAopAnnotation {
@Autowired
private UserController controller;
@Test
public void test(){
User u = new User(1001,"法外狂徒张三");
controller.setUser(u);
controller.add();
controller.delete();
controller.query();
}
}
运行截图
使用注解配置更加的简洁,但是理解起来比较困难一点。