一在抛、AOP概述
1.AOP
全稱是Aspect Oriented Programming即:面向切面編程。
簡單的說它就是把程序重復(fù)的代碼抽取出來谦炒,在需要執(zhí)行的時候挫剑,使用動態(tài)代理的技術(shù)去扣,在不修改源碼的基礎(chǔ)上,對我們的已有方法進(jìn)行增強(qiáng)樊破。
2.作用
在程序運(yùn)行期間愉棱,不修改源碼對已有方法進(jìn)行增強(qiáng)唆铐。
①減少重復(fù)代碼
②提高開發(fā)效率
③維護(hù)方便
AOP的實(shí)現(xiàn)方式
使用動態(tài)代理技術(shù)
二、動態(tài)代理
1.動態(tài)代理的特點(diǎn)
字節(jié)碼隨用隨創(chuàng)建奔滑,隨用隨加載或链。
它與靜態(tài)代理的區(qū)別也在于此。因?yàn)殪o態(tài)代理是字節(jié)碼一上來就創(chuàng)建好档押,并完成加載澳盐。
裝飾者模式就是靜態(tài)代理的一種體現(xiàn)。
2.動態(tài)代理常用的兩種方式
- 基于接口的動態(tài)代理
提供者:JDK官方的Proxy類令宿。
要求:被代理類最少實(shí)現(xiàn)一個接口叼耙。
創(chuàng)建代理對象的方法:newProxyInstance(ClassLoader,Class[],InvocationHandler)
參數(shù)的含義:
ClassLoader
:類加載器粒没。和被代理對象使用相同的類加載器筛婉。一般都是固定寫法
Class[]
:字節(jié)碼數(shù)組。被代理類實(shí)現(xiàn)的接口癞松。要求代理對象和被代理對象有相同的行為爽撒。一般都是固定寫法。
InvocationHandler
:它是一個接口响蓉,就是用于提供增強(qiáng)代碼的硕勿。一般都是寫一個該接口的實(shí)現(xiàn)類。實(shí)現(xiàn)類可以是匿名內(nèi)部類枫甲。
含義:如何代理源武。這個參數(shù)的代碼只能是誰用誰提供。
策略模式:數(shù)據(jù)有想幻,目標(biāo)明確粱栖,達(dá)成目標(biāo)的過程就是策略。
舉一個演員和經(jīng)紀(jì)公司的例子
定義一個藝人標(biāo)準(zhǔn)的接口IActor.java
package com.company.proxy;
/**
* 藝人標(biāo)準(zhǔn)
*/
public interface IActor {
public void basicAct(float money);
public void dangerAct(float money);
}
再實(shí)現(xiàn)一個演員類Actor.java
package com.company.proxy;
/**
* 一個演員
*/
public class Actor implements IActor{
public void basicAct(float money){
System.out.println("拿到錢脏毯,開始基本表演"+money);
}
public void dangerAct(float money){
System.out.println("拿到錢闹究,開始危險表演"+money);
}
}
最后再來個經(jīng)紀(jì)公司管理演員的出場費(fèi)用,并使用動態(tài)代理
Client.java
package com.company.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 模擬一個經(jīng)紀(jì)公司
*/
public class Client {
public static void main(String[] args) {
Actor actor = new Actor();
//沒有經(jīng)紀(jì)公司的時候食店,直接找演員渣淤。
// actor.basicAct(100f);
// actor.dangerAct(500f);
//動態(tài)代理
IActor proxyActor = (IActor) Proxy.newProxyInstance(actor.getClass().getClassLoader(),
actor.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 執(zhí)行被代理對象的任何方法都會經(jīng)過該方法,該方法有攔截的功能
* 方法的參數(shù)
* Object proxy:代理對象的引用叛买。不一定每次都會有
* Method method:當(dāng)前執(zhí)行的方法
* Object[] args:當(dāng)前執(zhí)行方法所需的參數(shù)
* 返回值:
* 當(dāng)前執(zhí)行方式的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object rtValue = null;
//1.取出執(zhí)行方法中的參數(shù)
Float money = (Float) args[0];
//2.判斷當(dāng)前執(zhí)行的是什么方法
//每個經(jīng)紀(jì)公司對不同演出收費(fèi)不一樣性芬,此處開始判斷
if ("basicAct".equals(method.getName())){
//基本演出
if (money > 10000){
//執(zhí)行方法
//看上去劇組是給了8000褪那,實(shí)際到演員手里只有4000
//這就是我們沒有修改原來basicAct方法源碼镣奋,對方法進(jìn)行了增強(qiáng)
rtValue = method.invoke(actor,money/2);
}
}
if ("dangerAct".equals(method.getName())){
//危險演出
if (money > 50000){
//執(zhí)行方法
rtValue = method.invoke(actor,money);
}
}
return rtValue;
}
});
//劇組無法直接聯(lián)系演員推姻,而是由經(jīng)紀(jì)公司找的演員
proxyActor.basicAct(80000f);
proxyActor.dangerAct(60000f);
}
}
- 基于子類的動態(tài)代理
提供者:第三方的CGLib哪轿,如果報asmxxxx異常,需要導(dǎo)入asm.jar华烟。
要求:被代理類不能用final修飾的類(最終類)坑鱼。
涉及的類:Enhancer
創(chuàng)建代理對象的方法:create(Class,Callback);
參數(shù)的含義:
Class
:被代理對象的字節(jié)碼
Callback
:如何代理。它和InvocationHandler的作用一樣动漾。
它也是一個接口丁屎,一般使用該接口的子接口MethodInterceptor,在使用是也是創(chuàng)建該接口的匿名內(nèi)部類旱眯。
還是上述例子
不需要IActor.java的接口
更改Client.java
package com.company.proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 模擬一個經(jīng)紀(jì)公司
*/
public class Client {
public static void main(String[] args) {
Actor actor = new Actor();
//動態(tài)代理
Actor cglibActor = (Actor) Enhancer.create(actor.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
/**
* 執(zhí)行被代理對象的任何方法都會經(jīng)過該方法晨川。它和基于接口動態(tài)代理的invoke方法的作用一樣
* 方法的參數(shù):
* 前面三個和invoke方法的參數(shù)含義和作用一樣。
* MethodProxy methodProxy:當(dāng)前執(zhí)行方法的代理對象删豺,一般不用
*/
Object rtValue = null;
//1.取出執(zhí)行方法中的參數(shù)
Float money = (Float) objects[0];
//2.判斷當(dāng)前執(zhí)行的是什么方法
//每個經(jīng)紀(jì)公司對不同演出收費(fèi)不一樣共虑,此處開始判斷
if ("basicAct".equals(method.getName())){
//基本演出
if (money > 10000){
//執(zhí)行方法
//看上去劇組是給了8000,實(shí)際到演員手里只有4000
//這就是我們沒有修改原來basicAct方法源碼呀页,對方法進(jìn)行了增強(qiáng)
rtValue = method.invoke(actor,money/2);
}
}
if ("dangerAct".equals(method.getName())){
//危險演出
if (money > 50000){
//執(zhí)行方法
rtValue = method.invoke(actor,money);
}
}
return rtValue;
}
});
cglibActor.basicAct(50000);
cglibActor.dangerAct(100000);
}
}
3.動態(tài)代理的應(yīng)用——連接池原理(c3p0)
導(dǎo)入的jar包要mysql-connector-java-5.0.8-bin.jar
因?yàn)檫@個包里的Connection是個類妈拌,而5.1.7的版本里Connection是個接口,無法強(qiáng)轉(zhuǎn)蓬蝶。
關(guān)閉一個連接就少一個尘分,啟用動態(tài)代理,讓用完的連接還回池中
項(xiàng)目目錄結(jié)構(gòu)
dbconfig.properties
#數(shù)據(jù)庫連接的配置
#數(shù)據(jù)庫驅(qū)動
DRIVER=com.mysql.jdbc.Driver
#連接字符串
URL=jdbc:mysql://localhost:3306/goods
#數(shù)據(jù)庫用戶名
USER=root
#數(shù)據(jù)庫密碼
PASSWORD=root
JDBCUtil.java
package com.edu.util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ResourceBundle;
/**
* 數(shù)據(jù)庫操作相關(guān)的工具類
* @author zhy
*
*/
public class JDBCUtil {
//使用ResourceBundle讀取資源文件
private static ResourceBundle bundle = ResourceBundle.getBundle("dbconfig");
private static String driver;
private static String url;
private static String user;
private static String password;
//使用靜態(tài)代碼塊進(jìn)行賦值
static{
driver = bundle.getString("DRIVER");
url = bundle.getString("URL");
user = bundle.getString("USER");
password = bundle.getString("PASSWORD");
}
/**
* 獲取連接
* @return
*/
public static Connection getConnection(){
Connection conn = null;
try {
Class.forName(driver);
conn = DriverManager.getConnection(url, user, password);
return conn;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 釋放資源
* @param rs
* @param st
* @param conn
*/
public static void release(ResultSet rs,Statement st,Connection conn){
if(rs != null){
try{
rs.close();
}catch(Exception e){
e.printStackTrace();
}
}
if(st != null){
try{
st.close();
}catch(Exception e){
e.printStackTrace();
}
}
if(conn != null){
try{
conn.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
自定義連接池MyDataSource.java
package com.edu.dataSource;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.edu.util.JDBCUtil;
/**
* 自定義連接池
* @author zhy
*
*/
public class MyDataSource {
//定義一個池丸氛,用于存放連接
private static List<Connection> pool = Collections.synchronizedList(new ArrayList<Connection>());//把ArrayList轉(zhuǎn)成線程安全的
//使用靜態(tài)代碼塊給池中加入連接
static{
for(int i=0;i<10;i++){
Connection conn = JDBCUtil.getConnection();
pool.add(conn);
}
}
/**
* 獲取一個連接
* @return
*/
public static Connection getConnection(){
final Connection conn = pool.remove(0);
//創(chuàng)建代理對象
Connection proxyConn = (Connection) Proxy.newProxyInstance(conn.getClass().getClassLoader(),
conn.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object rtValue = null;
//1.判斷當(dāng)前方法是不是close方法
if("close".equals(method.getName())){
//不能直接關(guān)閉
pool.add(conn);//還回池中
}else{
rtValue = method.invoke(conn, args);
}
return rtValue;
}
});
return proxyConn;
}
/**
* 獲取池中的連接數(shù)
* @return
*/
public static int getPoolSize(){
return pool.size();
}
}
測試類
package com.edu.test;
import com.edu.dataSource.MyDataSource;
import java.sql.Connection;
public class Main {
public static void main(String[] args) throws Exception {
int size = MyDataSource.getPoolSize();
System.out.println("使用連接之前"+size);
for (int i = 0;i < 8 ;i++) {
Connection conn = MyDataSource.getConnection();
System.out.println(conn);
conn.close();
}
int size1 = MyDataSource.getPoolSize();
System.out.println("使用連接之后"+size1);
for (int i = 0;i < 8 ;i++) {
Connection conn = MyDataSource.getConnection();
System.out.println(conn);
conn.close();
}
}
}
三培愁、Spring中的AOP
1.AOP相關(guān)術(shù)語
- Joinpoint(連接點(diǎn))
業(yè)務(wù)層的接口方法,一端:公共代碼(增強(qiáng)的代碼)缓窜;另一端就是業(yè)務(wù)竭钝,究竟要干什么事。這個連接點(diǎn)就是將業(yè)務(wù)和增強(qiáng)的代碼結(jié)合起來雹洗。 - Pointcut(切入點(diǎn)):
被增強(qiáng)的就是切入點(diǎn)香罐,沒被增強(qiáng)的就是連接點(diǎn)。
連接點(diǎn)不一定是切入點(diǎn)时肿;切入點(diǎn)一定是連接點(diǎn)庇茫。 - Advice(通知/增強(qiáng)):
增強(qiáng)的代碼在哪個類,哪個類就是通知螃成。
通知類型:前置通知旦签,后置通知,異常通知寸宏,最終通知宁炫,環(huán)繞通知。 - Introduction(引介):
引介是一種特殊的通知在不修改類代碼的前提下, Introduction可以在運(yùn)行期為類動態(tài)地添加一些方法或Field氮凝。 - Target(目標(biāo)對象):
代理的目標(biāo)對象羔巢。 - Weaving(織入):
是指把增強(qiáng)應(yīng)用到目標(biāo)對象來創(chuàng)建新的代理對象的過程。
spring采用動態(tài)代理織入,而AspectJ采用編譯期織入和類裝載期織入竿秆。 - Proxy(代理):
一個類被AOP織入增強(qiáng)后启摄,就產(chǎn)生一個結(jié)果代理類。 - Aspect(切面):
是切入點(diǎn)和通知(引介)的結(jié)合幽钢。
2.基于XML的AOP環(huán)境搭建
所需jar包
目錄結(jié)構(gòu)
需要加強(qiáng)的類:
package com.edu.utils;
public class Logger {
/**
* 記錄日志的操作
* 計(jì)劃讓其在業(yè)務(wù)核心方法(切入點(diǎn)方法)執(zhí)行之前執(zhí)行
*/
/**
* 前置通知
*/
public void beforePrintLog(){
System.out.println("Logger中的beforePrintLog方法開始記錄日志了歉备。。匪燕。蕾羊。");
}
/**
* 后置通知
*/
public void afterReturningPrintLog(){
System.out.println("Logger中的afterReturningPrintLog方法開始記錄日志了。帽驯。龟再。。");
}
/**
* 異常通知
*/
public void afterThrowingPrintLog(){
System.out.println("Logger中的afterThrowingPrintLog方法開始記錄日志了界拦。吸申。。享甸。");
}
/**
* 最終通知
*/
public void afterPrintLog(){
System.out.println("Logger中的afterPrintLog方法開始記錄日志了截碴。。蛉威。日丹。");
}
}
配置bean.xml
<?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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置Service -->
<bean id="customerService" class="com.edu.service.impl.CustomerImpl"></bean>
<!-- 基于XML的aop配置 -->
<!-- 1.把通知類交給spring管理 -->
<bean id="Logger" class="com.edu.utils.Logger"></bean>
<!-- 2.導(dǎo)入aop名稱空間,并且使用aop:config開始aop的配置 -->
<aop:config>
<!-- 定義通用的切入點(diǎn)表達(dá)式蚯嫌,如果寫在aop:aspct標(biāo)簽外部哲虾,則表示所有切面可用 -->
<aop:pointcut expression="execution(* com.edu.service.impl.*.*(..))" id="pt1"/>
<!-- 3.使用aop:aspect配置切面,id屬性用于給切面提供一個唯一標(biāo)識择示。ref屬性:用于通知bean的id -->
<aop:aspect id="logAdvice" ref="Logger">
<!-- 4.配置通知的類型束凑,指定增強(qiáng)的方法什么時候執(zhí)行。
method屬性:用于指定的方法名稱栅盲,
pointcut:用于指定切入點(diǎn)表達(dá)式
-->
<!-- 切入點(diǎn)表達(dá)式:
關(guān)鍵字:execution(表達(dá)式)
表達(dá)式寫法:
訪問修飾符 返回值 報名.報名...類名.方法名(參數(shù)列表)
全匹配方法:
public void com.edu.service.impl.CustomerImpl.saveCustomer()
訪問修飾符可以省略
void com.edu.service.impl.CustomerServiceImpl.saveCustomer()
返回值可以使用*號汪诉,表示任意返回值
* com.edu.service.impl.CustomerServiceImpl.saveCustomer()
包名可以使用*號,表示任意包谈秫,但是有幾級包扒寄,需要寫幾個*
* *.*.*.*.CustomerServiceImpl.saveCustomer()
使用..來表示當(dāng)前包,及其子包
* com..CustomerServiceImpl.saveCustomer()
類名可以使用*號拟烫,表示任意類
* com..*.saveCustomer()
方法名可以使用*號该编,表示任意方法
* com..*.*()
參數(shù)列表可以使用*,表示參數(shù)可以是任意數(shù)據(jù)類型硕淑,但是必須有參數(shù)
* com..*.*(*)
參數(shù)列表可以使用..表示有無參數(shù)均可课竣,有參數(shù)可以是任意類型
* com..*.*(..)
全通配方式:
* *..*.*(..)
-->
<!-- 配置前置通知: 永遠(yuǎn)在切入點(diǎn)方法執(zhí)行之前執(zhí)行 -->
<aop:before method="beforePrintLog" pointcut-ref="pt1"/>
<!-- 配置后置通知: 切入點(diǎn)方法正常執(zhí)行之后執(zhí)行 -->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"/>
<!-- 配置異常通知: 切入點(diǎn)方法執(zhí)行產(chǎn)生異常之后執(zhí)行嘉赎。它和后置通知永遠(yuǎn)只能執(zhí)行一個 -->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"/>
<!-- 配置最終通知:無論切入點(diǎn)方法是否正常執(zhí)行,它都會在其后面執(zhí)行 -->
<aop:after method="afterPrintLog" pointcut-ref="pt1"/>
<!-- 定義通用的切入點(diǎn)表達(dá)式:如果只寫在了aop:aspect標(biāo)簽內(nèi)部稠氮,則表達(dá)式只有當(dāng)前切面可用-->
<!--<aop:pointcut id="pt1" expression="execution(* com.edu.service.impl.*.*(..))"/>-->
</aop:aspect>
</aop:config>
</beans>
ui界面:
package com.edu.ui;
import com.edu.service.ICustomerService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Client {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
ICustomerService cs = (ICustomerService) applicationContext.getBean("customerService");
cs.saveCustomer();
}
}
環(huán)繞通知
/**
* 環(huán)繞通知
* 它是spring框架為我們提供的一種可以在代碼中手動控制增強(qiáng)部分什么時候執(zhí)行的方式曹阔。
* 問題:
* 當(dāng)我們配置了環(huán)繞通知之后半开,增強(qiáng)的代碼執(zhí)行了隔披,業(yè)務(wù)核心方法沒有執(zhí)行。
* 分析:
* 通過動態(tài)代理我們知道在invoke方法中寂拆,有明確調(diào)用業(yè)務(wù)核心方法:method.invoke()奢米。
* 我們配置的環(huán)繞通知中,沒有明確調(diào)用業(yè)務(wù)核心方法纠永。
* 解決:
* spring框架為我們提供了一個接口:ProceedingJoinPoint鬓长,它可以作為環(huán)繞通知的方法參數(shù)
* 在環(huán)繞通知執(zhí)行時,spring框架會為我們提供該接口的實(shí)現(xiàn)類對象尝江,我們直接使用就行涉波。
* 該接口中有一個方法proceed(),此方法就相當(dāng)于method.invoke()
*/
public void aroundPringLog(ProceedingJoinPoint pjp){
try {
System.out.println("前置通知:Logger類的aroundPringLog方法記錄日志");
pjp.proceed();
System.out.println("后置通知:Logger類的aroundPringLog方法記錄日志");
} catch (Throwable e) {
System.out.println("異常通知:Logger類的aroundPringLog方法記錄日志");
e.printStackTrace();
}finally{
System.out.println("最終通知:Logger類的aroundPringLog方法記錄日志");
}
}
bean.xml
<aop:around method="aroundPrintLog" pointcut-ref="pt1"/>
3.基于注解的AOP環(huán)境搭建
上述案例
加強(qiáng)的類:
package com.edu.utils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 一個用于記錄日志的類
*
*/
@Component("logger")
@Aspect//配置了切面
public class Logger {
@Pointcut("execution(* com.edu.service.impl.*.*(..))")
private void pt1(){}
/**
* 前置通知
*/
//@Before("pt1()")
public void beforePrintLog(){
System.out.println("前置:Logger中的beforePrintLog方法開始記錄日志了炭序。啤覆。。惭聂。");
}
/**
* 后置通知
*/
//@AfterReturning("pt1()")
public void afterReturningPrintLog(){
System.out.println("后置:Logger中的afterReturningPrintLog方法開始記錄日志了窗声。。辜纲。笨觅。");
}
/**
* 異常通知
*/
//@AfterThrowing("pt1()")
public void afterThrowingPrintLog(){
System.out.println("異常:Logger中的afterThrowingPrintLog方法開始記錄日志了。耕腾。见剩。。");
}
/**
* 最終通知
*/
//@After("pt1()")
public void afterPrintLog(){
System.out.println("最終:Logger中的afterPrintLog方法開始記錄日志了扫俺。苍苞。。牵舵。");
}
/**
* 環(huán)繞通知
*/
@Around("pt1()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try {
System.out.println("Logger中的aroundPrintLog方法開始記錄日志了柒啤。。畸颅。担巩。前置");
rtValue = pjp.proceed();
System.out.println("Logger中的aroundPrintLog方法開始記錄日志了。没炒。涛癌。。后置");
} catch (Throwable e) {
System.out.println("Logger中的aroundPrintLog方法開始記錄日志了。拳话。先匪。。異常");
e.printStackTrace();
}finally{
System.out.println("Logger中的aroundPrintLog方法開始記錄日志了弃衍。呀非。。镜盯。最終");
}
return rtValue;
}
}
bean.xml
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring創(chuàng)建容器時要掃描的包 -->
<context:component-scan base-package="com.edu"></context:component-scan>
<!-- 開啟spring對注解AOP的支持-->
<aop:aspectj-autoproxy />
</beans>
純注解
package config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}
Client.java
package com.itheima.ui;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.itheima.service.ICustomerService;
import config.SpringConfiguration;
public class Client {
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
ICustomerService cs = (ICustomerService) ac.getBean("customerService");
cs.saveCustomer();
}
}