3.spring(三)
使用註解實現自動裝配
- JDK1.5開始可以支援註解,spring2.5開始支援註解
- 使用註解須知
- 匯入約束
- 配置註解支援
<?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: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/context http://www.springframework.org/schema/context/spring-context.xsd"> //註解配置 <context:annotation-config/> </beans>
使用上面一個人有兩隻寵物的例子
@Data
public class People {
@Autowired
private Cat cat;
@Autowired
private Dog dog;
private String name;
}
<?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: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/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <bean id="cat1" class="com.cdl.pojo.Cat"></bean> <bean id="dog2" class="com.cdl.pojo.Dog"></bean> <bean id="people" class="com.cdl.pojo.People"></bean> </beans>
@Autowired
直接寫在屬性上面即可,也可以在set方式上使用
使用@Autowired ,我們可以不用寫set方法,前提是你這個自動裝配的屬性在IOC容器中存在,且符合ByType
科普:
@Nullable
欄位標記這個,說明這個可以為null
@Autowired(required=true)
如果顯示定義了Autowired屬性為false,說明這個物件可以為null,否者不允許為空
@Data public class People { @Autowired private Cat cat; @Autowired @Qualifier(value = "dog222") private Dog dog; private String name; }
<?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: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/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean id="cat1" class="com.cdl.pojo.Cat"></bean>
<bean id="dog2" class="com.cdl.pojo.Dog"></bean>
<bean id="dog222" class="com.cdl.pojo.Dog"></bean>
<bean id="people" class="com.cdl.pojo.People"></bean>
</beans>
如果@Autowired自動裝配環境比較複雜,自動裝配無法通過一個註解@Autowired完成的時候,使用 @Qualifier(value = xxx)去配合@Autowired使用,制定一個唯一的Bean物件注入
@Resource -- java的註解,既可以通過名字也可以通過型別裝配
- @Resource
- @Resource(name="cat2")
小結:
- @Resource和@Autowired都是用來自動裝配的,都可以放在屬性的欄位上
- @Autowired通過ByType方式實現
- @Resource預設通過ByName,如果找不到名字,就是用ByType
Spring使用註解開發
- 在spring4,要使用註解開發,必須保證aop的包匯入(spring-webMvc包預設匯入了aop的包)!
- 與之前一樣,在使用註解開發之前,使用註解需匯入context約束,增加註解開發支援
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 掃描包下面的註解-->
<context:component-scan base-package="com.cdl"/>
<!-- 配置支援-->
<context:annotation-config/>
</beans>
實體類
@Data
@Component //等價於在spring中註冊一個bean,將該類交給spring管理,相當於配置文案的<bean/>標籤
@ComponentScan("prototype") //作用域 : 單例 ,原型
public class User {
@Value("陳丹龍") //給bean的屬性賦值,相當於bean標籤裡面的property標籤
private String name;
}
dao層
@Repository
public class UserDao {
}
service層
@Service
public class UserService {
}
註解:
- @compoent元件,放在類上,說明這個類被spring管理了,就是bean
- dao層:@Repository
- service層:@service
- controller層:@controller
- 這三個註解的功能與@compoent一樣的,只是在不同的層用不一樣的註解要好一點
- @Value("cdl") 給bean的屬性賦值,相當於bean標籤裡面的property標籤
- @ComponentScan("prototype") 作用域,值為單例或原型
小結:
- xml與註解:
- xml更加萬能,適用於任何場合,維護更加簡單,註解,不是自己的類使用不了,維護相對複雜
- xml與註解最佳實踐
- xml用來管理bean
- 註解用來屬性的注入
- 我們再用的過程中,只需要注意一個問題,就是在配置檔案裡,必須讓註解生效
使用java的方式配置spring(javaConfig)
- 現在完全不需要xml配置來做了,全權交給java來做
- JavaConfig是spring的子專案,在spring4之後,javaConfig的方式成為了一個核心功能
實體類
@Data
@Component
public class User {
@Value("陳丹龍")
public String name;
}
配置類
@Configuration //把這個類新增到spring容器中,並表示這是一個配置類
@ComponentScan("com.cdl") //掃描包
@Import(Config2.class) //引入一個配置類
public class Config {
//@bean,就相當於bean標籤,註冊一個bean
//方法名,就相當於id的值
//這個方法的返回值就相當於bean中的class屬性屬性
@Bean
public User getUser(){
return new User();
}
}
@Configuration
public class Config2 {
}
測試類
public class Test07 {
public static void main(String[] args) {
//若完全使用配置類的方式去做,這裡就需要使用AnnotationConfigApplicationContext上下文來獲取容器,通過配置類的class來載入
ApplicationContext config = new AnnotationConfigApplicationContext(Config.class);
User getUser = (User) config.getBean("getUser");
System.out.println(getUser);
}
}
這種純java的方式在springBoot中隨處可見
什麼叫spring的上下文?
應用上下文即是Spring容器抽象的一種實現;而我們常見的ApplicationContext本質上說就是一個維護Bean定義以及物件之間協作關係的高階介面。
spring的核心那就是容器,類似工廠的地方,應用程式中那麼多物件的產生銷燬,肯定需要一個地方來專門處理--容器,有一個很大家很熟悉的容器,tomcat,它是servlet的web容器,容器負責了物件整個的生命週期--------建立、裝配、銷燬,有一個專業的術語來形容spring容器----IOC容器,IOC是指我們在開發的過程中不用管物件的建立這些,都交給容器去處理,等於說把控制權交給了容器(控制反轉),這裡要說明一下,IOC不是spring專有的,還有很多通過IOC容器的框架。
光有spring容器也沒什麼用,容器說到底只是一個管理物件的空間,就像一個沒有圖紙的工廠,不知道怎麼生產產品,這就涉及到了spring應用上下文,說的簡單點就是容器的物件,是對spring容器抽象的實現,我們常見的ApplicationContext本質上來說是一種維護Bean的定義和物件之間協作關係的高階介面,
代理模式 靜態代理
- 為什麼要學習代理模式
- 因為這就是springAOP的底層
- 代理模式的分類
- 靜態代理
- 動態代理
- 靜態代理
- 角色分析
- 抽象角色:一般使用介面或抽象類來解決
- 真實角色:被代理的角色
- 代理角色:代理真實角色,代理真實角色後,一般會做一些附屬操作
- 客戶:訪問代理物件的人
- 角色分析
抽象角色
//租房
public interface Rent {
public void rent();
}
真實角色
//房東
public class FangDong implements Rent{
@Override
public void rent() {
System.out.println("房東要出租房子!");
}
}
代理角色
//中介
public class Proxy implements Rent{
private FangDong fangDong;
public Proxy(FangDong fangDong) {
this.fangDong = fangDong;
}
public Proxy(){
}
@Override
public void rent() {
fangDong.rent();
seeHost();
qianHeTong();
}
public void seeHost(){
System.out.println("看房子");
}
public void qianHeTong(){
System.out.println("籤合同");
}
}
客戶
//我要租房子
public class MySelf {
public static void main(String[] args) {
//房東要出租房子
FangDong fangDong = new FangDong();
//代理,中介幫房東出租房子,但是一般會做一些附屬操作
Proxy proxy = new Proxy(fangDong);
//中介直接給你租房子
proxy.rent();
}
}
代理模式的好處:
- 可以使更加真實角色的操作更加純粹,不用去關注公共業務
- 公共業務就交給了代理角色,實現了業務的分工
- 公共業務發生擴充套件的時候,方便集中管理
缺點 - 一個真實的角色就會產生一個代理角色,程式碼量翻倍,開發效率低
靜態代理在理解
介面
public interface UserService {
public void add();
public void delete();
public void update();
public void select();
}
實現類
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加");
}
@Override
public void delete() {
System.out.println("刪除");
}
@Override
public void update() {
System.out.println("修改");
}
@Override
public void select() {
System.out.println("查詢");
}
}
代理類
/**
* 利用代理模式,增加一項列印日誌的功能,
* 不需要改動原來的程式碼,即可實現功能,實現橫向開發
* */
public class Proxy implements UserService{
private UserServiceImpl userService;
public UserServiceImpl getUserService() {
return userService;
}
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
@Override
public void add() {
msg();
userService.add();
}
@Override
public void delete() {
msg();
userService.delete();
}
@Override
public void update() {
msg();
userService.update();
}
@Override
public void select() {
msg();
userService.select();
}
public void msg(){
System.out.println("列印日誌");
}
}
主方法
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
Proxy proxy = new Proxy();
proxy.setUserService(userService);
proxy.add();
}
}
這樣既可不改變原來的程式碼,改變原來的程式碼,在公司是大忌,最大優點就是不改變原來的程式碼,但是每次代理一個角色,就需要去寫一個實現類,太麻煩
動態代理詳解 底層:反射
- 動態代理和靜態代理的角色是一樣的
- 動態代理類是動態生成的,不是我們直接寫好的
- 動態代理類分為兩大類
- 基於介面的動態代理:JDK動態代理【使用這個例子學習】
- 基於類的動態代理:cglib動態代理
- 另外還可以通過java的位元組碼生成:Javassist
JDK動態代理
- 需要了解兩個類:Proxy(代理類) InvocationHandler(呼叫處理程式)
- InvocationHandler
- 是一個介面,java.lang.reflect反射包下,他是一個由代理例項的呼叫處理程式實現介面,每一個代理例項都有一個關聯的呼叫處理程式,當在代理例項上呼叫方法時,方法呼叫將被編碼並分派到其呼叫處理程式的invoke方法
- 其下只有一個invoke方法,處理代理例項上的方法,呼叫並返回結果
- invoke方法的三個引數:
- proxy 需要代理的物件
- method 物件的方法
- args 方法引數
- proxy類
- 也是反射包下(java.lang.reflect)
- proxy提供了建立動態代理類和例項的靜態方法,他也是這些方法建立的動態類的超類(父類)
- InvocationHandler
使用上面靜態代理的例子
//使用這個類自動生成代理類
public class ProxyInvocationHandler implements InvocationHandler {
//需要代理的介面
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成代理類
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
//處理代理例項,並返回一個結果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
Object result = method.invoke(target, args);
return result;
}
//若要在代理類加入一個什麼方法,即直接將該方法載入處理
public void log(String args){
System.out.println("執行了"+args+"方法");
}
}
代理多個類,只要實現了同一個介面
public class Client {
public static void main(String[] args) {
//真實角色
UserServiceImpl userService = new UserServiceImpl();
UserServiceImpl02 userServiceImpl02 = new UserServiceImpl02();
//代理角色,不存在
ProxyInvocationHandler pih = new ProxyInvocationHandler();
//設定要代理的物件
pih.setTarget(userService);
pih.setTarget(userServiceImpl02);
//動態生成代理類
UserService proxy = (UserService) pih.getProxy();
proxy.add();
proxy.select();
}
}
作用:
- proxy:生成動態代理例項
- InvocationHandler:呼叫處理程式,並返回一個結果
動態代理的優點:一個動態代理類可以代理多個類,只要這些類實現的是同一個介面
AOP
- 什麼是AOP?(Aspect Oriented Proramming)
- AOP,意味為著面向切面程式設計,通過預編譯和執行期動態代理,實現程式功能的統一維護的一種技術,AOP是OOP的延續,是軟體開發中的一個熱點,也是spring框架中的一個重要內容,是函數語言程式設計的一種衍生泛型,利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯的耦合度降低,提高程式的可用性,同事提高開發效率
- AOP在spring中的作用
- 提供宣告式事務
- 允許使用者自定義切面
- AOP常用術語
- 橫切關注點:跨越應用程式的多個模組的方法和功能,即使與我們業務邏輯無關,但是也是我們需要關注的部分,就是橫切關注點,如日誌,安全,快取,事務等:
- 切面(Aspect):橫切關注點被模組化的一個物件,即一個類
- 通知(Advice):切面必須完成的一個工作,即類中的一個方法
- 目標(target):被通知物件
- 代理(proxy):向目標物件應用通知之後,插入的物件
- 切入點(PointCut):切面通知執行的"地點"的定義
- 連線點(jointPoint):與切入點匹配的執行點
- Spring中通過advice定義橫切邏輯,spring支援五種型別的advice
- 前置通知
- 後置通知
- 環繞通知:方法前後
- 異常丟擲通知:方法丟擲異常
- 引介通知:類中增加的新的方法,屬性
- 即使用AOP在不改變原來的程式碼,增加新功能
<!--使用AOP,需要匯入一個織入包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
方式一:使用sping的api介面
public interface UserService {
void add();
void select();
void delete();
void update();
}
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加");
}
@Override
public void select() {
System.out.println("查詢");
}
@Override
public void delete() {
System.out.println("刪除");
}
@Override
public void update() {
System.out.println("修改");
}
}
public class Log implements MethodBeforeAdvice {
/**
* method:要執行的目標引數的方法
* args:引數
* target:目標物件
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName() + "執行了" + method.getName() + "方法");
}
}
public class AfterLog implements AfterReturningAdvice {
//Object o:方法執行後的返回值
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println(method.getName()+"方法的返回結果為"+ o);
}
}
<?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
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 註冊bean-->
<bean id="userService" class="com.cdl.service.UserServiceImpl"></bean>
<bean id="log" class="com.cdl.service.Log"></bean>
<bean id="afterLog" class="com.cdl.service.AfterLog"></bean>
<!-- 方式一:使用原生spring-api介面 -->
<!-- 配置AOP,需要匯入約束-->
<aop:config>
<!-- 配置切入點,expression表示式,execution需要執行的位置,目標物件UserServiceImpl下的所有方法及方法的引數,
可以配置多個切入點-->
<aop:pointcut id="pointCut" expression="execution(* com.cdl.service.UserServiceImpl.*(..))"/>
<!-- 執行環繞增強-->
<!-- 意思就是把LOG這個方法切入到哪個方法上面-->
<aop:advisor advice-ref="log" pointcut-ref="pointCut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointCut"/>
</aop:config>
</beans>
public class Test09 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
//動態代理的是介面
UserService userService = (UserService) context.getBean("userService");
userService.add();
}
}
方式二:使用自定義類實現aop[主要是切面定義]
public class DiyPointCut {
public void before(){
System.out.println("前置通知");
}
public void after(){
System.out.println("後置通知");
}
}
<!-- 自定義類-->
<bean id="diyPointCut" class="com.cdl.diy.DiyPointCut"> </bean>
<aop:config>
<!-- 自定義切面,ref是需要引用的類-->
<aop:aspect ref="diyPointCut">
<!-- 切入點-->
<aop:pointcut id="pointCut" expression="execution(* com.cdl.service.UserServiceImpl.*(..))"/>
<!-- 通知-->
<aop:before method="before" pointcut-ref="pointCut"></aop:before>
<aop:after method="after" pointcut-ref="pointCut"></aop:after>
</aop:aspect>
</aop:config>
方式三:使用註解實現
/**
* 方式三:使用註解方式實現AOP
* @Aspect:表示將該類配置為切面
*/
@Aspect
public class AnnotationPointCut {
@Before("execution(* com.cdl.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("------方法執行前--------");
}
@After("execution(* com.cdl.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("------方法執行後--------");
}
//在環繞增強中,我們可以傳入一個引數,代表我們要獲取資料切入的點
@Around("execution(* com.cdl.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint pt) throws Throwable {
System.out.println("------環繞前--------");
Object proceed = pt.proceed();
System.out.println("------環繞後--------");
}
}
<!-- 方式三:註解-->
<bean id="annotationPoint" class="com.cdl.diy.AnnotationPointCut"></bean>
<!--開啟註解支援-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
宣告式事務
- 宣告式事務
在宣告式事務中,要配置一個切面,其中就用到了Propagation,其中,Propagation表示打算對這些方法怎麼使用事務,是用還是不用,Propagation有七種屬性
- REOUIRED:支援當前事務,若當前沒有事務,就新建一個事務
- SUPPORTS:支援當前事務,若當前沒有事務,就以非事務的方式執行
- MANDATORY:支援當前事務,若當前沒有事務,就丟擲異常,
- REQUIRES_NEW:新建事務,若當前存在事務,就把當前事務掛起
- NOT_SUPPORTED:以非事務的方式執行操作,若當前存在事務,把當前事務掛起
- NEVER:以非事務的方式執行,若當前存在事務,則丟擲異常
- NESTED:支援當前事務,若當前存在事務,則執行一個巢狀事務,若當前沒有事務,就新建一個事務
- 程式設計式事務