概述
Tomcat Javaweb服務器 幫助我們去集中去處理請求和響應 包括完成Servlet以及相關代碼的運行和解析
而其中Servlet引擎 最核心的功能
Controller | 控制器 |
---|---|
Service | 業務處理 |
DAO | 數據庫的訪問和操作 |
DB | 數據庫 |
設計模式
-
⼴義概念
⾯向對象設計中,解決特定問題的經典代碼
-
狹義概念
GOF4⼈幫定義的23種設計模式:⼯⼚、適配器、裝飾器、⻔⾯、代理、模板… GOF4 (面向對象領域的4個大師)
工廠模式
好處:解耦合
對象的創建方式:
1.直接調用構造方法創建對象 UserService userService=new UserServiceImpl();
把接口的實現類硬編碼在程序中
這行代碼有強關聯關係,如果想改變UserServiceImpl
這行代碼,那麼整個類都要改變
//UserServiceImpl userService = new UserServiceImpl();
//這裏只需要更改BeanFactory就可以更改UserServiceImpl
UserService userService = BeanFactory.getUserservice(); // return new UserServiceImpl();
public class BeanFactory {
public static UserService getUserservice(){
return new UserServiceImpl();
}
}
2.通過反射的形式創建對象解耦合
public class BeanFactory {
public static UserService getUserservice() throws Exception {
Class<?> clazz = Class.forName("com.zhang.basic.UserServiceImpl");
return (UserService) clazz.newInstance();
}
}
//配置文件的方式
private static final Properties env = new Properties() ;
static {
InputStream resourceAsStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties");
try {
env.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static UserService getUserservice() throws Exception {
Class<?> clazz = Class.forName(env.getProperty("UserService"));
return (UserService) clazz.newInstance();
}
#只需要更改配置文件的值就可以更改對應的UserServiceImpl
UserService = com.zhang.basic.UserServiceImpl
方法聲明5個要素 修飾符、返回值類型、方法名、參數表、異常(可選)
通用工廠文件
public class BeanFactory {
private static Properties env = new Properties();
//IO 系統級資源 儘量避免重複打開IO 而且最好是在程序啓動的時候一次性讀取想要的內容 用靜態代碼塊的方式來完成
static{
try {
//爲避免耦合 類名最好寫在配置文件中
//第一步 獲得IO輸入流
InputStream inputStream=BeanFactory.class.getResourceAsStream("/applicationContext.properties");
//第二步 文件內容到封裝 Properties集合中 key = userService value = com.baizhixx.UserServiceImpl
env.load(inputStream);
inputStream.close(); //關閉IO輸入流
} catch (IOException e) {
e.printStackTrace();
}
}
/* 這裏結構所使用的代碼基本一致 只有小配置文件的key是有所區別的
* 所以爲每一個對象提供一個獨立的工廠是沒有價值和意義的
*/
public static Object getBean(String key){
Object ret = null;
try {
Class clazz = Class.forName(env.getProperty(key));
ret = clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
/* test文件中 */
@Test
public void test1() {
UserService userService = (UserService)BeanFactory.getBean("userService");
//返回的是Object但實際需要的是UserService 所以需要強制類型轉換
userService.login("name", "suns"); //具體user Service對象的使用
User user = new User("suns", "123456");
userService.register(user);
}
將文件內容封裝到集合中
Java Properties類:用於讀取java的配置文件
https://www.cnblogs.com/bakari/p/3562244.html
load ( InputStream inStream),從輸入流中讀取屬性列表(鍵和元素對)。通過對指定的文件(比如說xxx.properties 文件)進行裝載來獲取該文件中的所有鍵值對。以供 getProperty ( String key) 來搜索。
第一個Spring程序
環境配置
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.14.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person" class="com.zhang.basic.Person"/>
</beans>
@org.junit.Test
public void test3(){
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
Person person = (Person) ctx.getBean("person");
}
Spring⼯⼚的相關的⽅法
//第一種方式
Person person = (Person) ctx.getBean("person");
//第二種方式 通過這種⽅式獲得對象,就不需要強制類型轉換
Person person =ctx.getBean("person",Person.class);
//第三種方式
//當前Spring的配置⽂件中 只能有⼀個bean class是Person類型
Person person =ctx.getBean(Person.class);
//獲取的是 Spring工廠配置文件中所有bean標籤的id值 person person1
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println("beanDefinitionName = " + beanDefinitionName);
}
//根據類型獲得Spring配置文件中對應的id值
String[] beanNamesForType = ctx.getBeanNamesForType(Person.class);
for (String id : beanNamesForType) {
System.out.println("id = " + id);
}
//用於判斷是否存在指定id值得bean,不能判斷name值
if (ctx.containsBeanDefinition("person")) {
System.out.println("true = " + true);
}else{
System.out.println("false = " + false);
}
//用於判斷是否存在指定id值得bean,也可以判斷name值
if (ctx.containsBean("p")) {
System.out.println("true = " + true);
}else{
System.out.println("false = " + false);
}
注入
通過Spring⼯⼚及配置⽂件,爲所創建對象的成員變量賦值
爲什麼需要注⼊?
通過編碼的⽅式,爲成員變量進⾏賦值,存在耦合
好處:解耦合
如何進⾏注⼊?
- 類的成員變量提供set get⽅法
- 配置spring的配置⽂件
String+8種基本類型
private Integer id;
private String name;
//Getter and Setter
public Integer getId() {return id;}
public void setId(Integer id) {this.id = id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
<bean id="person" name="p" class="com.baizhiedu.basic.Person">
<property name="id">
<value>20</value>
</property>
<property name="name">
<value>zy</value>
</property>
</bean>
基於屬性簡化
<!-- value 屬性只能簡化8種基本類型 + String 注入標籤-->
<bean id="person" name="p" class="com.baizhiedu.basic.Person">
<property name="id" value="20"/>
<property name="name" value="zy"/>
</bean>
基於p命名空間的簡化
在
P:
中間按alt+enter 選擇Create namespace declaration
<bean id="person" name="p" class="com.baizhiedu.basic.Person" p:id="20" p:name="zy"/>
數組
<property name="addresses">
<list>
<value>zpark</value>
<value>shangdi</value>
<value>xierq</value>
<value>xierq</value>
<value>xierq</value>
</list>
</property>
Set集合
<property name="tels">
<set>
<value>138111111</value>
<value>139111111</value>
<value>166111111</value>
<value>166111111</value>
<value>166111111</value>
</set>
</property>
List集合
<property name="addresses">
<list>
<value>zpark</value>
<value>shangdi</value>
<value>xierq</value>
<value>xierq</value>
<value>xierq</value>
</list>
</property>
Map集合
<!--注意: map -- entry -- key有特定的 標籤 <key></key>
值根據對應類型選擇對應類型的標籤-->
<property name="qqs">
<map>
<entry>
<key><value>suns</value></key>
<value>3434334343</value>
</entry>
<entry>
<key><value>chenyn</value></key>
<value>73737373</value>
</entry>
</map>
</property>
Map<String, String> qqs = person.getQqs();
Set<String> keys = qqs.keySet(); //通過keySet獲得對象裏的key 然後通過對象.get(key)獲得value
for (String key : keys) {
System.out.println(key + " value is " + qqs.get(key));
}
Properites
<!--Properties類型 特殊的Map key=String
value=String-->
<props>
<prop key="key1">
value1
</prop>
<prop key="key2">
value2
</prop>
</props>
爲用戶自定義類型賦值
public class UserServiceImpl implements UserService {
// private UserDAO userDAO = new UserDAOImpl();
// private UserDAO userDAO = (UserDAO)BeanFactory.getBean("userDAO");
private UserDAO userDAO; //具體的賦值使用配置文件來完成->解耦合
public UserDAO getUserDAO() {
return userDAO;
}
public void setUserDAO(UserDAO userDAO) { //通過set方法爲UserDAO對象賦值
this.userDAO = userDAO;
}
@Override
public void register(User user) {
userDAO.save(user);
}
@Override
public void login(String name, String password) {
userDAO.queryUserByNameAndPassword(name, password);
}
}
userService接口裏的UserServiceImpl想要使用UserDAO接口裏的UserDAOImpl類的方法
<bean id="userService" class="com.baizhiedu.basic.UserServiceImpl">
<property name="userDAO">
<bean class="com.baizhiedu.basic.UserDAOImpl"/>
</property>
</bean>
@Test
public void test10() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
UserService userService = (UserService) ctx.getBean("userService");
userService.register(new User("suns", "123456")); //UserDAO裏面的方法可以使用
userService.login("xiaohei", "999999");
}
自定義類型賦值第二種方式
第⼀種賦值⽅式存在的問題:
- 配置⽂件代碼冗餘;
- 被注入的對象 (UserDAO)多次創建,浪費(JVM)內存資源。
<bean id="userDAO" class="com.baizhiedu.dao.UserDAOImpl"></bean>
<bean id="userService" class="com.baizhiedu.service.UserServiceImpl">
<!--這裏的name是Setter裏的形參 public void setUserDAO(UserDAO userDAO) {this.userDAO = userDAO; }-->
<!-- ↑ -->
<property name="userDAO">
<ref bean="userDAO"/>
</property>
</bean>
基於屬性簡化
<bean id="userDAO" class="com.baizhiedu.dao.UserDAOImpl"></bean>
<bean id="userService" class="com.baizhiedu.service.UserServiceImpl">
<property name="userDAO" ref="userDAO"/>
</bean>
基於p命名空間的簡化
<bean id="userDAO" class="com.baizhiedu.dao.UserDAOImpl"></bean>
<bean id="userService" class="com.baizhiedu.service.UserServiceImpl" p:userDAO-ref="userDAO"/>
構造注入
public class Customer {
private String name;
private int age;
public Customer(String name) {this.name = name;}
public Customer(int age) {this.age = age;}
public Customer(String name, int age) { //被重載的方法必須改變參數列表(參數個數或類型或順序不一樣);
this.name = name;
this.age = age;
}
}
<bean id="customer" class="com.baizhiedu.basic.constructer.Customer">
<constructor-arg type="int">
<value>20</value>
</constructor-arg>
</bean>
反轉控制
反轉控制(IOC Inverse of Control)
控制:對於成員變量賦值的控制權;
反轉控制:把對於成員變量賦值的控制權,從代碼中轉移(反轉)到 Spring ⼯⼚和配置⽂件中完成。
好處:解耦合;
底層實現:工廠設計模式;
依賴注入
依賴注入 (Dependency Injection - DI)
注⼊:通過 Spring 的⼯⼚及配置⽂件,爲對象(bean,組件)的成員變量賦值;
依賴注⼊:當⼀個類需要另⼀個類時,就意味着依賴,⼀旦出現依賴,就可以把另⼀個類作爲本類的成員變量,最終通過 Spring 配置⽂件進⾏注⼊(賦值)
好處:解耦合;
FactoryBean 接口
public class ConnectionFactoryBean implements FactoryBean<Connection> {
// 用於書寫創建複雜對象時的代碼
@Override
public Connection getObject() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring", "root", "1234");
return conn;
}
// 返回創建的複雜對象的類型
@Override
public Class<Connection> getObjectType() {
return Connection.class;
}
// 是否單例
@Override
public boolean isSingleton() {
return false; // 每一次都創建新的複雜對象
// return true; // 只創建一次這種類型的複雜對象
}
}
<!--class 指定了 ConnectionFactoryBean, 獲得的是該類創建的複雜對象 Connection -->
<bean id="conn" class="com.baizhiedu.factorybean.ConnectionFactoryBean"/>
如果就想獲得
FactoryBean
類型的對象,加個&
,ctx.getBean("&conn")
ConnectionFactoryBean cfb = (ConnectionFactoryBean) ctx.getBean("&conn");
Spring 內部運行流程:
- 配置文件中通過 id conn 獲得 ConnectionFactoryBean 類的對象 ,進而通過 instanceof 判斷出是 FactoryBean 接⼝的實現類;
- Spring 按照規定 getObject() —> Connection;
- 返回 Connection;
實例工廠
- 避免 Spring 框架的侵⼊;
- 整合遺留系統;
public class ConnectionFactory {
public Connection getConnection() {
Connection conn = null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring?useSSL=false", "root", "1234");
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return conn;
}
}
<!--實例工廠-->
<!-- 先創建出工廠實例 實例方法必須先有對象 再有對應的方法調用 -->
<bean id="connFactory" class="com.baizhiedu.factorybean.ConnectionFactory"/>
<!-- 通過工廠實例裏的方法創建複雜對象 -->
<bean id="conn" factory-bean="connFactory" factory-method="getConnection"/>
靜態工廠
<!--靜態工廠-->
<bean id="conn" class="com.baizhiedu.factorybean.StaticFactoryBean" factory-method="getConnection"/>
控制 Spring 工廠創建對象的次數
控制簡單對象的創建次數
配置文件中進行配置:
sigleton:只會創建⼀次簡單對象,默認值;
prototype:每⼀次都會創建新的對象;
<!--控制簡單對象創建次數-->
<bean id="scope" scope="singleton" class="com.baizhiedu.scope.Scope"/>
控制複雜對象的創建次數
如果是 FactoryBean 方式創建的複雜對象:
public class xxxFactoryBean implements FactoryBean {
public boolean isSingleton() {
return true; // 只會創建⼀次
// return false; // 每⼀次都會創建新的
}
}
對象的生命週期
創建階段
Spring 工廠何時創建對象?
scope="prototype"
:Spring 工廠在獲取對象ctx.getBean("xxx")
的同時,創建對象。scope="singleton"
:Spring 工廠創建的同時,創建對象。
通過配置<bean lazy-init="true"/>
也可以實現工廠獲取對象的同時,創建對象。
初始化階段
什麼時候?Spring 工廠在創建完對象後,調用對象的初始化方法,完成對應的初始化操作。
初始化方法提供:程序員根據需求,提供初始化方法,最終完成初始化操作。
初始化方法調用:Spring 工廠進行調用。
提供初始化方法的兩種方式:
InitializingBean
接口:
public cass Product implements InitializingBean {
//程序員根據需求實現的方法, 完成初始化操作
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("Product.afterPropertiesSet");
}
}
對象中提供一個普通的初始化方法,配置文件種配置 init-method
:
public class Product {
public void myInit() {
System.out.println("Product.myInit");
}
}
<bean id="product" class="com.baizhiedu.life.Product" init-method="myInit"/>
- 如果⼀個對象既實現
InitializingBean
同時⼜提供的 普通的初始化方法,執行順序?
先執行InitializingBean
,再執行 普通初始化方法。 - 注入⼀定發⽣在初始化操作的前面。
銷燬階段
Spring 銷燬對象前,會調用對象的銷燬方法,完成銷燬操作。
Spring 什麼時候銷燬所創建的對象?ctx.close();
銷燬方法提供:程序員根據業務需求,定義銷燬方法,完成銷燬操作
銷燬方法調用:Spring 工廠進行調用。
開發流程與初始化操作一樣,提供銷燬方法的兩種方式:
DisposableBean 接口:
public class Product implements DisposableBean {
// 程序員根據⾃⼰的需求, 定義銷燬方法, 完成銷燬操作
@Override
public void destroy() throws Exception {
System.out.println("Product.destroy");
}
}
對象中提供一個普通的銷燬方法,配置文件種配置 destroy-method
:
public class Product {
// 程序員根據⾃⼰的需求, 定義銷燬方法, 完成銷燬操作
public void myDestory() {
System.out.println("Product.myDestory");
}
}
<bean id="product" class="com.baizhiedu.life.Product" destroy-method="myDestory"/>
- 銷燬方法的操作只適用於
scope="singleton"
,初始化操作都適用。 - 銷燬操作到底是什麼?
資源的釋放:io.close()
、connection.close()
、…
配置文件參數化
${key}
jdbc.driverClassName = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/spring?useSSL=false
jdbc.username = root
jdbc.password = 1234
<!--Spring的配置文件與⼩配置文件進行整合 classpath代表類路徑:class文件夾這個路徑-->
<!--resources 下的文件在整個程序編譯完後會被放到 classpath 目錄下,src.main.java中的文件也是-->
<context:property-placeholder location="classpath:/db.properties"/>
<!-- 在 Spring 配置文件中通過 ${key} 獲取小配置文件中對應的值 -->
<bean id="conn" class="com.baizhiedu.factorybean.ConnectionFactoryBean">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
自定義類型轉換器
public class MyDateConverter implements Converter<String, Date> { //<原始類型,轉換好的類型>
/*
convert方法作用: String ---> Date
SimpleDateFormat sdf = new SimpleDateFormat();
sdf.parset(String) ---> Date
參數:
source : 代表的是配置文件中, 日期字符串 <value>2020-10-11</value>
return : 當把轉換好的 Date 作爲 convert 方法的返回值後,
Spring ⾃動的爲birthday屬性進行注入(賦值)
*/
@Override
public Date convert(String source) { //聲明的是Data類型的轉換器
Date date = null;
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
date = sdf.parse(source);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
<!--創建 MyDateConverter 對象-->
<bean id="myDateConverter" class="com.baizhiedu.converter.MyDateConverter"/>
<!--用於註冊類型轉換器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="myDateConverter"/>
</set>
</property>
</bean>
<bean id="good" class="com.baizhiedu.converter.Good">
<property name="name" value="zy"/>
<property name="birthday" value="2020-6-12"/>
</bean>
細節處理
MyDateConverter
中的日期的格式,通過 依賴注入 的方式,由配置文件完成賦值。
public class MyDateConverter implements Converter<String, Date> {
private String pattern;
@Override
public Date convert(String source) {
Date date = null;
try {
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
date = sdf.parse(source);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
public String getPattern() {
return pattern;
}
public void setPattern(String pattern) {
this.pattern = pattern;
}
}
<!-- 配置文件完成對日期格式的賦值 -->
<bean id="myDateConverter" class="com.baizhiedu.converter.MyDateConverter">
<property name="pattern" value="yyyy-MM-dd"/>
</bean>
ConversionSeviceFactoryBean
定義 id屬性,值必須是 conversionService
;
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="myDateConverter"/>
</set>
</property>
</bean>
Spring 框架其實內置了日期類型的轉換器:日期格式必須是 2020/06/12
。
<bean id="good" class="com.baizhiedu.converter.Good">
<property name="name" value="zhenyu"/>
<property name="birthday" value="2012/12/12"/>
</bean>
後置處理 Bean
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return null; //Object bean, String beanName 類名 對象名
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Category) { //BeanPostProcessor 會對 Spring 工廠創建的所有對象進行加工
Category category = (Category) bean;
category.setName("baizhiedu");
return category;
}
return bean;
}
}
<bean id="myBeanPostProcessor" class="com.baizhiedu.beanpost.MyBeanPostProcessor"/>
靜態代理
靜態代理:爲每⼀個原始類,手工編寫⼀個代理類(.java .class)
接口
public interface UserService {
void register(User user);
boolean login(String name, String password);
}
原始類
public class UserServiceImpl implements UserService {
@Override
public void register(User user) {
System.out.println("UserServiceImpl.register 業務運算 + DAO");
}
@Override
public boolean login(String name, String password) {
System.out.println("UserServiceImpl.login 業務運算 + DAO");
return true;
}
}
代理類
代理類是爲原始類添加額外功能的
/**
* 靜態代理類編碼實現
*/
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 boolean login(String name, String password) {
System.out.println("---log---"); // 額外功能
return userService.login(name, password);
}
}
動態代理開發
搭建開發環境
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
創建原始對象(目標對象)
public interface UserService {
void register(User user);
boolean login(String name, String password);
}
public class UserServiceImpl implements UserService {
@Override
public void register(User user) {
System.out.println("UserServiceImpl.register 業務運算 + DAO");
}
@Override
public boolean login(String name, String password) {
System.out.println("UserServiceImpl.login 業務運算 + DAO");
return true;
}
}
額外功能
MethodBeforeAdvice
接口
public class Before implements MethodBeforeAdvice {
/**
* 作用: 把需要運行在原始方法執行之前運行的額外功能, 書寫在 before 方法中
*/
@Override
public void before(Method method, Object[] objects, Object target) throws Throwable {
System.out.println("---method before advice log---");
}
}
<!-- 額外功能 -->
<bean id="before" class="com.baizhiedu.aop.Before"/>
定義 切入點:額外功能的加入
⽬的: 由程序員根據⾃⼰的需要,決定額外功能加入給哪個原始方法(register、login)
<!--所有方法都做爲切入點,加入額外的功能(register、login)-->
<aop:config>
<aop:pointcut id="pc" expression="execution(* * (..))"/>
</aop:config>
組裝
<aop:config>
<aop:pointcut id="pc" expression="execution(* * (..))"/>
<!--組裝:目的把切入點與額外功能進行整合-->
<aop:advisor advice-ref="before" pointcut-ref="pc"/>
</aop:config>
調用
//Spring 的工廠通過原始對象的 id 值獲得的是代理對象
//獲得代理對象後,可以通過聲明接口類型,進行對象的存儲
@Test
public void test() {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
UserService userService = (UserService) ctx.getBean("userService");
userService.login("admin", "1234");
userService.register(new User());
}
動態字節碼技術:通過第三方動態字節碼框架,在 JVM 中創建對應類的字節碼,進而創建對象,當虛擬機結束,動態字節碼跟着消失。
結論:動態代理不需要定義類文件,都是 JVM 運行過程中動態創建的,所以不會造成類⽂件數量過多,影響項目管理的問題。
動態代理使得 額外功能的維護性大大增強
動態代理開發詳解
MethodBeforeAdvice
作用:需要把運行在原始方法執行之前運行的額外功能,書寫在before方法中
Method: 額外功能所增加給的那個原始方法
login方法register方法
showOrder方法
Object[]: 額外功能所增加給的那個原始方法的參數。String name,String password
UserObject: 額外功能所增加給的那個原始對象 UserServiceImpl
OrderServiceImpl
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("-----new method before advice log------");
}
MethodInterceptor(方法攔截器)
public class Around implements MethodInterceptor { //比如事務、拋出異常
@Override
public Object invoke(MethodInvocation Invocation) throws Throwable {
System.out.println("---額外功能運行在原始方法執行之前---");
Object ret = Invocation.proceed(); // 原始方法運行, 獲取原始方法的返回值
System.out.println("---額外功能運行在原始方法執行之後---");
return ret;
}
}
<!-- 額外功能 -->
<bean id="around" class="com.baizhiedu.dynamic.Around"/>
<aop:config>
<aop:pointcut id="pc" expression="execution(* * (..))"/>
<!--組裝:目的把切入點與額外功能進行整合-->
<aop:advisor advice-ref="around" pointcut-ref="pc"/>
</aop:config>
切入點表達式
方法切入點
定義一個方法
public void add(int i, int j)
* * (..)
* * (..) --> 所有方法
* ---> 修飾符 返回值
* ---> 方法名
() ---> 參數表
.. ---> 對於參數沒有要求 (參數有沒有,參數有⼏個都行,參數是什麼類型的都行)
<!-- 定義login作爲切入點 -->
<aop:pointcut id="pc" expression="execution(* login (..))"/>
<!-- 定義register作爲切入點 -->
<aop:pointcut id="pc" expression="execution(* register (..))"/>
精準方法切入點限定
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy.UserServiceImpl.login(..))"/>
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy.UserServiceImpl.login(String, String))"/>
類切入點
# 類中所有的方法加入了額外功能
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy.UserServiceImpl.*(..))"/>
# 忽略包
1. 類只存在一級包
<aop:pointcut id="pc" expression="execution(* *.UserServiceImpl.*(..))"/>
2. 類存在多級包
<aop:pointcut id="pc" expression="execution(* *..UserServiceImpl.*(..))"/>
包切入點(實戰中用的多)
# 切入點包中的所有類,必須在proxy中,不能在proxy包的⼦包中
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy.*.*(..))"/>
# 切入點當前包及其⼦包都生效
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy..*.*(..))"/>
切入點函數(execution、args、within)
args
args
作用:主要用於 函數(方法) 參數的匹配;
切入點:方法參數必須得是 2 個字符串類型的參數
# 使用 execution
<aop:pointcut id="pc" expression="execution(* *(String, String))"/>
# 使用 args
<aop:pointcut id="pc" expression="args(String, String)"/>
within
within
作用:主要用於進行 類、包切入點表達式 的匹配。
切入點: UserServiceImpl 這個類
# 使用 execution
<aop:pointcut id="pc" expression="expression(* *..UserServiceImpl.*(..))"/>
# 使用 within
<aop:pointcut id="pc" expression="within(*..UserServiceImpl)"/>
切入點: com.baizhiedu.proxy 這個包
# 使用 execution
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy..*.*(..)"/>
# 使用 within
<aop:pointcut id="pc" expression="within(com.baizhiedu.proxy..*)"/>
@annotation
作用:爲具有特殊註解的 方法 加入額外功能。
@Target(ElementType.METHOD) //註解需要用在方法上面
@Retention(RetentionPolicy.RUNTIME) //註解什麼時候起作用
public @interface Log {
}
<aop:pointcut id="pc" expression="@annotation(com.baizhiedu.Log)"/>
切入點函數的邏輯運算(and、or)
and 與操作
案例: 方法名叫 login 同時 參數是 2個字符串
# execution
<aop:pointcut id="pc" expression="execution(* login(String, String))"/>
# execution and args
<aop:pointcut id="pc" expression="execution(* login(..)) and args(String, String))"/>
注意:與操作不能⽤於同種類型的切⼊點函數
以下這個是錯誤的, 因爲不存在同時叫 login 和 register 的方法
<aop:pointcut id="pc" expression="execution(* login(..)) and execution(* register(..))"/>
or 或操作:
案例: 方法名叫 register 或 login 的⽅法作爲切⼊點
<aop:pointcut id="pc" expression="execution(* login(..)) or execution(* register(..))"/>
AOP
AOP (Aspect Oriented Programing)
⾯向切⾯編程 = Spring動態代理開發
以切⾯爲基本單位的程序開發,通過切⾯間的彼此協同,相互調⽤,完成程序的構建
切⾯ = 切⼊點 + 額外功能
OOP (Object Oritened Programing)
⾯向對象編程 Java
以對象爲基本單位的程序開發,通過對象間的彼此協同,相互調⽤,完成程序的構建
POP (Producer Oriented Programing)
⾯向過程(⽅法、函數)編程 C
以過程爲基本單位的程序開發,通過過程間的彼此協同,相互調⽤,完成程序的構建
AOP的概念:
本質就是Spring得動態代理開發,通過代理類爲原始類增加額外功能。
好處:利於原始類的維護
注意:AOP編程不可能取代OOP,OOP編程有意補充。
JDK 的動態代理
public class TestJDKProxy {
/*
1. 借用類加載器 TestJDKProxy
UserServiceImpl
2. JDK8.x前
final UserService userService = new UserServiceImpl();
*/
public static void main(String[] args) {
//1 創建原始對象
UserService userService = new UserServiceImpl();
//2 JDK創建動態代理
InvocationHandler handler = new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("------proxy log --------");
Object ret = method.invoke(userService, args); //原始方法運行
return ret;
}
};
UserService userServiceProxy = (UserService)Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(),userService.getClass().getInterfaces(),handler);
userServiceProxy.login("suns", "123456");
userServiceProxy.register(new User());
}
}
CGlib
CGlib創建動態代理的原理:⽗⼦繼承關係創建代理對象,原始類作爲⽗類,代理類作爲⼦類,這樣既可以保證2者⽅法⼀致,同時在代理類中提供新的實現(額外功能+原始⽅法)
public class UserService {
public void login(String name, String password) {
System.out.println("UserService.login");
}
public void register(User user) {
System.out.println("UserService.register");
}
}
package com.baizhiedu.cglib;
import com.baizhiedu.proxy.User;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class TestCglib {
public static void main(String[] args) {
//1 創建原始對象
UserService userService = new UserService();
/*
2 通過cglib方式創建動態代理對象
Proxy.newProxyInstance(classloader,interface,invocationhandler)
Enhancer.setClassLoader()
Enhancer.setSuperClass()
Enhancer.setCallback(); ---> MethodInterceptor(cglib)
Enhancer.create() ---> 代理
*/
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(TestCglib.class.getClassLoader());
enhancer.setSuperclass(userService.getClass());
MethodInterceptor interceptor = new MethodInterceptor() {
//等同於 InvocationHandler --- invoke
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("---cglib log----");
Object ret = method.invoke(userService, args);
return ret;
}
};
enhancer.setCallback(interceptor);
UserService userServiceProxy = (UserService) enhancer.create();
userServiceProxy.login("suns", "123345");
userServiceProxy.register(new User());
}
}
Spring 工廠如何加工原始對象
思路分析:主要通過 BeanPostProcessor
將原始對象加工爲代理對象
public class ProxyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
/*
Proxy.newProxyInstance();
*/
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("----- new Log-----");
Object ret = method.invoke(bean, args);
return ret;
}
};
return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(),bean.getClass().getInterfaces(),handler);
}
}
基於註解的 AOP 編程的開發
# 通過切⾯類 定義了 額外功能 @Around
定義了 切⼊點 @Around(“execution(* login(…))”)
@Aspect 切⾯類
/*
1. 額外功能
public class MyArround implements MethodInterceptor{
public Object invoke(MethodInvocation invocation){
Object ret = invocation.proceed();
return ret;
}
}
2. 切入點
<aop:config
<aop:pointcut id="" expression="execution(* login(..))"/>
*/
@Aspect
public class MyAspect {
@Around("execution(* login(..))")
public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("----aspect log ------");
Object ret =joinPoint.proceed();
return ret;
}
}
<bean id="userService" class="com.baizhiedu.aspect.UserServiceImpl"/>
<!--
切面
1. 額外功能
2. 切入點
3. 組裝切面
-->
<bean id="arround" class="com.baizhiedu.aspect.MyAspect"/>
<!--告知Spring基於註解進行AOP編程-->
<aop:aspectj-autoproxy proxy-target-class="true" />
切入點複用
切入點複用:在切面類中定義⼀個函數,上面用 @Pointcut
註解。
通過這種方式定義切入點表達式,後續更加有利於切入點複用。
@Aspect
public class MyAspect {
@Pointcut("execution(* *..UserServiceImpl.*(..))")
public void myPointcut(){}
@Around(value="myPointcut()")
public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("----aspect log ------");
Object ret = joinPoint.proceed();
return ret;
}
@Around(value="myPointcut()")
public Object arround1(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("----aspect tx ------");
Object ret = joinPoint.proceed();
return ret;
}
}
默認情況 AOP 編程 底層應用 JDK動態代理創建方式。
基於註解的 AOP 開發 中切換爲 Cglib:
<aop:aspectj-autoproxy proxy-target-class="true"/>
傳統的 AOP 開發 中切換爲 Cglib:
<aop:config proxy-target-class="true">
...
</aop:config>
AOP 開發中的一個坑
public class UserServiceImpl implements UserService, ApplicationContextAware {
private ApplicationContext ctx;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.ctx = applicationContext;
}
@Log
@Override
public void register(User user) {
System.out.println("UserServiceImpl.register 業務運算 + DAO ");
//throw new RuntimeException("測試異常");
//調用的是原始對象的login方法 ---> 核心功能
/*
設計目的:代理對象的login方法 ---> 額外功能+核心功能
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext2.xml");
UserService userService = (UserService) ctx.getBean("userService");
userService.login();
Spring工廠重量級資源 一個應用中 應該只創建一個工廠
*/
UserService userService = (UserService) ctx.getBean("userService");
userService.login("suns", "123456");
}
@Override
public boolean login(String name, String password) {
System.out.println("UserServiceImpl.login");
return true;
}
}
Object ret = joinPoint.proceed();
return ret;
}
}
**默認情況 AOP 編程 底層應用 JDK動態代理創建方式**。
基於註解的 AOP 開發 中切換爲 Cglib:
```xml
<aop:aspectj-autoproxy proxy-target-class="true"/>
傳統的 AOP 開發 中切換爲 Cglib:
<aop:config proxy-target-class="true">
...
</aop:config>