在JavaEE分层开发中,最为重要的是Service层。
概念:通过代理类,为原始类(目标类)增加额外的功能。
名词解释:
目标类(原始类):指的是 业务类(核心功能 --> 业务运算、DAO调用);
目标方法(原始方法):目标类(原始类)中的方法就是 目标方法(原始方法);
额外功能(附加功能):主要以日志、事务、性能为代表。
代理类 = 目标类(原始类)+ 额外功能 + 实现目标类(原始类)相同的接口
创建原始类(目标类)实现核心功能:
public class UserServiceImpl implements UserService {
private UserDao userDao =(UserDao) BeanFactory.getBean("userDao");
@Override
public void register(User user) {
userDao.save(user);
}
@Override
public void login(String name, String password) {
userDao.queryUserByUsernameAndPassword(name,password);
}
}
代理类:
//实现目标类相同的接口
public class UserServiceProxy implements UserService{
//目标类
private UserService userService = new UserServiceImpl();
@Override
public void register(User user) {
//额外功能
System.out.println("--------log-------");
//目标类的核心功能
userService.register(user);
}
@Override
public void login(String name, String password) {
//额外功能
System.out.println("--------log-------");
//目标类的核心功能
userService.login(name,password);
}
}
概念:通过代理类,为原始类(目标类)增加额外的功能。
搭建开发环境:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>5.2.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.5version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjrtartifactId>
<version>1.9.6version>
dependency>
创建原始类对象(目标类对象)
public class UserServiceImpl implements UserService {
private UserDao userDao =(UserDao) BeanFactory.getBean("userDao");
@Override
public void register(User user) {
userDao.save(user);
}
@Override
public void login(String name, String password) {
userDao.queryUserByUsernameAndPassword(name,password);
}
}
<bean class="com.itheima.basic.UserServiceImpl" id="userService">bean>
额外功能:
public class Before implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("--------log--------");
}
}
<bean class="com.itheima.basic.Before" id="before">bean>
定义切入点:
切入点:额外功能加入的位置。
目的:程序员根据自己的需要,决定额外功能加入给哪个原始方法。
<aop:config>
<aop:pointcut id="pt1" expression="execution(* com.itheima.basic.*.*(..))"/>
aop:config>
组装(2、3步骤)
组装:把 切入点 和 额外功能 进行整合。
<aop:config>
<aop:pointcut id="pt1" expression="execution(* com.itheima.basic.*.*(..))"/>
<aop:advisor advice-ref="before" pointcut-ref="pt1">aop:advisor>
aop:config>
调用
目的:获得Spring工厂创建的动态代理对象,并进行调用。
ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext3.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.login("aaa","sss");
//--------log--------
//query User name = aaa,password = sss
注意:
Spring创建的动态代理类在哪里?
什么叫动态字节码技术?
动态代理技术的好处:动态代理不需要定义类文件,都是JVM运行过程中动态创建的,所以不会造成静态代理类文件数量过多,影响项目管理的问题。
动态代理编程会简化代理的开发。
动态代理的额外功能的可维护性大大增强。
public class Before implements MethodBeforeAdvice {
/**
作用:需要把运行在原始方法执行之前运行的额外功能,书写在before方法中
方法的参数:
method:额外功能所增加给的那个原始方法。
objects:额外功能所增加给的那个原始方法参数数组。
o:代表原始对象。
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("--------log--------");
}
}
public class MyInterceptor implements MethodInterceptor {
/*
invoke方法的作用:额外功能书写在invoke方法中,可以执行在原始方法之前、之后、或者环绕都可以
参数:
MethodInvocation:额外功能要增加的那个原始方法。
- 使用methodInvocation.proceed()使原始方法运行.
返回值:原始方法的返回值
- methodInvocation.proceed()的返回值就是原始方法的返回值
*/
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object object = null;
try{
System.out.println("前置方法");
object = methodInvocation.proceed();
System.out.println("后置方法");
}catch (Exception e){
System.out.println("原始方法抛出异常时需要执行的方法");
e.printStackTrace();
}
return object;
}
}
切入点决定了额外功能加入的位置(方法)。
<aop:pointcut id="pt1" expression="execution(* *(..))"/>
* *(..) --> 所有方法
含义:
* --> 修饰符 返回值
* --> 方法名
() --> 参数表
.. --> 对于参数没有要求(参数有没有,参数有几个都行,参数是什么类型都行)
定义login方法作为切入点:
<aop:pointcut id="pt1" expression="execution(* com.itheima.basic.UserServiceImpl.login(String,String))"/>
注意:
1. 对于非java.lang包下的类,必须要写全限定类名;
2. ..是可以和具体的参数类型联用的。
类切入点表达式:
语法1:定义com.itheima.basic包下的UserServiceImpl这个类的所有方法的切入点:
<aop:pointcut id="pt1" expression="execution(* com.itheima.basic.UserServiceImpl.*(..))"/>
语法2:定义任意包下的UserServiceImpl类的所有方法的切入点:
<aop:pointcut id="pt1" expression="execution(* *..UserServiceImpl.*(..))"/>
注意:切入点表达式中要用*..来表示多级包
包切入点表达式(更具实战价值):
切入点包中的所有类,必须在basic包中,不能在basic包的子包中:
<aop:pointcut id="pt1" expression="execution(* com.itheima.basic.*.*(..))"/>
切入点当前包及其子包都生效:
<aop:pointcut id="pt1" expression="execution(* com.itheima.baisc..*.*(..))"/>
切入点函数:用于执行切入点表达式。
execution:最为重要的切入点函数,功能最完整;
args:主要用于函数或者方法参数的匹配;
比如:方法参数必须得是2个字符串类型的参数:
<aop:pointcut id="pt1" expression="args(String,String)"/>
within:主要用于进行类或者包切入点表达式的匹配;
比如:UserServiceImpl这个类的所有方法:
<aop:pointcut id="pt1" expression="within(*..UserServiceImpl)"/>
比如指定basic包下的所有方法:
<aop:pointcut id="pt1" expression="within(com.itheima.basic..*)"/>
@annotation:为具有特殊注解的方法加入额外功能;
创建自定义注解:
package com.itheima.basic;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
}
在要增加额外功能的方法上加上该注解。
在切入点函数中指定@annotation的切入点表达式:
<aop:pointcut id="pt1" expression="@annotation(com.itheima.basic.Log)"/>
切入点函数的逻辑运算:整合多个切入点函数一起工作,进而完成更为复杂的需求。
and与操作
案例:login方法 同时要求有两个字符串参数
execution(* login(String,String))
使用and与 -->
execution(* login(..)) and args(String,String)
注意:与操作不能用于同类型的切入点函数。
or或操作
案例:register方法 与 login方法 作为切入点
execution(* login(..)) or execution(* register(..))
AOP(Aspect Oriented Programing),面向切面编程:以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建。切面 = 切入点 + 额外功能。
AOP的概念:本质就是Spring的动态代理开发,通过代理类为原始类增加额外功能。
好处:利于原始类的维护。
注意:AOP不能取代OOP,只是对OOP编程进行有利的补充。
- 原始对象;
- 额外功能(实现MethodInterceptor接口,重写invoke方法);
- 切入点;
- 组装切面。
切面:是由切入点+额外功能组成的。
JDK的动态代理是基于接口创建动态代理对象的,当原始类实现了某接口,那么就可以使用 JDK的动态代理创建动态代理对象:
public class TestJDKProxy {
public static void main(String[] args) {
//创建原始对象
UserService userService = new UserServiceImpl();
//JDK创建动态代理对象
UserService userServiceProxy = (UserService) Proxy.newProxyInstance(
//参数1:任意借用一个类加载器,使得代理类加载进JVM
TestJDKProxy.class.getClassLoader()
//参数2:原始对象所实现的接口
, userService.getClass().getInterfaces()
//参数3:额外功能的方法
, new InvocationHandler()
{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//参数1:Object proxy,代表代理对象,基本不用
//参数2:Method method,代表原始方法
//参数3: Object[] args,代表原始方法的参数
//返回值:Object,原始方法的返回值
//增加额外功能
System.out.println("-------log--------");
//实现原始对象的方法
Object ret = method.invoke(userService, args);
return ret;
}
});
userServiceProxy.login("suns","123456");
}
}
当要增加额外功能的原始类没有实现任何接口,那就可以使用cglib的动态代理(基于子类)创建动态代理对象。
cglib创建动态代理的原理:父子继承关系创建动态代理对象,原始类作为父类,代理类作为子类,这样既可以2者保证方法一致,也可以在代理类中提供新的实现。(额外功能+原始方法)
代码实现:
public class UserService {
private UserDao userDao =(UserDao) BeanFactory.getBean("userDao");
public void register(User user) {
userDao.save(user);
}
public void login(String name, String password) {
userDao.queryUserByUsernameAndPassword(name,password);
}
}
public class TestCglibProxy {
public static void main(String[] args) {
//1.创建原始对象
UserService userService = new UserService();
//2.通过cglib的方式创建动态代理对象
//cglib提供了一个Enhancer类
Enhancer enhancer = new Enhancer();
//需要设置Enhancer类对象的三个属性
//设置代理类的父类
enhancer.setSuperclass(UserService.class);
//设置代理类字节码的类加载器
enhancer.setClassLoader(TestCglibProxy.class.getClassLoader());
//设置代理类增加的额外功能:
//一般在CallBack接口的子接口MethodInterceptor的内部实现类中定义额外方法
MethodInterceptor methodInterceptor = new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("----------proxy log-----------");
method.invoke(userService,objects);
return null;
}
};
enhancer.setCallback(methodInterceptor);
//使用Enhancer类对象的create方法创建代理类,由于代理类是UserService的子类,所以类型也可以声明为UserService
UserService userServiceProxy = (UserService)enhancer.create();
userServiceProxy.login("aaa","sss");
}
}
JDK提供的动态代理:
Proxy.newProxyInstance():通过实现接口来创建代理的实现类
Cglib动态代理:
Enhancer:通过继承父类来实现的代理类
Spring创建代理对象,是通过BeanPostProcessor指定的:
public class MyAOP implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof OrderService){
//创建该类的代理对象
OrderService proxyInstance =(OrderService) Proxy.newProxyInstance(MyAOP.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("增加的额外功能!");
Object ret = method.invoke(bean, args);
return ret;
}
});
return proxyInstance;
}
return bean;
}
}
<bean id="aop" class="com.itheima.basic.aop.MyAOP">bean>
<bean class="com.itheima.basic.aop.OrderServiceImpl" id="orderService">bean>
- 原始对象;
- 额外功能;
- 切入点;
- 组装切面。
原始对象:
<bean class="com.itheima.basic.aspect.OrderServiceImpl" id="orderService">bean>
定义切入点及额外方法:
/**
* 切面都得有两个固定的组成:
* 1.切入点
* 2.额外功能
*/
@Aspect//表示该类为切面类
public class MyAspect {
//将切入点定义为切面类的方法,实现切入点复用
//切入点表达式之间用||、&&进行与或运算
@Pointcut(value = "execution(* com.itheima.basic.aspect.OrderServiceImpl.*(..)) || execution(* *(..))")
public void pt1(){
}
//定义前置额外功能
@Before("pt1()")
public void before(){
System.out.println("前置方法!");
}
//定义后置方法
@After("pt1()")
public void after(){
System.out.println("后置方法!");
}
//定义异常后抛出的额外功能方法
@AfterThrowing("pt1()")
public void afterThrowing(){
System.out.println("异常后执行");
}
//最终方法(finally中执行的方法)
@AfterReturning("pt1()")
public void afterReturning(){
System.out.println("最终方法");
}
//环绕方法
@Around("pt1()")
public Object around(ProceedingJoinPoint proceedingJoinPoint){
Object proceed = null;
try {
System.out.println("前置方法!");
Object[] args = proceedingJoinPoint.getArgs();
proceed = proceedingJoinPoint.proceed(args);
System.out.println("后置方法!");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("异常时额外方法");
}finally {
System.out.println("最终方法!");
}
return proceed;
}
}
在配置文件中配置该类,告知Spring基于注解进行aop开发:
<bean class="com.itheima.basic.aspect.MyAspect" id="aspect">bean>
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
Spring中AOP编程默认使用的是JDK的动态代理模式,若要切换成Cglib动态代理模式,则需要在配置文件对proxy-target-class进行设置:
对于传统的配置文件进行aop开发:
<aop:config proxy-target-class="true">
...
aop:config>
对于基于注解的方式进行aop开发:
<aop:aspectj-autoproxy proxy-target-class="true">aop:aspectj-autoproxy>
在同一个业务类中,尽心业务方法的相互调用。只有最外层的方法,才是加入了额外功能的(内部的方法,通过普通的方式调用,都是调用的原始方法)。如果想让内层的方法也调用代理对象的方法,可以通过让业务类实现ApplicationContextAware接口,实现其方法,获得ApplicationContext对象,以此来获得业务类的代理对象,将内层方法替换为代理类的方法,就可以获得加入额外功能的方法。
public class OrderServiceImpl implements OrderService, ApplicationContextAware {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
@Override
public void save() {
System.out.println("保存对象!");
}
@Override
public void login() {
context.getBean("orderService",OrderService.class).save();
}
}
AOP编程(Spring的动态代理开发)
AOP编程的开发步骤(动态代理的开发步骤):
基于XML方式的AOP开发:
在Spring配置文件中创建原始对象:
<bean class="com.itheima.basic.UserServiceImpl" id="userService">bean>
额外功能:
创建类实现MethodInterceptor接口,重写invoke方法,在其中增加额外功能
并在配置文件中配置该类:
<bean class="com.itheima.basic.MyInterceptor" id="before">bean>
切入点:
<aop:pointcut id="pt1" expression="execution(* login(..)) and args(String) and within(*..UserServiceImpl)"/>
组装切面
<aop:config proxy-target-class="true">
<aop:advisor advice-ref="before" pointcut-ref="pt1">aop:advisor>
aop:config>
基于注解方式的AOP开发
在Spring配置文件中创建原始对象:
<bean class="com.itheima.basic.UserServiceImpl" id="userService">bean>
定义切面类,并在切面类中定义切入点,额外功能,组装切面:
@Aspect//表示该类为切面类
public class MyAspect {
//将切入点定义为切面类的方法
@Pointcut(value = "execution(* com.itheima.basic.aspect.OrderServiceImpl.*(..)) || execution(* *(..))")
public void pt1(){
}
//环绕方法
@Around("pt1()")
public Object around(ProceedingJoinPoint proceedingJoinPoint){
Object proceed = null;
try {
System.out.println("前置方法!");
Object[] args = proceedingJoinPoint.getArgs();
proceed = proceedingJoinPoint.proceed(args);
System.out.println("后置方法!");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("异常时额外方法");
}finally {
System.out.println("最终方法!");
}
return proceed;
}
}
在配置文件中配置切面类,并指定利用注解开发AOP:
<bean class="com.itheima.basic.aspect.MyAspect" id="aspect">bean>
<aop:aspectj-autoproxy proxy-target-class="true">aop:aspectj-autoproxy>
AOP的底层实现
JDK动态代理(Spring默认方式):基于原始类的接口 创建代理对象的实现
Proxy.newProxyInstance(classLoader,interfaces,invokeHandler)
Cglib动态代理:把原始类作为代理类的父类 通过继承的关系创建代理对象
Enhancer enhancer = new Enhancer();
//需要设置Enhancer类对象的三个属性
//设置代理类的父类
enhancer.setSuperclass(UserService.class);
//设置代理类字节码的类加载器
enhancer.setClassLoader(TestCglibProxy.class.getClassLoader());
//设置代理类增加的额外功能:
//一般在CallBack接口的子接口MethodInterceptor的内部实现类中定义额外方法
MethodInterceptor methodInterceptor = new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy
...
}
};
enhancer.setCallback(methodInterceptor);
//使用Enhancer类对象的create方法创建代理类,由于代理类是UserService的子类,所以类型也可以声明为UserService
UserService userServiceProxy = (UserService)enhancer.create();
两种动态代理的切换:
基于xml:
<aop:config proxy-target-class="true">
...
aop:config>
基于注解:
<aop:aspectj-autoproxy proxy-target-class="true">aop:aspectj-autoproxy>
Spring底层基于BeanPostProcessor完成对象的加工,创建动态代理对象。