代理比较好理解,类似于生活中的房屋中介、经销商、代理商
假设没有代理,顾客就可以直接从工厂里面购买东西了
很美好,没有中间商赚差价
但现实很骨感,工厂在售卖的时候,需要负责接待顾客、展示产品、磋商洽谈等等这些工作,当只有一个顾客的话还好,如果有成千上万个顾客,那工厂一定吃不消了,这个时候自然而然想要把这些重复的工作交给别人来干。
Salable
接口,可售卖接口public interface Salable {
void sale();
}
Product
类,public class Product implements Salable{
private long money;
public Product(long money){
this.money = money;
}
public long getMoney() {
return money;
}
public void setMoney(long money) {
this.money = money;
}
public void sale(){
System.out.println("买东西,价格为"+money+"$");
}
}
Proxy
public class Proxy implements Salable{
Product product;
public Proxy(Product product){
this.product = product;
}
public void sale() {
System.out.println("欢迎光临!");
System.out.println("原价为:"+product.getMoney());
System.out.println("服务价位:1000$");
product.setMoney(product.getMoney()+1000L);
product.sale();
System.out.println("欢迎下次光临!");
}
}
Client
public class Client {
@Test
public void purchase(){
Proxy proxy = new Proxy(new Product(2000L));
proxy.sale();
}
}
结果为:
欢迎光临!
原价为:2000
服务价位:1000$
买东西,价格为3000$
欢迎下次光临!
以上整个过程就是代理的过程了,这样的代理叫做“静态代理”,静态是相对于动态而言的,静态代理不能实现对不同顾客呈现不同的表现,而动态代理却可以。
java的动态代理本质还是依靠反射来实现的,它能够动态生成代理类。
动态代理分为两大类:
Salable
接口public interface Salable {
void sale();
void secondHandSale();
}
Product
public class Product implements Salable{
private long money;
public Product(long money){
this.money = money;
}
public long getMoney() {
return money;
}
public void setMoney(long money) {
this.money = money;
}
public void sale(){
System.out.println("卖东西,价格为"+money+"$");
}
public void secondHandSale() {
System.out.println("卖东西,价格为"+(money*0.8)+"$");
}
}
InvocationHandler
的实现类ProxyInvocationHandler
,它的作用是动态生成Salable
的代理类public class ProxyInvocationHandler implements InvocationHandler {
private Salable salable;
public void setSalable(Salable salable) {
this.salable = salable;
}
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
salable.getClass().getInterfaces(),
this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equalsIgnoreCase("secondHandSale")){
System.out.println("二手商品打八折");
}
Object result = method.invoke(salable,args);
return result;
}
}
public class Client {
@Test
public void purchase(){
Product product = new Product(2000L);
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setSalable(product);
Salable proxy = (Salable) pih.getProxy();
proxy.sale();
System.out.println();
proxy.secondHandSale();
}
}
结果输出为:
卖东西,价格为2000$
二手商品打八折
卖东西,价格为1600.0$
JDK只能提供对接口的动态代理的实现,接下来我们使用cglib来实现对类的直接动态代理
<dependency>
<groupId>cglibgroupId>
<artifactId>cglibartifactId>
<version>3.3.0version>
dependency>
public class Product {
private long money;
public Product() {
}
public Product(long money){
this.money = money;
}
public long getMoney() {
return money;
}
public void setMoney(long money) {
this.money = money;
}
public void sale(){
System.out.println("卖东西,价格为"+money+"$");
}
public void secondHandSale(){
System.out.println("卖东西,价格为"+(money*0.8)+"$");
}
}
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class Client {
public static void main(String[] args) {
//和JDK提供的动态代理的Proxy类一样,只不过Enhancer能够同时支持接口和普通类
Enhancer enhancer = new Enhancer();
//设置要代理的类,将其作为父类
enhancer.setSuperclass(Product.class);
//设置回调方法——可实现对方法的增强
enhancer.setCallback(new MethodInterceptor(){
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if (method.getName().equalsIgnoreCase("secondHandSale")){
System.out.println("二手商品打八折");
}
Object result = methodProxy.invokeSuper(o, objects);
return result;
}
});
Product product = (Product) enhancer.create();
product.setMoney(2000L);
product.sale();
System.out.println();
product.secondHandSale();
}
}
卖东西,价格为2000$
二手商品打八折
卖东西,价格为1600.0$
Joinpoint:连接点,在Spring中,这些点指的是方法,因为Spring只支持方法的连接
Pointcut:切入点,指对哪些连接进行拦截,哪些不拦截
连接点不一定是切入点
Advice:通知/增强,即拦截到Joinpoint之后所要做的事情
通知的类型有:前置通知、后置通知、异常通知、最终通知、环绕通知
Introduction:引介,特殊的通知,在不修改类代码的前提下在运行期动态得为类添加一些方法或Field
Target:目标对象,被代理的目标对象
Weaving:织入,是把增强应用到目标对象来创建新的代理对象的过程
Spring采用动态代理织入,AspectJ采用编译器织入和类转载期织入
Proxy:代理,一个类被AOP织入增强后,所产生的一个结果代理类
Aspect:切面,即切入点和通知的结合
XML各个通知
before
:切入点方法执行之前执行after-returning
:切入点方法执行之后执行after-throwing
:切入点方法出现异常时执行after
:不管有没有出现异常,该方法都会执行around
:前置、后置、异常、最终通知的集合 <dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.5version>
dependency>
public class TransferAccount {
public void transferAccount(){
System.out.println("转账$$$$");
}
}
public class Logger {
public void logger(){
System.out.println("记录!!!!!!!!");
}
}
applicationContext.xml
文件,注意导入约束<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/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="TransferAccount" class="com.cap.service.TransferAccount" />
<bean id="Logger" class="com.cap.log.Logger" />
<aop:config>
<aop:aspect ref="Logger">
<aop:pointcut id="logTransferAccount" expression="execution(* com.cap.service.*.*(..))"/>
<aop:before method="logger" pointcut-ref="logTransferAccount"/>
aop:aspect>
aop:config>
beans>
public class MyTest {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
TransferAccount transferAccount = context.getBean(TransferAccount.class);
transferAccount.transferAccount();
}
}
记录!!!!!!!!
转账$$$$
可以看到我们前置通知的方法已经织入了
使用后置通知
<aop:after-returning method="logger" pointcut-ref="logTransferAccount" />
结果得到
转账$$$$
记录!!!!!!!!
使用后置通知
<aop:around method="logger" pointcut-ref="logTransferAccount" />
结果得到:
记录!!!!!!!!
当配置了环绕通知后,切入点方法没有执行,而通知方法执行了
Spring框架为我们提供了一个接口:ProceedingJoinPoint。
该接口有一个方法proceed(),此方法相当于明确调用切入点方法。
该接口可以作为环绕通知的方法参数,在程序执行时,
Spring框架会为我们提供接口的实现类供我们使用。
在Logger类中添加环绕通知的方法
public Object aroundLogger(ProceedingJoinPoint pjp){
Object result = null;
Object[] args = pjp.getArgs();
try {
System.out.println("前置通知:记录!!!!!!!!");
result = pjp.proceed();
System.out.println("后置通知:记录!!!!!!!!");
} catch (Throwable throwable) {
System.out.println("异常通知:记录!!!!!!!!");
} finally {
System.out.println("最终通知:记录!!!!!!!!");
}
return result;
}
修改xml
<aop:around method="aroundLogger" pointcut-ref="logTransferAccount" />
运行测试方法,得到
前置通知:记录!!!!!!!!
转账$$$$
后置通知:记录!!!!!!!!
最终通知:记录!!!!!!!!
切入点表达式的写法
切入点表达式的写法
关键字:execution
表达式: 访问修饰符 返回值 包名.包名…包名.类名.方法名(参数列表)
实例:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
void com.itheima.service.impl.AccountServiceImpl.saveAccount()
* com.itheima.service.impl.AccountServiceImpl.saveAccount()
*.
* *.*.*.*.AccountServiceImpl.saveAccount()
..
表示当前包及其子包
* *..AccountServiceImpl.saveAccount()
*
通配符
* *..*.*()
* *..*.*(..)
* com.itheima.service.impl.*.*(..)
修改元数据配置文件
<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/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="TransferAccount" class="com.cap.service.TransferAccount" />
<bean id="Logger" class="com.cap.log.Logger" />
<aop:aspectj-autoproxy />
beans>
@Aspect
表明这是一个切面@Aspect
public class Logger {
@Before("execution(* com.cap.service.*.*(..))")
public void logger(){
System.out.println("记录!!!!!!!!");
}
通过aop命名空间的